OSDN Git Service

Introduce --multi-dex option in dx.
authorBenoit Lamarche <benoitlamarche@google.com>
Wed, 31 Jul 2013 10:15:10 +0000 (12:15 +0200)
committerBrian Carlstrom <bdc@google.com>
Wed, 28 Aug 2013 23:36:02 +0000 (16:36 -0700)
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

dx/src/com/android/dx/cf/direct/ClassPathOpener.java
dx/src/com/android/dx/cf/direct/DirectClassFile.java
dx/src/com/android/dx/command/Main.java
dx/src/com/android/dx/command/dexer/Main.java
dx/src/com/android/dx/dex/cf/CfTranslator.java
dx/src/com/android/dx/dex/file/AnnotationUtils.java
dx/src/com/android/dx/dex/file/DexFile.java
dx/src/com/android/dx/dex/file/MemberIdsSection.java
dx/src/com/android/dx/dex/file/TypeIdsSection.java

index e2e2cfb..6d33733 100644 (file)
@@ -45,6 +45,7 @@ public class ClassPathOpener {
      * package.
      */
     private final boolean sort;
+    private FileNameFilter filter;
 
     /**
      * Callback interface for {@code ClassOpener}.
@@ -82,6 +83,25 @@ public class ClassPathOpener {
     }
 
     /**
+     * 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 @@ public class ClassPathOpener {
      * @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 @@ public class ClassPathOpener {
                     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;
index 2af2efe..f908547 100644 (file)
@@ -229,6 +229,15 @@ public class DirectClassFile implements ClassFile {
     }
 
     /**
+     * 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
index 6540e35..1132edb 100644 (file)
@@ -34,12 +34,24 @@ public class Main {
         "  [--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" +
index a9d5a32..d9f6ab0 100644 (file)
 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.CfOptions;
 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.Annotations;
 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 @@ import java.util.jar.Manifest;
  */
 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 @@ public class Main {
     /** 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 @@ public class Main {
         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 @@ public class Main {
             // 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 @@ public class Main {
         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 @@ public class Main {
      * @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 @@ public class Main {
         }
 
         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 @@ public class Main {
         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 @@ public class Main {
         String fixedName = fixPath(name);
 
         if (isClass) {
+
             if (keepResources && args.keepClassesInJar) {
                 synchronized (outputResources) {
                     outputResources.put(fixedName, bytes);
@@ -474,13 +652,34 @@ public class Main {
             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 @@ public class Main {
         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 @@ public class Main {
                      * 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 @@ public class Main {
     }
 
     /**
-     * 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 @@ public class Main {
             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 @@ public class Main {
         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 @@ public class Main {
      * 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 @@ public class Main {
         /** 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 class Main {
         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 @@ public class Main {
                     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 @@ public class Main {
                     }
                 } 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 @@ public class Main {
                 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();
         }
 
index d4cda2a..8ab1c84 100644 (file)
@@ -20,7 +20,6 @@ import com.android.dex.util.ExceptionWithContext;
 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.DalvCode;
 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.LocalVariableInfo;
 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 @@ public class CfTranslator {
      * @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 @@ public class CfTranslator {
      * @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 @@ public class CfTranslator {
             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 @@ public class CfTranslator {
      * @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 @@ public class CfTranslator {
                 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 @@ public class CfTranslator {
      * @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 @@ public class CfTranslator {
                 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();
index 935c250..f7d4492 100644 (file)
@@ -35,6 +35,13 @@ import java.util.ArrayList;
  * 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;"));
index 0d5b110..01a5e4b 100644 (file)
@@ -18,7 +18,7 @@ package com.android.dx.dex.file;
 
 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.CstString;
 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 @@ public final class DexFile {
      * @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 @@ public final class DexFile {
     /**
      * 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 @@ public final class DexFile {
     /**
      * 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;
     }
 
index 9f82f41..6c6eb49 100644 (file)
 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 @@ public abstract class MemberIdsSection extends UniformItemSection {
     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 @@ public abstract class MemberIdsSection extends UniformItemSection {
             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();
-    }
 }
index 1df4b46..1603f7c 100644 (file)
 
 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 @@ public final class TypeIdsSection extends UniformItemSection {
         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()) {