Introduce --multi-dex option in dx.
Add --multi-dex options to dx command line to allow the generation of
several dex files when method index limit is about to be reached.
Also add
--main-dex-list allowing to force some classes in the main dex.
--minimal-main-dex to keep in main edx only classes specified by
main-dex-list.
--set-max-idx-number to set an arbitrary idx limit for the
splitting.
(cherry picked from commit c7daf656da3a4854296b6a8bb702e3ee418450e5)
Change-Id: I2b42272be91484a75783eb94cd30581159948975
diff --git a/dx/src/com/android/dx/cf/direct/ClassPathOpener.java b/dx/src/com/android/dx/cf/direct/ClassPathOpener.java
index e2e2cfb..6d33733 100644
--- a/dx/src/com/android/dx/cf/direct/ClassPathOpener.java
+++ b/dx/src/com/android/dx/cf/direct/ClassPathOpener.java
@@ -45,6 +45,7 @@
* package.
*/
private final boolean sort;
+ private FileNameFilter filter;
/**
* Callback interface for {@code ClassOpener}.
@@ -82,6 +83,25 @@
}
/**
+ * Filter interface for {@code ClassOpener}.
+ */
+ public interface FileNameFilter {
+
+ boolean accept(String path);
+ }
+
+ /**
+ * An accept all filter.
+ */
+ public static final FileNameFilter acceptAll = new FileNameFilter() {
+
+ @Override
+ public boolean accept(String path) {
+ return true;
+ }
+ };
+
+ /**
* Constructs an instance.
*
* @param pathname {@code non-null;} path element to process
@@ -91,9 +111,24 @@
* @param consumer {@code non-null;} callback interface
*/
public ClassPathOpener(String pathname, boolean sort, Consumer consumer) {
+ this(pathname, sort, acceptAll, consumer);
+ }
+
+ /**
+ * Constructs an instance.
+ *
+ * @param pathname {@code non-null;} path element to process
+ * @param sort if true, sort such that classes appear before their inner
+ * classes and "package-info" occurs before all other classes in that
+ * package.
+ * @param consumer {@code non-null;} callback interface
+ */
+ public ClassPathOpener(String pathname, boolean sort, FileNameFilter filter,
+ Consumer consumer) {
this.pathname = pathname;
this.sort = sort;
this.consumer = consumer;
+ this.filter = filter;
}
/**
@@ -129,9 +164,12 @@
path.endsWith(".apk")) {
return processArchive(file);
}
-
- byte[] bytes = FileUtils.readFile(file);
- return consumer.processFileBytes(path, file.lastModified(), bytes);
+ if (filter.accept(path)) {
+ byte[] bytes = FileUtils.readFile(file);
+ return consumer.processFileBytes(path, file.lastModified(), bytes);
+ } else {
+ return false;
+ }
} catch (Exception ex) {
consumer.onException(ex);
return false;
diff --git a/dx/src/com/android/dx/cf/direct/DirectClassFile.java b/dx/src/com/android/dx/cf/direct/DirectClassFile.java
index 2af2efe..f908547 100644
--- a/dx/src/com/android/dx/cf/direct/DirectClassFile.java
+++ b/dx/src/com/android/dx/cf/direct/DirectClassFile.java
@@ -229,6 +229,15 @@
}
/**
+ * Gets the path where this class file is located.
+ *
+ * @return {@code non-null;} the filePath
+ */
+ public String getFilePath() {
+ return filePath;
+ }
+
+ /**
* Gets the {@link ByteArray} that this instance's data comes from.
*
* @return {@code non-null;} the bytes
diff --git a/dx/src/com/android/dx/command/Main.java b/dx/src/com/android/dx/command/Main.java
index 6540e35..1132edb 100644
--- a/dx/src/com/android/dx/command/Main.java
+++ b/dx/src/com/android/dx/command/Main.java
@@ -34,12 +34,24 @@
" [--dump-method=<name>[*]] [--verbose-dump] [--no-files] " +
"[--core-library]\n" +
" [--num-threads=<n>] [--incremental] [--force-jumbo]\n" +
+ " [--multi-dex [--main-dex-list=<file> [--minimal-main-dex]]\n" +
" [<file>.class | <file>.{zip,jar,apk} | <directory>] ...\n" +
" Convert a set of classfiles into a dex file, optionally " +
"embedded in a\n" +
" jar/zip. Output name must end with one of: .dex .jar " +
- ".zip .apk. Positions\n" +
- " options: none, important, lines.\n" +
+ ".zip .apk or be a directory.\n" +
+ " Positions options: none, important, lines.\n" +
+ " --multi-dex: allows to generate several dex files if needed. " +
+ "This option is \n" +
+ " exclusive with --incremental, causes --num-threads to be ignored " +
+ "and only\n" +
+ " supports folder or archive output.\n" +
+ " --main-dex-list=<file>: <file> is a list of class file names, " +
+ "classes defined by\n" +
+ " those class files are put in classes.dex.\n" +
+ " --minimal-main-dex: only classes selected by --main-dex-list are " +
+ "to be put in\n" +
+ " the main dex.\n" +
" dx --annotool --annotation=<class> [--element=<element types>]\n" +
" [--print=<print types>]\n" +
" dx --dump [--debug] [--strict] [--bytes] [--optimize]\n" +
diff --git a/dx/src/com/android/dx/command/dexer/Main.java b/dx/src/com/android/dx/command/dexer/Main.java
index a9d5a32..d9f6ab0 100644
--- a/dx/src/com/android/dx/command/dexer/Main.java
+++ b/dx/src/com/android/dx/command/dexer/Main.java
@@ -17,11 +17,15 @@
package com.android.dx.command.dexer;
import com.android.dex.Dex;
+import com.android.dex.DexException;
import com.android.dex.DexFormat;
import com.android.dex.util.FileUtils;
import com.android.dx.Version;
import com.android.dx.cf.code.SimException;
import com.android.dx.cf.direct.ClassPathOpener;
+import com.android.dx.cf.direct.ClassPathOpener.FileNameFilter;
+import com.android.dx.cf.direct.DirectClassFile;
+import com.android.dx.cf.direct.StdAttributeFactory;
import com.android.dx.cf.iface.ParseException;
import com.android.dx.command.DxConsole;
import com.android.dx.command.UsageException;
@@ -30,6 +34,7 @@
import com.android.dx.dex.cf.CfTranslator;
import com.android.dx.dex.cf.CodeStatistics;
import com.android.dx.dex.code.PositionList;
+import com.android.dx.dex.file.AnnotationUtils;
import com.android.dx.dex.file.ClassDefItem;
import com.android.dx.dex.file.DexFile;
import com.android.dx.dex.file.EncodedMethod;
@@ -40,18 +45,24 @@
import com.android.dx.rop.annotation.AnnotationsList;
import com.android.dx.rop.cst.CstNat;
import com.android.dx.rop.cst.CstString;
+
+import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
+import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -66,6 +77,23 @@
*/
public class Main {
/**
+ * {@code non-null;} Error message for too many method/field/type ids.
+ */
+ public static final String TO_MANY_ID_ERROR_MESSAGE =
+ "Dex limit exceeded. You may try option " + Arguments.MULTI_DEX_OPTION;
+
+ /**
+ * File extension of a {@code .dex} file.
+ */
+ private static final String DEX_EXTENSION = ".dex";
+
+ /**
+ * File name prefix of a {@code .dex} file automatically loaded in an
+ * archive.
+ */
+ private static final String DEX_PREFIX = "classes";
+
+ /**
* {@code non-null;} the lengthy message that tries to discourage
* people from defining core classes in applications
*/
@@ -153,6 +181,12 @@
/** class files older than this must be defined in the target dex file. */
private static long minimumFileAge = 0;
+ private static Set<String> classesInMainDex = null;
+
+ private static List<byte[]> dexOutputArrays = new ArrayList<byte[]>();
+
+ private static OutputStreamWriter humanOutWriter = null;
+
/**
* This class is uninstantiable.
*/
@@ -189,6 +223,25 @@
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() throws IOException {
+
File incrementalOutFile = null;
if (args.incremental) {
if (args.outName == null) {
@@ -231,7 +284,10 @@
// Effectively free up the (often massive) DexFile memory.
outputDex = null;
- if (!createJar(args.outName, outArray)) {
+ if (outArray != null) {
+ outputResources.put(DexFormat.DEX_IN_JAR_NAME, outArray);
+ }
+ if (!createJar(args.outName)) {
return 3;
}
} else if (outArray != null && args.outName != null) {
@@ -243,6 +299,87 @@
return 0;
}
+ private static int runMultiDex() throws IOException {
+
+ assert !args.incremental;
+ assert args.numThreads == 1;
+
+ if (args.mainDexListFile != null) {
+ classesInMainDex = loadMainDexListFile(args.mainDexListFile);
+ }
+
+ 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 String getDexFileName(int i) {
+ if (i == 0) {
+ return DexFormat.DEX_IN_JAR_NAME;
+ } else {
+ return DEX_PREFIX + (i + 1) + DEX_EXTENSION;
+ }
+ }
+
+ private static Set<String> loadMainDexListFile(String mainDexListFile) throws IOException {
+ Set<String> mainDexList = new HashSet<String>();
+ BufferedReader bfr = null;
+ try {
+ FileReader fr = new FileReader(mainDexListFile);
+ bfr = new BufferedReader(fr);
+
+ String line;
+
+ while (null != (line = bfr.readLine())) {
+ mainDexList.add(fixPath(line));
+ }
+
+ } finally {
+ if (bfr != null) {
+ bfr.close();
+ }
+ }
+ return mainDexList;
+ }
+
/**
* Merges the dex files {@code update} and {@code base}, preferring
* {@code update}'s definition for types defined in both dex files.
@@ -307,16 +444,12 @@
* @return whether processing was successful
*/
private static boolean processAllFiles() {
- outputDex = new DexFile(args.dexOptions);
+ createDexFile();
if (args.jarOutput) {
outputResources = new TreeMap<String, byte[]>();
}
- if (args.dumpWidth != 0) {
- outputDex.setDumpWidth(args.dumpWidth);
- }
-
anyFilesProcessed = false;
String[] fileNames = args.fileNames;
@@ -325,9 +458,40 @@
}
try {
- for (int i = 0; i < fileNames.length; i++) {
- if (processOne(fileNames[i])) {
- anyFilesProcessed = true;
+ 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++) {
+ if (processOne(fileNames[i], mainPassFilter)) {
+ anyFilesProcessed = true;
+ }
+ }
+
+ if (dexOutputArrays.size() > 1) {
+ 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++) {
+ if (processOne(fileNames[i], new NotFilter(mainPassFilter))) {
+ anyFilesProcessed = true;
+ }
+ }
+ } else {
+ // without --main-dex-list
+ for (int i = 0; i < fileNames.length; i++) {
+ if (processOne(fileNames[i], ClassPathOpener.acceptAll)) {
+ anyFilesProcessed = true;
+ }
}
}
} catch (StopProcessing ex) {
@@ -368,18 +532,31 @@
return true;
}
+ private static void createDexFile() {
+ if (outputDex != null) {
+ dexOutputArrays.add(writeDex());
+ }
+
+ outputDex = new DexFile(args.dexOptions);
+
+ if (args.dumpWidth != 0) {
+ outputDex.setDumpWidth(args.dumpWidth);
+ }
+ }
+
/**
* Processes one pathname element.
*
* @param pathname {@code non-null;} the pathname to process. May
* be the path of a class file, a jar file, or a directory
* containing class files.
+ * @param filter {@code non-null;} A filter for excluding files.
* @return whether any processing actually happened
*/
- private static boolean processOne(String pathname) {
+ private static boolean processOne(String pathname, FileNameFilter filter) {
ClassPathOpener opener;
- opener = new ClassPathOpener(pathname, false,
+ opener = new ClassPathOpener(pathname, false, filter,
new ClassPathOpener.Consumer() {
public boolean processFileBytes(String name, long lastModified, byte[] bytes) {
if (args.numThreads > 1) {
@@ -439,6 +616,7 @@
String fixedName = fixPath(name);
if (isClass) {
+
if (keepResources && args.keepClassesInJar) {
synchronized (outputResources) {
outputResources.put(fixedName, bytes);
@@ -474,13 +652,34 @@
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 numTypeIds = outputDex.getTypeIds().items().size();
+ int constantPoolSize = cf.getConstantPool().size();
+
+ if (args.multiDex && ((numMethodIds + constantPoolSize > args.maxNumberOfIdxPerDex) ||
+ (numFieldIds + constantPoolSize > args.maxNumberOfIdxPerDex) ||
+ (numTypeIds + constantPoolSize
+ /* annotation added by dx are not counted in numTypeIds */
+ + AnnotationUtils.DALVIK_ANNOTATION_NUMBER
+ > args.maxNumberOfIdxPerDex))) {
+ createDexFile();
+ }
+
try {
ClassDefItem clazz =
- CfTranslator.translate(name, bytes, args.cfOptions, args.dexOptions);
+ 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) {
@@ -544,14 +743,7 @@
byte[] outArray = null;
try {
- OutputStream humanOutRaw = null;
- OutputStreamWriter humanOut = null;
try {
- if (args.humanOutName != null) {
- humanOutRaw = openOutput(args.humanOutName);
- humanOut = new OutputStreamWriter(humanOutRaw);
- }
-
if (args.methodToDump != null) {
/*
* Simply dump the requested method. Note: The call
@@ -559,23 +751,22 @@
* structures ready.
*/
outputDex.toDex(null, false);
- dumpMethod(outputDex, args.methodToDump, humanOut);
+ 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(humanOut, args.verboseDump);
+ outArray = outputDex.toDex(humanOutWriter, args.verboseDump);
}
if (args.statistics) {
DxConsole.out.println(outputDex.getStatistics().toHuman());
}
} finally {
- if (humanOut != null) {
- humanOut.flush();
+ if (humanOutWriter != null) {
+ humanOutWriter.flush();
}
- closeOutput(humanOutRaw);
}
} catch (Exception ex) {
if (args.debug) {
@@ -592,14 +783,12 @@
}
/**
- * Creates a jar file from the resources and given dex file array.
+ * Creates a jar file from the resources (including dex file arrays).
*
* @param fileName {@code non-null;} name of the file
- * @param dexArray array containing the dex file to include, or null if the
- * output contains no class defs.
* @return whether the creation was successful
*/
- private static boolean createJar(String fileName, byte[] dexArray) {
+ private static boolean createJar(String fileName) {
/*
* Make or modify the manifest (as appropriate), put the dex
* array into the resources map, and then process the entire
@@ -611,23 +800,19 @@
OutputStream out = openOutput(fileName);
JarOutputStream jarOut = new JarOutputStream(out, manifest);
- if (dexArray != null) {
- outputResources.put(DexFormat.DEX_IN_JAR_NAME, dexArray);
- }
-
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 " +
- contents.length + "...");
+ DxConsole.out.println("writing " + name + "; size " + length + "...");
}
- entry.setSize(contents.length);
+ entry.setSize(length);
jarOut.putNextEntry(entry);
jarOut.write(contents);
jarOut.closeEntry();
@@ -856,6 +1041,82 @@
pw.flush();
}
+ private static class NotFilter implements FileNameFilter {
+ private final FileNameFilter filter;
+
+ private NotFilter(FileNameFilter filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public boolean accept(String path) {
+ return !filter.accept(path);
+ }
+ }
+
+ /**
+ * A quick and accurate filter for when file path can be trusted.
+ */
+ private static class MainDexListFilter implements FileNameFilter {
+
+ @Override
+ public boolean accept(String fullPath) {
+ if (fullPath.endsWith(".class")) {
+ String path = fixPath(fullPath);
+ return classesInMainDex.contains(path);
+ } else {
+ return true;
+ }
+ }
+ }
+
+ /**
+ * A best effort conservative filter for when file path can <b>not</b> be trusted.
+ */
+ private static class BestEffortMainDexListFilter implements FileNameFilter {
+
+ Map<String, List<String>> map = new HashMap<String, List<String>>();
+
+ public BestEffortMainDexListFilter() {
+ for (String pathOfClass : classesInMainDex) {
+ String normalized = fixPath(pathOfClass);
+ String simple = getSimpleName(normalized);
+ List<String> fullPath = map.get(simple);
+ if (fullPath == null) {
+ fullPath = new ArrayList<String>(1);
+ map.put(simple, fullPath);
+ }
+ fullPath.add(normalized);
+ }
+ }
+
+ @Override
+ public boolean accept(String path) {
+ if (path.endsWith(".class")) {
+ String normalized = fixPath(path);
+ String simple = getSimpleName(normalized);
+ List<String> fullPaths = map.get(simple);
+ if (fullPaths != null) {
+ for (String fullPath : fullPaths) {
+ if (normalized.endsWith(fullPath)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private static String getSimpleName(String path) {
+ int index = path.lastIndexOf('/');
+ if (index >= 0) {
+ return path.substring(index + 1);
+ } else {
+ return path;
+ }
+ }
+ }
+
/**
* Exception class used to halt processing prematurely.
*/
@@ -867,6 +1128,17 @@
* Command-line argument parser and access.
*/
public static class Arguments {
+
+ private static final String MINIMAL_MAIN_DEX_OPTION = "--minimal-main-dex";
+
+ private static final String MAIN_DEX_LIST_OPTION = "--main-dex-list";
+
+ private static final String MULTI_DEX_OPTION = "--multi-dex";
+
+ private static final String NUM_THREADS_OPTION = "--num-threads";
+
+ private static final String INCREMENTAL_OPTION = "--incremental";
+
/** whether to run in debug mode */
public boolean debug = false;
@@ -952,6 +1224,19 @@
/** number of threads to run with */
public int numThreads = 1;
+ /** generation of multiple dex is allowed */
+ public boolean multiDex = false;
+
+ /** Optional file containing a list of class files containing classes to be forced in main
+ * dex */
+ public String mainDexListFile = null;
+
+ /** Produce the smallest possible main dex. Ignored unless multiDex is true and
+ * mainDexListFile is specified and non empty. */
+ public boolean minimalMainDex = false;
+
+ private int maxNumberOfIdxPerDex = DexFormat.MAX_MEMBER_IDX + 1;
+
private static class ArgumentsParser {
/** The arguments to process. */
@@ -1061,6 +1346,9 @@
public void parse(String[] args) {
ArgumentsParser parser = new ArgumentsParser(args);
+ boolean outputIsDirectory = false;
+ boolean outputIsDirectDex = false;
+
while(parser.getNext()) {
if (parser.isArg("--debug")) {
debug = true;
@@ -1098,11 +1386,15 @@
keepClassesInJar = true;
} else if (parser.isArg("--output=")) {
outName = parser.getLastValue();
- if (FileUtils.hasArchiveSuffix(outName)) {
+ if (new File(outName).isDirectory()) {
+ jarOutput = false;
+ outputIsDirectory = true;
+ } else if (FileUtils.hasArchiveSuffix(outName)) {
jarOutput = true;
} else if (outName.endsWith(".dex") ||
outName.equals("-")) {
jarOutput = false;
+ outputIsDirectDex = true;
} else {
System.err.println("unknown output extension: " +
outName);
@@ -1130,13 +1422,21 @@
}
} else if (parser.isArg("--no-locals")) {
localInfo = false;
- } else if (parser.isArg("--num-threads=")) {
+ } else if (parser.isArg(NUM_THREADS_OPTION + "=")) {
numThreads = Integer.parseInt(parser.getLastValue());
- } else if (parser.isArg("--incremental")) {
+ } else if (parser.isArg(INCREMENTAL_OPTION)) {
incremental = true;
} else if (parser.isArg("--force-jumbo")) {
forceJumbo = true;
- } else {
+ } else if (parser.isArg(MULTI_DEX_OPTION)) {
+ multiDex = true;
+ } else if (parser.isArg(MAIN_DEX_LIST_OPTION + "=")) {
+ mainDexListFile = parser.getLastValue();
+ } else if (parser.isArg(MINIMAL_MAIN_DEX_OPTION)) {
+ minimalMainDex = true;
+ } else if (parser.isArg("--set-max-idx-number=")) { // undocumented test option
+ maxNumberOfIdxPerDex = Integer.parseInt(parser.getLastValue());
+ } else {
System.err.println("unknown option: " + parser.getCurrent());
throw new UsageException();
}
@@ -1156,6 +1456,39 @@
humanOutName = "-";
}
+ if (mainDexListFile != null && !multiDex) {
+ System.err.println(MAIN_DEX_LIST_OPTION + " is only supported in combination with "
+ + MULTI_DEX_OPTION);
+ throw new UsageException();
+ }
+
+ if (minimalMainDex && (mainDexListFile == null || !multiDex)) {
+ System.err.println(MINIMAL_MAIN_DEX_OPTION + " is only supported in combination with "
+ + MULTI_DEX_OPTION + " and " + MAIN_DEX_LIST_OPTION);
+ throw new UsageException();
+ }
+
+ if (multiDex && numThreads != 1) {
+ System.out.println(NUM_THREADS_OPTION + "is ignored when used with "
+ + MULTI_DEX_OPTION);
+ }
+
+ if (multiDex && incremental) {
+ System.err.println(INCREMENTAL_OPTION + " is not supported with "
+ + MULTI_DEX_OPTION);
+ throw new UsageException();
+ }
+
+ if (multiDex && outputIsDirectDex) {
+ System.err.println("Unsupported output \"" + outName +"\". " + MULTI_DEX_OPTION +
+ " supports only archive or directory output");
+ throw new UsageException();
+ }
+
+ if (outputIsDirectory && !multiDex) {
+ outName = new File(outName, DexFormat.DEX_IN_JAR_NAME).getPath();
+ }
+
makeOptionsObjects();
}
diff --git a/dx/src/com/android/dx/dex/cf/CfTranslator.java b/dx/src/com/android/dx/dex/cf/CfTranslator.java
index d4cda2a..8ab1c84 100644
--- a/dx/src/com/android/dx/dex/cf/CfTranslator.java
+++ b/dx/src/com/android/dx/dex/cf/CfTranslator.java
@@ -20,7 +20,6 @@
import com.android.dx.cf.code.ConcreteMethod;
import com.android.dx.cf.code.Ropper;
import com.android.dx.cf.direct.DirectClassFile;
-import com.android.dx.cf.direct.StdAttributeFactory;
import com.android.dx.cf.iface.Field;
import com.android.dx.cf.iface.FieldList;
import com.android.dx.cf.iface.Method;
@@ -30,8 +29,12 @@
import com.android.dx.dex.code.PositionList;
import com.android.dx.dex.code.RopTranslator;
import com.android.dx.dex.file.ClassDefItem;
+import com.android.dx.dex.file.DexFile;
import com.android.dx.dex.file.EncodedField;
import com.android.dx.dex.file.EncodedMethod;
+import com.android.dx.dex.file.FieldIdsSection;
+import com.android.dx.dex.file.MethodIdsSection;
+import com.android.dx.dex.file.TypeIdsSection;
import com.android.dx.rop.annotation.Annotations;
import com.android.dx.rop.annotation.AnnotationsList;
import com.android.dx.rop.code.AccessFlags;
@@ -41,11 +44,15 @@
import com.android.dx.rop.code.RopMethod;
import com.android.dx.rop.code.TranslationAdvice;
import com.android.dx.rop.cst.Constant;
+import com.android.dx.rop.cst.ConstantPool;
+import com.android.dx.rop.cst.CstBaseMethodRef;
import com.android.dx.rop.cst.CstBoolean;
import com.android.dx.rop.cst.CstByte;
import com.android.dx.rop.cst.CstChar;
+import com.android.dx.rop.cst.CstEnumRef;
import com.android.dx.rop.cst.CstFieldRef;
import com.android.dx.rop.cst.CstInteger;
+import com.android.dx.rop.cst.CstInterfaceMethodRef;
import com.android.dx.rop.cst.CstMethodRef;
import com.android.dx.rop.cst.CstShort;
import com.android.dx.rop.cst.CstString;
@@ -81,12 +88,12 @@
* @param dexOptions options for dex output
* @return {@code non-null;} the translated class
*/
- public static ClassDefItem translate(String filePath, byte[] bytes,
- CfOptions cfOptions, DexOptions dexOptions) {
+ public static ClassDefItem translate(DirectClassFile cf, byte[] bytes,
+ CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) {
try {
- return translate0(filePath, bytes, cfOptions, dexOptions);
+ return translate0(cf, bytes, cfOptions, dexOptions, dexFile);
} catch (RuntimeException ex) {
- String msg = "...while processing " + filePath;
+ String msg = "...while processing " + cf.getFilePath();
throw ExceptionWithContext.withContext(ex, msg);
}
}
@@ -103,13 +110,8 @@
* @param dexOptions options for dex output
* @return {@code non-null;} the translated class
*/
- private static ClassDefItem translate0(String filePath, byte[] bytes,
- CfOptions cfOptions, DexOptions dexOptions) {
- DirectClassFile cf =
- new DirectClassFile(bytes, filePath, cfOptions.strictNameCheck);
-
- cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
- cf.getMagic();
+ private static ClassDefItem translate0(DirectClassFile cf, byte[] bytes,
+ CfOptions cfOptions, DexOptions dexOptions, DexFile dexFile) {
OptimizerOptions.loadOptimizeLists(cfOptions.optimizeListFile,
cfOptions.dontOptimizeListFile);
@@ -130,8 +132,32 @@
out.setClassAnnotations(classAnnotations);
}
- processFields(cf, out);
- processMethods(cf, cfOptions, dexOptions, out);
+ FieldIdsSection fieldIdsSection = dexFile.getFieldIds();
+ MethodIdsSection methodIdsSection = dexFile.getMethodIds();
+ TypeIdsSection typeIdsSection = dexFile.getTypeIds();
+ processFields(cf, out, fieldIdsSection);
+ processMethods(cf, cfOptions, dexOptions, out, methodIdsSection);
+
+ // intern constant pool method, field and type references
+ ConstantPool constantPool = cf.getConstantPool();
+ int constantPoolSize = constantPool.size();
+
+ synchronized (dexFile) {
+ for (int i = 0; i < constantPoolSize; i++) {
+ Constant constant = constantPool.getOrNull(i);
+ if (constant instanceof CstMethodRef) {
+ methodIdsSection.intern((CstBaseMethodRef) constant);
+ } else if (constant instanceof CstInterfaceMethodRef) {
+ methodIdsSection.intern(((CstInterfaceMethodRef) constant).toMethodRef());
+ } else if (constant instanceof CstFieldRef) {
+ fieldIdsSection.intern((CstFieldRef) constant);
+ } else if (constant instanceof CstEnumRef) {
+ fieldIdsSection.intern(((CstEnumRef) constant).getFieldRef());
+ } else if (constant instanceof CstType) {
+ typeIdsSection.intern((CstType) constant);
+ }
+ }
+ }
return out;
}
@@ -142,7 +168,8 @@
* @param cf {@code non-null;} class being translated
* @param out {@code non-null;} output class
*/
- private static void processFields(DirectClassFile cf, ClassDefItem out) {
+ private static void processFields(
+ DirectClassFile cf, ClassDefItem out, FieldIdsSection fieldIdsSection) {
CstType thisClass = cf.getThisClass();
FieldList fields = cf.getFields();
int sz = fields.size();
@@ -170,6 +197,9 @@
if (annotations.size() != 0) {
out.addFieldAnnotations(field, annotations);
}
+ synchronized (fieldIdsSection) {
+ fieldIdsSection.intern(field);
+ }
} catch (RuntimeException ex) {
String msg = "...while processing " + one.getName().toHuman() +
" " + one.getDescriptor().toHuman();
@@ -222,7 +252,7 @@
* @param out {@code non-null;} output class
*/
private static void processMethods(DirectClassFile cf, CfOptions cfOptions,
- DexOptions dexOptions, ClassDefItem out) {
+ DexOptions dexOptions, ClassDefItem out, MethodIdsSection methodIds) {
CstType thisClass = cf.getThisClass();
MethodList methods = cf.getMethods();
int sz = methods.size();
@@ -338,6 +368,9 @@
if (list.size() != 0) {
out.addParameterAnnotations(meth, list);
}
+ synchronized (methodIds) {
+ methodIds.intern(meth);
+ }
} catch (RuntimeException ex) {
String msg = "...while processing " + one.getName().toHuman() +
" " + one.getDescriptor().toHuman();
diff --git a/dx/src/com/android/dx/dex/file/AnnotationUtils.java b/dx/src/com/android/dx/dex/file/AnnotationUtils.java
index 935c250..f7d4492 100644
--- a/dx/src/com/android/dx/dex/file/AnnotationUtils.java
+++ b/dx/src/com/android/dx/dex/file/AnnotationUtils.java
@@ -35,6 +35,13 @@
* Utility class for dealing with annotations.
*/
public final class AnnotationUtils {
+
+ /**
+ * Number of annotation types that dx may add in the dex file that were
+ * not defined in the translated class file.
+ */
+ public static final int DALVIK_ANNOTATION_NUMBER = 7;
+
/** {@code non-null;} type for {@code AnnotationDefault} annotations */
private static final CstType ANNOTATION_DEFAULT_TYPE =
CstType.intern(Type.intern("Ldalvik/annotation/AnnotationDefault;"));
diff --git a/dx/src/com/android/dx/dex/file/DexFile.java b/dx/src/com/android/dx/dex/file/DexFile.java
index 0d5b110..01a5e4b 100644
--- a/dx/src/com/android/dx/dex/file/DexFile.java
+++ b/dx/src/com/android/dx/dex/file/DexFile.java
@@ -18,7 +18,7 @@
import com.android.dex.util.ExceptionWithContext;
import com.android.dx.dex.DexOptions;
-import static com.android.dx.dex.file.MixedItemSection.SortType;
+import com.android.dx.dex.file.MixedItemSection.SortType;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstBaseMethodRef;
import com.android.dx.rop.cst.CstEnumRef;
@@ -27,6 +27,7 @@
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.Type;
import com.android.dx.util.ByteArrayAnnotatedOutput;
+
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
@@ -241,7 +242,7 @@
* @return {@code >= 0;} the total file size
* @throws RuntimeException thrown if the file size is not yet known
*/
- /*package*/ int getFileSize() {
+ public int getFileSize() {
if (fileSize < 0) {
throw new RuntimeException("file size not yet known");
}
@@ -342,13 +343,13 @@
/**
* Gets the type identifiers section.
*
- * <p>This is package-scope in order to allow
+ * <p>This is public in order to allow
* the various {@link Item} instances to add items to the
- * instance.</p>
+ * instance and help early counting of type ids.</p>
*
* @return {@code non-null;} the class identifiers section
*/
- /*package*/ TypeIdsSection getTypeIds() {
+ public TypeIdsSection getTypeIds() {
return typeIds;
}
@@ -368,26 +369,26 @@
/**
* Gets the field identifiers section.
*
- * <p>This is package-scope in order to allow
+ * <p>This is public in order to allow
* the various {@link Item} instances to add items to the
- * instance.</p>
+ * instance and help early counting of field ids.</p>
*
* @return {@code non-null;} the field identifiers section
*/
- /*package*/ FieldIdsSection getFieldIds() {
+ public FieldIdsSection getFieldIds() {
return fieldIds;
}
/**
* Gets the method identifiers section.
*
- * <p>This is package-scope in order to allow
+ * <p>This is public in order to allow
* the various {@link Item} instances to add items to the
- * instance.</p>
+ * instance and help early counting of method ids.</p>
*
* @return {@code non-null;} the method identifiers section
*/
- /*package*/ MethodIdsSection getMethodIds() {
+ public MethodIdsSection getMethodIds() {
return methodIds;
}
diff --git a/dx/src/com/android/dx/dex/file/MemberIdsSection.java b/dx/src/com/android/dx/dex/file/MemberIdsSection.java
index 9f82f41..6c6eb49 100644
--- a/dx/src/com/android/dx/dex/file/MemberIdsSection.java
+++ b/dx/src/com/android/dx/dex/file/MemberIdsSection.java
@@ -17,17 +17,13 @@
package com.android.dx.dex.file;
import com.android.dex.DexException;
-import java.util.Formatter;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.atomic.AtomicInteger;
+import com.android.dex.DexFormat;
+import com.android.dx.command.dexer.Main;
/**
* Member (field or method) refs list section of a {@code .dex} file.
*/
public abstract class MemberIdsSection extends UniformItemSection {
- /** The largest addressable member is 0xffff, in the dex spec as field@CCCC or meth@CCCC. */
- private static final int MAX_MEMBERS = 0x10000;
/**
* Constructs an instance. The file offset is initially unknown.
@@ -45,8 +41,8 @@
protected void orderItems() {
int idx = 0;
- if (items().size() > MAX_MEMBERS) {
- throw new DexException(tooManyMembersMessage());
+ if (items().size() > DexFormat.MAX_MEMBER_IDX + 1) {
+ throw new DexException(Main.TO_MANY_ID_ERROR_MESSAGE);
}
for (Object i : items()) {
@@ -54,26 +50,4 @@
idx++;
}
}
-
- private String tooManyMembersMessage() {
- Map<String, AtomicInteger> membersByPackage = new TreeMap<String, AtomicInteger>();
- for (Object member : items()) {
- String packageName = ((MemberIdItem) member).getDefiningClass().getPackageName();
- AtomicInteger count = membersByPackage.get(packageName);
- if (count == null) {
- count = new AtomicInteger();
- membersByPackage.put(packageName, count);
- }
- count.incrementAndGet();
- }
-
- Formatter formatter = new Formatter();
- String memberType = this instanceof MethodIdsSection ? "methods" : "fields";
- formatter.format("Too many %s: %d; max is %d. By package:",
- memberType, items().size(), MAX_MEMBERS);
- for (Map.Entry<String, AtomicInteger> entry : membersByPackage.entrySet()) {
- formatter.format("%n%6d %s", entry.getValue().get(), entry.getKey());
- }
- return formatter.toString();
- }
}
diff --git a/dx/src/com/android/dx/dex/file/TypeIdsSection.java b/dx/src/com/android/dx/dex/file/TypeIdsSection.java
index 1df4b46..1603f7c 100644
--- a/dx/src/com/android/dx/dex/file/TypeIdsSection.java
+++ b/dx/src/com/android/dx/dex/file/TypeIdsSection.java
@@ -16,11 +16,15 @@
package com.android.dx.dex.file;
+import com.android.dex.DexException;
+import com.android.dex.DexFormat;
+import com.android.dx.command.dexer.Main;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.cst.CstType;
import com.android.dx.rop.type.Type;
import com.android.dx.util.AnnotatedOutput;
import com.android.dx.util.Hex;
+
import java.util.Collection;
import java.util.TreeMap;
@@ -80,8 +84,8 @@
int sz = typeIds.size();
int offset = (sz == 0) ? 0 : getFileOffset();
- if (sz > 65536) {
- throw new UnsupportedOperationException("too many type ids");
+ if (sz > DexFormat.MAX_TYPE_IDX + 1) {
+ throw new DexException(Main.TO_MANY_ID_ERROR_MESSAGE);
}
if (out.annotates()) {