Last active
December 11, 2015 02:39
-
-
Save jensck/4532039 to your computer and use it in GitHub Desktop.
Script for Android developers to help find which of jars are causing the most pain re: the dex method count limit (more info: http://goo.gl/kHSox )
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
"""Util script to determine how many methods are in a given (list of) jar(s). | |
Requires Python 2.6 or higher (it even works with Python 3!) | |
Special thanks to "JesusFreke" on StackOverflow for providing the info on how to extract the | |
method count from a dex file: http://stackoverflow.com/a/14026180/180788 | |
""" | |
from __future__ import print_function | |
import glob | |
import os | |
import struct | |
import subprocess | |
import sys | |
from textwrap import dedent | |
def dex_it(jar_path, dex_path): | |
cmd = "dx --dex --output={dex} {jar} &> /dev/null".format(dex=dex_path, jar=jar_path) | |
print("Processing '{0}'...".format(jar_path)) | |
subprocess.call(cmd, shell=True) | |
def get_method_count(dex_path): | |
with open(dex_path, 'rb') as dex: | |
# read 88 bytes in, plus the 4 bytes for the actual integer data we want | |
dex.seek(88) | |
method_count_bytes = dex.read(4) | |
return struct.unpack('<I', method_count_bytes)[0] | |
def print_report(jar_list): | |
def _process_jar(jar_path): | |
dex_path = 'dex_counter_temp.dex' | |
try: | |
dex_it(jar_path, dex_path) | |
count = get_method_count(dex_path) | |
except KeyboardInterrupt: | |
sys.exit("\n\nCancelled by ^C, exiting.\n") | |
finally: | |
# make sure we clean up after ourselves. | |
try: os.unlink(dex_path) | |
except: pass | |
return count | |
# print "manifest" of jars we're going to inspect | |
print("Found {0} jars to check:\n".format(len(jar_list))) | |
for jar in jar_list: | |
print(jar) | |
print('\n') | |
# analyze each jar and immediately give feedback | |
counts = [] | |
for jar in jar_list: | |
count = _process_jar(jar) | |
print("Methods found:", count, "\n") | |
counts.append((count, jar)) | |
# don't bother for a single jar | |
if len(jar_list) > 1: | |
total = 0 | |
# give a list of the worst offenders | |
print("Worst offenders (jars with the most methods): ") | |
for count, jar in sorted(counts, reverse=True): | |
print("{0} methods: {1}".format(count, jar)) | |
total += count | |
print("Total count:", total) | |
if total > 65535: | |
print("DEX METHOD COUNT LIMIT EXCEEDED BY:", total - 65535) | |
def _get_jar_list(): | |
if len(sys.argv) < 2: | |
print("No args given; will analyze all jars in current dir\n") | |
return glob.glob("*.jar") | |
elif len(sys.argv) == 2 and os.path.isdir(sys.argv[1]): | |
return glob.glob(sys.argv[1] + "/*.jar") | |
else: | |
return [jar_path for jar_path in sys.argv[1:] if jar_path.endswith(".jar")] | |
def main(): | |
jars = sorted(_get_jar_list()) | |
if not jars: | |
sys.exit(dedent("""\ | |
ERROR: No jars found to process. Either run the script from a dir with jars in it, or | |
provide arguments of either: | |
A) a path with jars in it, OR | |
B) a list of paths to individual jars""")) | |
else: | |
print_report(jars) | |
print("\n\nDone.") | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment