Fields Summary |
---|
private static final String | DEX_EXTENSIONFile extension of a {@code .dex} file. |
private static final String | DEX_PREFIXFile name prefix of a {@code .dex} file automatically loaded in an
archive. |
private static final String | IN_RE_CORE_CLASSES{@code non-null;} the lengthy message that tries to discourage
people from defining core classes in applications |
private static final String | MANIFEST_NAME{@code non-null;} name of the standard manifest file in {@code .jar}
files |
private static final Attributes$Name | CREATED_BY{@code non-null;} attribute name for the (quasi-standard?)
{@code Created-By} attribute |
private static final String[] | JAVAX_CORE{@code non-null;} list of {@code javax} subpackages that are considered
to be "core". Note:: This list must be sorted, since it
is binary-searched. |
private static final int | MAX_METHOD_ADDED_DURING_DEX_CREATION |
private static final int | MAX_FIELD_ADDED_DURING_DEX_CREATION |
private static AtomicInteger | errorsnumber of errors during processing |
private static Arguments | args{@code non-null;} parsed command-line arguments |
private static com.android.dx.dex.file.DexFile | outputDex{@code non-null;} output file in-progress |
private static TreeMap | outputResources{@code null-ok;} map of resources to include in the output, or
{@code null} if resources are being ignored |
private static final List | libraryDexBuffersLibrary .dex files to merge into the output .dex. |
private static ExecutorService | threadPoolthread pool object used for multi-threaded file processing |
private static List | parallelProcessorFuturesused to handle Errors for multi-threaded file processing |
private static volatile boolean | anyFilesProcessedtrue if any files are successfully processed |
private static long | minimumFileAgeclass files older than this must be defined in the target dex file. |
private static Set | classesInMainDex |
private static List | dexOutputArrays |
private static OutputStreamWriter | humanOutWriter |
Methods Summary |
---|
private static void | checkClassName(java.lang.String name)Check the class name to make sure it's not a "core library"
class. If there is a problem, this updates the error count and
throws an exception to stop processing.
boolean bogus = false;
if (name.startsWith("java/")) {
bogus = true;
} else if (name.startsWith("javax/")) {
int slashAt = name.indexOf('/", 6);
if (slashAt == -1) {
// Top-level javax classes are verboten.
bogus = true;
} else {
String pkg = name.substring(6, slashAt);
bogus = (Arrays.binarySearch(JAVAX_CORE, pkg) >= 0);
}
}
if (! bogus) {
return;
}
/*
* The user is probably trying to include an entire desktop
* core library in a misguided attempt to get their application
* working. Try to help them understand what's happening.
*/
DxConsole.err.println("\ntrouble processing \"" + name + "\":\n\n" +
IN_RE_CORE_CLASSES);
errors.incrementAndGet();
throw new StopProcessing();
|
private static void | closeOutput(java.io.OutputStream stream)Flushes and closes the given output stream, except if it happens to be
{@link System#out} in which case this method does the flush but not
the close. This method will also silently do nothing if given a
{@code null} argument.
if (stream == null) {
return;
}
stream.flush();
if (stream != System.out) {
stream.close();
}
|
private static void | createDexFile()
if (outputDex != null) {
dexOutputArrays.add(writeDex());
}
outputDex = new DexFile(args.dexOptions);
if (args.dumpWidth != 0) {
outputDex.setDumpWidth(args.dumpWidth);
}
|
private static boolean | createJar(java.lang.String fileName)Creates a jar file from the resources (including dex file arrays).
/*
* Make or modify the manifest (as appropriate), put the dex
* array into the resources map, and then process the entire
* resources map in a uniform manner.
*/
try {
Manifest manifest = makeManifest();
OutputStream out = openOutput(fileName);
JarOutputStream jarOut = new JarOutputStream(out, manifest);
try {
for (Map.Entry<String, byte[]> e :
outputResources.entrySet()) {
String name = e.getKey();
byte[] contents = e.getValue();
JarEntry entry = new JarEntry(name);
int length = contents.length;
if (args.verbose) {
DxConsole.out.println("writing " + name + "; size " + length + "...");
}
entry.setSize(length);
jarOut.putNextEntry(entry);
jarOut.write(contents);
jarOut.closeEntry();
}
} finally {
jarOut.finish();
jarOut.flush();
closeOutput(out);
}
} catch (Exception ex) {
if (args.debug) {
DxConsole.err.println("\ntrouble writing output:");
ex.printStackTrace(DxConsole.err);
} else {
DxConsole.err.println("\ntrouble writing output: " +
ex.getMessage());
}
return false;
}
return true;
|
private static void | dumpMethod(com.android.dx.dex.file.DexFile dex, java.lang.String fqName, java.io.OutputStreamWriter out)Dumps any method with the given name in the given file.
boolean wildcard = fqName.endsWith("*");
int lastDot = fqName.lastIndexOf('.");
if ((lastDot <= 0) || (lastDot == (fqName.length() - 1))) {
DxConsole.err.println("bogus fully-qualified method name: " +
fqName);
return;
}
String className = fqName.substring(0, lastDot).replace('.", '/");
String methodName = fqName.substring(lastDot + 1);
ClassDefItem clazz = dex.getClassOrNull(className);
if (clazz == null) {
DxConsole.err.println("no such class: " + className);
return;
}
if (wildcard) {
methodName = methodName.substring(0, methodName.length() - 1);
}
ArrayList<EncodedMethod> allMeths = clazz.getMethods();
TreeMap<CstNat, EncodedMethod> meths =
new TreeMap<CstNat, EncodedMethod>();
/*
* Figure out which methods to include in the output, and get them
* all sorted, so that the printout code is robust with respect to
* changes in the underlying order.
*/
for (EncodedMethod meth : allMeths) {
String methName = meth.getName().getString();
if ((wildcard && methName.startsWith(methodName)) ||
(!wildcard && methName.equals(methodName))) {
meths.put(meth.getRef().getNat(), meth);
}
}
if (meths.size() == 0) {
DxConsole.err.println("no such method: " + fqName);
return;
}
PrintWriter pw = new PrintWriter(out);
for (EncodedMethod meth : meths.values()) {
// TODO: Better stuff goes here, perhaps.
meth.debugPrint(pw, args.verboseDump);
/*
* The (default) source file is an attribute of the class, but
* it's useful to see it in method dumps.
*/
CstString sourceFile = clazz.getSourceFile();
if (sourceFile != null) {
pw.println(" source file: " + sourceFile.toQuoted());
}
Annotations methodAnnotations =
clazz.getMethodAnnotations(meth.getRef());
AnnotationsList parameterAnnotations =
clazz.getParameterAnnotations(meth.getRef());
if (methodAnnotations != null) {
pw.println(" method annotations:");
for (Annotation a : methodAnnotations.getAnnotations()) {
pw.println(" " + a);
}
}
if (parameterAnnotations != null) {
pw.println(" parameter annotations:");
int sz = parameterAnnotations.size();
for (int i = 0; i < sz; i++) {
pw.println(" parameter " + i);
Annotations annotations = parameterAnnotations.get(i);
for (Annotation a : annotations.getAnnotations()) {
pw.println(" " + a);
}
}
}
}
pw.flush();
|
private static java.lang.String | fixPath(java.lang.String path)Returns the "fixed" version of a given file path, suitable for
use as a path within a {@code .jar} file and for checking
against a classfile-internal "this class" name. This looks for
the last instance of the substring {@code "/./"} within
the path, and if it finds it, it takes the portion after to be
the fixed path. If that isn't found but the path starts with
{@code "./"}, then that prefix is removed and the rest is
return. If neither of these is the case, this method returns
its argument.
/*
* If the path separator is \ (like on windows), we convert the
* path to a standard '/' separated path.
*/
if (File.separatorChar == '\\") {
path = path.replace('\\", '/");
}
int index = path.lastIndexOf("/./");
if (index != -1) {
return path.substring(index + 3);
}
if (path.startsWith("./")) {
return path.substring(2);
}
return path;
|
private static java.lang.String | getDexFileName(int i)
if (i == 0) {
return DexFormat.DEX_IN_JAR_NAME;
} else {
return DEX_PREFIX + (i + 1) + DEX_EXTENSION;
}
|
public static java.lang.String | getTooManyIdsErrorMessage(){@code non-null;} Error message for too many method/field/type ids.
if (args.multiDex) {
return "The list of classes given in " + Arguments.MAIN_DEX_LIST_OPTION +
" is too big and does not fit in the main dex.";
} else {
return "You may try using " + Arguments.MULTI_DEX_OPTION + " option.";
}
|
public static void | main(java.lang.String[] argArray)Run and exit if something unexpected happened.
Arguments arguments = new Arguments();
arguments.parse(argArray);
int result = run(arguments);
if (result != 0) {
System.exit(result);
}
|
private static java.util.jar.Manifest | makeManifest()Creates and returns the manifest to use for the output. This may
modify {@link #outputResources} (removing the pre-existing manifest).
byte[] manifestBytes = outputResources.get(MANIFEST_NAME);
Manifest manifest;
Attributes attribs;
if (manifestBytes == null) {
// We need to construct an entirely new manifest.
manifest = new Manifest();
attribs = manifest.getMainAttributes();
attribs.put(Attributes.Name.MANIFEST_VERSION, "1.0");
} else {
manifest = new Manifest(new ByteArrayInputStream(manifestBytes));
attribs = manifest.getMainAttributes();
outputResources.remove(MANIFEST_NAME);
}
String createdBy = attribs.getValue(CREATED_BY);
if (createdBy == null) {
createdBy = "";
} else {
createdBy += " + ";
}
createdBy += "dx " + Version.VERSION;
attribs.put(CREATED_BY, createdBy);
attribs.putValue("Dex-Location", DexFormat.DEX_IN_JAR_NAME);
return manifest;
|
private static byte[] | mergeIncremental(byte[] update, java.io.File base)Merges the dex files {@code update} and {@code base}, preferring
{@code update}'s definition for types defined in both dex files.
Dex dexA = null;
Dex dexB = null;
if (update != null) {
dexA = new Dex(update);
}
if (base.exists()) {
dexB = new Dex(base);
}
Dex result;
if (dexA == null && dexB == null) {
return null;
} else if (dexA == null) {
result = dexB;
} else if (dexB == null) {
result = dexA;
} else {
result = new DexMerger(dexA, dexB, CollisionPolicy.KEEP_FIRST).merge();
}
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
result.writeTo(bytesOut);
return bytesOut.toByteArray();
|
private static byte[] | mergeLibraryDexBuffers(byte[] outArray)Merges the dex files in library jars. If multiple dex files define the
same type, this fails with an exception.
for (byte[] libraryDex : libraryDexBuffers) {
if (outArray == null) {
outArray = libraryDex;
continue;
}
Dex a = new Dex(outArray);
Dex b = new Dex(libraryDex);
Dex ab = new DexMerger(a, b, CollisionPolicy.FAIL).merge();
outArray = ab.getBytes();
}
return outArray;
|
private static java.io.OutputStream | openOutput(java.lang.String name)Opens and returns the named file for writing, treating "-" specially.
if (name.equals("-") ||
name.startsWith("-.")) {
return System.out;
}
return new FileOutputStream(name);
|
private static boolean | processAllFiles()Constructs the output {@link DexFile}, fill it in with all the
specified classes, and populate the resources map if required.
createDexFile();
if (args.jarOutput) {
outputResources = new TreeMap<String, byte[]>();
}
anyFilesProcessed = false;
String[] fileNames = args.fileNames;
if (args.numThreads > 1) {
threadPool = Executors.newFixedThreadPool(args.numThreads);
parallelProcessorFutures = new ArrayList<Future<Void>>();
}
try {
if (args.mainDexListFile != null) {
// with --main-dex-list
FileNameFilter mainPassFilter = args.strictNameCheck ? new MainDexListFilter() :
new BestEffortMainDexListFilter();
// forced in main dex
for (int i = 0; i < fileNames.length; i++) {
processOne(fileNames[i], mainPassFilter);
}
if (dexOutputArrays.size() > 0) {
throw new DexException("Too many classes in " + Arguments.MAIN_DEX_LIST_OPTION
+ ", main dex capacity exceeded");
}
if (args.minimalMainDex) {
// start second pass directly in a secondary dex file.
createDexFile();
}
// remaining files
for (int i = 0; i < fileNames.length; i++) {
processOne(fileNames[i], new NotFilter(mainPassFilter));
}
} else {
// without --main-dex-list
for (int i = 0; i < fileNames.length; i++) {
processOne(fileNames[i], ClassPathOpener.acceptAll);
}
}
} catch (StopProcessing ex) {
/*
* Ignore it and just let the error reporting do
* their things.
*/
}
if (args.numThreads > 1) {
try {
threadPool.shutdown();
if (!threadPool.awaitTermination(600L, TimeUnit.SECONDS)) {
throw new RuntimeException("Timed out waiting for threads.");
}
} catch (InterruptedException ex) {
threadPool.shutdownNow();
throw new RuntimeException("A thread has been interrupted.");
}
try {
for (Future<?> future : parallelProcessorFutures) {
future.get();
}
} catch (ExecutionException e) {
Throwable cause = e.getCause();
// All Exceptions should have been handled in the ParallelProcessor, only Errors
// should remain
if (cause instanceof Error) {
throw (Error) e.getCause();
} else {
throw new AssertionError(e.getCause());
}
} catch (InterruptedException e) {
// If we're here, it means all threads have completed cleanly, so there should not be
// any InterruptedException
throw new AssertionError(e);
}
}
int errorNum = errors.get();
if (errorNum != 0) {
DxConsole.err.println(errorNum + " error" +
((errorNum == 1) ? "" : "s") + "; aborting");
return false;
}
if (args.incremental && !anyFilesProcessed) {
return true;
}
if (!(anyFilesProcessed || args.emptyOk)) {
DxConsole.err.println("no classfiles specified");
return false;
}
if (args.optimize && args.statistics) {
CodeStatistics.dumpStatistics(DxConsole.out);
}
return true;
|
private static boolean | processClass(java.lang.String name, byte[] bytes)Processes one classfile.
if (! args.coreLibrary) {
checkClassName(name);
}
DirectClassFile cf =
new DirectClassFile(bytes, name, args.cfOptions.strictNameCheck);
cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
cf.getMagic();
int numMethodIds = outputDex.getMethodIds().items().size();
int numFieldIds = outputDex.getFieldIds().items().size();
int constantPoolSize = cf.getConstantPool().size();
int maxMethodIdsInDex = numMethodIds + constantPoolSize + cf.getMethods().size() +
MAX_METHOD_ADDED_DURING_DEX_CREATION;
int maxFieldIdsInDex = numFieldIds + constantPoolSize + cf.getFields().size() +
MAX_FIELD_ADDED_DURING_DEX_CREATION;
if (args.multiDex
// Never switch to the next dex if current dex is already empty
&& (outputDex.getClassDefs().items().size() > 0)
&& ((maxMethodIdsInDex > args.maxNumberOfIdxPerDex) ||
(maxFieldIdsInDex > args.maxNumberOfIdxPerDex))) {
DexFile completeDex = outputDex;
createDexFile();
assert (completeDex.getMethodIds().items().size() <= numMethodIds +
MAX_METHOD_ADDED_DURING_DEX_CREATION) &&
(completeDex.getFieldIds().items().size() <= numFieldIds +
MAX_FIELD_ADDED_DURING_DEX_CREATION);
}
try {
ClassDefItem clazz =
CfTranslator.translate(cf, bytes, args.cfOptions, args.dexOptions, outputDex);
synchronized (outputDex) {
outputDex.add(clazz);
}
return true;
} catch (ParseException ex) {
DxConsole.err.println("\ntrouble processing:");
if (args.debug) {
ex.printStackTrace(DxConsole.err);
} else {
ex.printContext(DxConsole.err);
}
}
errors.incrementAndGet();
return false;
|
private static boolean | processFileBytes(java.lang.String name, long lastModified, byte[] bytes)Processes one file, which may be either a class or a resource.
boolean isClass = name.endsWith(".class");
boolean isClassesDex = name.equals(DexFormat.DEX_IN_JAR_NAME);
boolean keepResources = (outputResources != null);
if (!isClass && !isClassesDex && !keepResources) {
if (args.verbose) {
DxConsole.out.println("ignored resource " + name);
}
return false;
}
if (args.verbose) {
DxConsole.out.println("processing " + name + "...");
}
String fixedName = fixPath(name);
if (isClass) {
if (keepResources && args.keepClassesInJar) {
synchronized (outputResources) {
outputResources.put(fixedName, bytes);
}
}
if (lastModified < minimumFileAge) {
return true;
}
return processClass(fixedName, bytes);
} else if (isClassesDex) {
synchronized (libraryDexBuffers) {
libraryDexBuffers.add(bytes);
}
return true;
} else {
synchronized (outputResources) {
outputResources.put(fixedName, bytes);
}
return true;
}
|
private static void | processOne(java.lang.String pathname, com.android.dx.cf.direct.ClassPathOpener.FileNameFilter filter)Processes one pathname element.
ClassPathOpener opener;
opener = new ClassPathOpener(pathname, false, filter,
new ClassPathOpener.Consumer() {
@Override
public boolean processFileBytes(String name, long lastModified, byte[] bytes) {
return Main.processFileBytes(name, lastModified, bytes);
}
@Override
public void onException(Exception ex) {
if (ex instanceof StopProcessing) {
throw (StopProcessing) ex;
} else if (ex instanceof SimException) {
DxConsole.err.println("\nEXCEPTION FROM SIMULATION:");
DxConsole.err.println(ex.getMessage() + "\n");
DxConsole.err.println(((SimException) ex).getContext());
} else {
DxConsole.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
ex.printStackTrace(DxConsole.err);
}
errors.incrementAndGet();
}
@Override
public void onProcessArchiveStart(File file) {
if (args.verbose) {
DxConsole.out.println("processing archive " + file +
"...");
}
}
});
if (args.numThreads > 1) {
parallelProcessorFutures.add(threadPool.submit(new ParallelProcessor(opener)));
} else {
if (opener.process()) {
anyFilesProcessed = true;
}
}
|
private static void | readPathsFromFile(java.lang.String fileName, java.util.Collection paths)
BufferedReader bfr = null;
try {
FileReader fr = new FileReader(fileName);
bfr = new BufferedReader(fr);
String line;
while (null != (line = bfr.readLine())) {
paths.add(fixPath(line));
}
} finally {
if (bfr != null) {
bfr.close();
}
}
|
public static int | run(com.android.dx.command.dexer.Main$Arguments arguments)Run and return a result code.
// Reset the error count to start fresh.
errors.set(0);
// empty the list, so that tools that load dx and keep it around
// for multiple runs don't reuse older buffers.
libraryDexBuffers.clear();
args = arguments;
args.makeOptionsObjects();
OutputStream humanOutRaw = null;
if (args.humanOutName != null) {
humanOutRaw = openOutput(args.humanOutName);
humanOutWriter = new OutputStreamWriter(humanOutRaw);
}
try {
if (args.multiDex) {
return runMultiDex();
} else {
return runMonoDex();
}
} finally {
closeOutput(humanOutRaw);
}
|
private static int | runMonoDex()
File incrementalOutFile = null;
if (args.incremental) {
if (args.outName == null) {
System.err.println(
"error: no incremental output name specified");
return -1;
}
incrementalOutFile = new File(args.outName);
if (incrementalOutFile.exists()) {
minimumFileAge = incrementalOutFile.lastModified();
}
}
if (!processAllFiles()) {
return 1;
}
if (args.incremental && !anyFilesProcessed) {
return 0; // this was a no-op incremental build
}
// this array is null if no classes were defined
byte[] outArray = null;
if (!outputDex.isEmpty() || (args.humanOutName != null)) {
outArray = writeDex();
if (outArray == null) {
return 2;
}
}
if (args.incremental) {
outArray = mergeIncremental(outArray, incrementalOutFile);
}
outArray = mergeLibraryDexBuffers(outArray);
if (args.jarOutput) {
// Effectively free up the (often massive) DexFile memory.
outputDex = null;
if (outArray != null) {
outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray);
}
if (!createJar(args.outName)) {
return 3;
}
} else if (outArray != null && args.outName != null) {
OutputStream out = openOutput(args.outName);
out.write(outArray);
closeOutput(out);
}
return 0;
|
private static int | runMultiDex()
assert !args.incremental;
assert args.numThreads == 1;
if (args.mainDexListFile != null) {
classesInMainDex = new HashSet<String>();
readPathsFromFile(args.mainDexListFile, classesInMainDex);
}
if (!processAllFiles()) {
return 1;
}
if (!libraryDexBuffers.isEmpty()) {
throw new DexException("Library dex files are not supported in multi-dex mode");
}
if (outputDex != null) {
// this array is null if no classes were defined
dexOutputArrays.add(writeDex());
// Effectively free up the (often massive) DexFile memory.
outputDex = null;
}
if (args.jarOutput) {
for (int i = 0; i < dexOutputArrays.size(); i++) {
outputResources.put(getDexFileName(i),
dexOutputArrays.get(i));
}
if (!createJar(args.outName)) {
return 3;
}
} else if (args.outName != null) {
File outDir = new File(args.outName);
assert outDir.isDirectory();
for (int i = 0; i < dexOutputArrays.size(); i++) {
OutputStream out = new FileOutputStream(new File(outDir, getDexFileName(i)));
try {
out.write(dexOutputArrays.get(i));
} finally {
closeOutput(out);
}
}
}
return 0;
|
private static byte[] | writeDex()Converts {@link #outputDex} into a {@code byte[]} and do whatever
human-oriented dumping is required.
byte[] outArray = null;
try {
try {
if (args.methodToDump != null) {
/*
* Simply dump the requested method. Note: The call
* to toDex() is required just to get the underlying
* structures ready.
*/
outputDex.toDex(null, false);
dumpMethod(outputDex, args.methodToDump, humanOutWriter);
} else {
/*
* This is the usual case: Create an output .dex file,
* and write it, dump it, etc.
*/
outArray = outputDex.toDex(humanOutWriter, args.verboseDump);
}
if (args.statistics) {
DxConsole.out.println(outputDex.getStatistics().toHuman());
}
} finally {
if (humanOutWriter != null) {
humanOutWriter.flush();
}
}
} catch (Exception ex) {
if (args.debug) {
DxConsole.err.println("\ntrouble writing output:");
ex.printStackTrace(DxConsole.err);
} else {
DxConsole.err.println("\ntrouble writing output: " +
ex.getMessage());
}
return null;
}
return outArray;
|