Home | History | Annotate | Download | only in multidex
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.multidex;
     18 
     19 import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations;
     20 import com.android.dx.cf.direct.DirectClassFile;
     21 import com.android.dx.cf.iface.Attribute;
     22 import com.android.dx.cf.iface.FieldList;
     23 import com.android.dx.cf.iface.HasAttribute;
     24 import com.android.dx.cf.iface.MethodList;
     25 import java.io.FileNotFoundException;
     26 import java.io.IOException;
     27 import java.util.HashSet;
     28 import java.util.Set;
     29 import java.util.zip.ZipFile;
     30 
     31 /**
     32  * This is a command line tool used by mainDexClasses script to build a main dex classes list. First
     33  * argument of the command line is an archive, each class file contained in this archive is used to
     34  * identify a class that can be used during secondary dex installation, those class files
     35  * are not opened by this tool only their names matter. Other arguments must be zip files or
     36  * directories, they constitute in a classpath in with the classes named by the first argument
     37  * will be searched. Each searched class must be found. On each of this classes are searched for
     38  * their dependencies to other classes. The tool also browses for classes annotated by runtime
     39  * visible annotations and adds them to the list/ Finally the tools prints on standard output a list
     40  * of class files names suitable as content of the file argument --main-dex-list of dx.
     41  */
     42 public class MainDexListBuilder {
     43     private static final String CLASS_EXTENSION = ".class";
     44 
     45     private static final int STATUS_ERROR = 1;
     46 
     47     private static final String EOL = System.getProperty("line.separator");
     48 
     49     private static final String USAGE_MESSAGE =
     50             "Usage:" + EOL + EOL +
     51             "Short version: Don't use this." + EOL + EOL +
     52             "Slightly longer version: This tool is used by mainDexClasses script to build" + EOL +
     53             "the main dex list." + EOL;
     54 
     55     /**
     56      * By default we force all classes annotated with runtime annotation to be kept in the
     57      * main dex list. This option disable the workaround, limiting the index pressure in the main
     58      * dex but exposing to the Dalvik resolution bug. The resolution bug occurs when accessing
     59      * annotations of a class that is not in the main dex and one of the annotations as an enum
     60      * parameter.
     61      *
     62      * @see <a href="https://code.google.com/p/android/issues/detail?id=78144">bug discussion</a>
     63      *
     64      */
     65     private static final String DISABLE_ANNOTATION_RESOLUTION_WORKAROUND =
     66             "--disable-annotation-resolution-workaround";
     67 
     68     private Set<String> filesToKeep = new HashSet<String>();
     69 
     70     public static void main(String[] args) {
     71 
     72         int argIndex = 0;
     73         boolean keepAnnotated = true;
     74         while (argIndex < args.length -2) {
     75             if (args[argIndex].equals(DISABLE_ANNOTATION_RESOLUTION_WORKAROUND)) {
     76                 keepAnnotated = false;
     77             } else {
     78                 System.err.println("Invalid option " + args[argIndex]);
     79                 printUsage();
     80                 System.exit(STATUS_ERROR);
     81             }
     82             argIndex++;
     83         }
     84         if (args.length - argIndex != 2) {
     85             printUsage();
     86             System.exit(STATUS_ERROR);
     87         }
     88 
     89         try {
     90             MainDexListBuilder builder = new MainDexListBuilder(keepAnnotated, args[argIndex],
     91                     args[argIndex + 1]);
     92             Set<String> toKeep = builder.getMainDexList();
     93             printList(toKeep);
     94         } catch (IOException e) {
     95             System.err.println("A fatal error occured: " + e.getMessage());
     96             System.exit(STATUS_ERROR);
     97             return;
     98         }
     99     }
    100 
    101     public MainDexListBuilder(boolean keepAnnotated, String rootJar, String pathString)
    102             throws IOException {
    103         ZipFile jarOfRoots = null;
    104         Path path = null;
    105         try {
    106             try {
    107                 jarOfRoots = new ZipFile(rootJar);
    108             } catch (IOException e) {
    109                 throw new IOException("\"" + rootJar + "\" can not be read as a zip archive. ("
    110                         + e.getMessage() + ")", e);
    111             }
    112             path = new Path(pathString);
    113 
    114             ClassReferenceListBuilder mainListBuilder = new ClassReferenceListBuilder(path);
    115             mainListBuilder.addRoots(jarOfRoots);
    116             for (String className : mainListBuilder.getClassNames()) {
    117                 filesToKeep.add(className + CLASS_EXTENSION);
    118             }
    119             if (keepAnnotated) {
    120                 keepAnnotated(path);
    121             }
    122         } finally {
    123             try {
    124                 jarOfRoots.close();
    125             } catch (IOException e) {
    126                 // ignore
    127             }
    128             if (path != null) {
    129                 for (ClassPathElement element : path.elements) {
    130                     try {
    131                         element.close();
    132                     } catch (IOException e) {
    133                         // keep going, lets do our best.
    134                     }
    135                 }
    136             }
    137         }
    138     }
    139 
    140     /**
    141      * Returns a list of classes to keep. This can be passed to dx as a file with --main-dex-list.
    142      */
    143     public Set<String> getMainDexList() {
    144         return filesToKeep;
    145     }
    146 
    147     private static void printUsage() {
    148         System.err.print(USAGE_MESSAGE);
    149     }
    150 
    151     private static void printList(Set<String> fileNames) {
    152         for (String fileName : fileNames) {
    153             System.out.println(fileName);
    154         }
    155     }
    156 
    157     /**
    158      * Keep classes annotated with runtime annotations.
    159      */
    160     private void keepAnnotated(Path path) throws FileNotFoundException {
    161         for (ClassPathElement element : path.getElements()) {
    162             forClazz:
    163                 for (String name : element.list()) {
    164                     if (name.endsWith(CLASS_EXTENSION)) {
    165                         DirectClassFile clazz = path.getClass(name);
    166                         if (hasRuntimeVisibleAnnotation(clazz)) {
    167                             filesToKeep.add(name);
    168                         } else {
    169                             MethodList methods = clazz.getMethods();
    170                             for (int i = 0; i<methods.size(); i++) {
    171                                 if (hasRuntimeVisibleAnnotation(methods.get(i))) {
    172                                     filesToKeep.add(name);
    173                                     continue forClazz;
    174                                 }
    175                             }
    176                             FieldList fields = clazz.getFields();
    177                             for (int i = 0; i<fields.size(); i++) {
    178                                 if (hasRuntimeVisibleAnnotation(fields.get(i))) {
    179                                     filesToKeep.add(name);
    180                                     continue forClazz;
    181                                 }
    182                             }
    183                         }
    184                     }
    185                 }
    186         }
    187     }
    188 
    189     private boolean hasRuntimeVisibleAnnotation(HasAttribute element) {
    190         Attribute att = element.getAttributes().findFirst(
    191                 AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
    192         return (att != null && ((AttRuntimeVisibleAnnotations)att).getAnnotations().size()>0);
    193     }
    194 }
    195