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 
     26 import java.io.FileNotFoundException;
     27 import java.io.IOException;
     28 import java.util.HashSet;
     29 import java.util.Set;
     30 import java.util.zip.ZipFile;
     31 
     32 /**
     33  * This is a command line tool used by mainDexClasses script to build a main dex classes list. First
     34  * argument of the command line is an archive, each class file contained in this archive is used to
     35  * identify a class that can be used during secondary dex installation, those class files
     36  * are not opened by this tool only their names matter. Other arguments must be zip files or
     37  * directories, they constitute in a classpath in with the classes named by the first argument
     38  * will be searched. Each searched class must be found. On each of this classes are searched for
     39  * their dependencies to other classes. The tool also browses for classes annotated by runtime
     40  * visible annotations and adds them to the list/ Finally the tools prints on standard output a list
     41  * of class files names suitable as content of the file argument --main-dex-list of dx.
     42  */
     43 public class MainDexListBuilder {
     44     private static final String CLASS_EXTENSION = ".class";
     45 
     46     private static final int STATUS_ERROR = 1;
     47 
     48     private static final String EOL = System.getProperty("line.separator");
     49 
     50     private static String USAGE_MESSAGE =
     51             "Usage:" + EOL + EOL +
     52             "Short version: Don't use this." + EOL + EOL +
     53             "Slightly longer version: This tool is used by mainDexClasses script to build" + EOL +
     54             "the main dex list." + EOL;
     55 
     56     private Set<String> filesToKeep = new HashSet<String>();
     57 
     58     public static void main(String[] args) {
     59 
     60         if (args.length != 2) {
     61             printUsage();
     62             System.exit(STATUS_ERROR);
     63         }
     64 
     65         try {
     66 
     67             MainDexListBuilder builder = new MainDexListBuilder(args[0], args[1]);
     68             Set<String> toKeep = builder.getMainDexList();
     69             printList(toKeep);
     70         } catch (IOException e) {
     71             System.err.println("A fatal error occured: " + e.getMessage());
     72             System.exit(STATUS_ERROR);
     73             return;
     74         }
     75     }
     76 
     77     public MainDexListBuilder(String rootJar, String pathString) throws IOException {
     78         ZipFile jarOfRoots = null;
     79         Path path = null;
     80         try {
     81             try {
     82                 jarOfRoots = new ZipFile(rootJar);
     83             } catch (IOException e) {
     84                 throw new IOException("\"" + rootJar + "\" can not be read as a zip archive. ("
     85                         + e.getMessage() + ")", e);
     86             }
     87             path = new Path(pathString);
     88 
     89             ClassReferenceListBuilder mainListBuilder = new ClassReferenceListBuilder(path);
     90             mainListBuilder.addRoots(jarOfRoots);
     91             for (String className : mainListBuilder.getClassNames()) {
     92                 filesToKeep.add(className + CLASS_EXTENSION);
     93             }
     94             keepAnnotated(path);
     95         } finally {
     96             try {
     97                 jarOfRoots.close();
     98             } catch (IOException e) {
     99                 // ignore
    100             }
    101             if (path != null) {
    102                 for (ClassPathElement element : path.elements) {
    103                     try {
    104                         element.close();
    105                     } catch (IOException e) {
    106                         // keep going, lets do our best.
    107                     }
    108                 }
    109             }
    110         }
    111     }
    112 
    113     /**
    114      * Returns a list of classes to keep. This can be passed to dx as a file with --main-dex-list.
    115      */
    116     public Set<String> getMainDexList() {
    117         return filesToKeep;
    118     }
    119 
    120     private static void printUsage() {
    121         System.err.print(USAGE_MESSAGE);
    122     }
    123 
    124     private static void printList(Set<String> fileNames) {
    125         for (String fileName : fileNames) {
    126             System.out.println(fileName);
    127         }
    128     }
    129 
    130     /**
    131      * Keep classes annotated with runtime annotations.
    132      */
    133     private void keepAnnotated(Path path) throws FileNotFoundException {
    134         for (ClassPathElement element : path.getElements()) {
    135             forClazz:
    136                 for (String name : element.list()) {
    137                     if (name.endsWith(CLASS_EXTENSION)) {
    138                         DirectClassFile clazz = path.getClass(name);
    139                         if (hasRuntimeVisibleAnnotation(clazz)) {
    140                             filesToKeep.add(name);
    141                         } else {
    142                             MethodList methods = clazz.getMethods();
    143                             for (int i = 0; i<methods.size(); i++) {
    144                                 if (hasRuntimeVisibleAnnotation(methods.get(i))) {
    145                                     filesToKeep.add(name);
    146                                     continue forClazz;
    147                                 }
    148                             }
    149                             FieldList fields = clazz.getFields();
    150                             for (int i = 0; i<fields.size(); i++) {
    151                                 if (hasRuntimeVisibleAnnotation(fields.get(i))) {
    152                                     filesToKeep.add(name);
    153                                     continue forClazz;
    154                                 }
    155                             }
    156                         }
    157                     }
    158                 }
    159         }
    160     }
    161 
    162     private boolean hasRuntimeVisibleAnnotation(HasAttribute element) {
    163         Attribute att = element.getAttributes().findFirst(
    164                 AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
    165         return (att != null && ((AttRuntimeVisibleAnnotations)att).getAnnotations().size()>0);
    166     }
    167 }
    168