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 
main(String[] args)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 
MainDexListBuilder(String rootJar, String pathString)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      */
getMainDexList()116     public Set<String> getMainDexList() {
117         return filesToKeep;
118     }
119 
printUsage()120     private static void printUsage() {
121         System.err.print(USAGE_MESSAGE);
122     }
123 
printList(Set<String> fileNames)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      */
keepAnnotated(Path path)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 
hasRuntimeVisibleAnnotation(HasAttribute element)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