1893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel/*
2893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * Copyright (C) 2014 The Android Open Source Project
3893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel *
4893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * Licensed under the Apache License, Version 2.0 (the "License");
5893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * you may not use this file except in compliance with the License.
6893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * You may obtain a copy of the License at
7893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel *
8893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel *      http://www.apache.org/licenses/LICENSE-2.0
9893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel *
10893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * Unless required by applicable law or agreed to in writing, software
11893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * distributed under the License is distributed on an "AS IS" BASIS,
12893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * See the License for the specific language governing permissions and
14893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * limitations under the License.
15893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel */
16893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
17893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselpackage com.android.multidex;
18893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
19893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations;
20893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport com.android.dx.cf.direct.DirectClassFile;
21893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport com.android.dx.cf.iface.Attribute;
22893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport com.android.dx.cf.iface.FieldList;
23893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport com.android.dx.cf.iface.HasAttribute;
24893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport com.android.dx.cf.iface.MethodList;
25893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport java.io.FileNotFoundException;
26893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport java.io.IOException;
27893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport java.util.HashSet;
28893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport java.util.Set;
29893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselimport java.util.zip.ZipFile;
30893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
31893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel/**
32893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * This is a command line tool used by mainDexClasses script to build a main dex classes list. First
33893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * argument of the command line is an archive, each class file contained in this archive is used to
34893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * identify a class that can be used during secondary dex installation, those class files
35893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * are not opened by this tool only their names matter. Other arguments must be zip files or
36893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * directories, they constitute in a classpath in with the classes named by the first argument
37893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * will be searched. Each searched class must be found. On each of this classes are searched for
38893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * their dependencies to other classes. The tool also browses for classes annotated by runtime
39893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * visible annotations and adds them to the list/ Finally the tools prints on standard output a list
40893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel * of class files names suitable as content of the file argument --main-dex-list of dx.
41893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel */
42893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Rousselpublic class MainDexListBuilder {
43893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    private static final String CLASS_EXTENSION = ".class";
44893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
45893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    private static final int STATUS_ERROR = 1;
46893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
47893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    private static final String EOL = System.getProperty("line.separator");
48893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
499dbd802c8c96c3a66873bc600bc7d1374a1d08e5Orion Hodson    private static final String USAGE_MESSAGE =
50893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            "Usage:" + EOL + EOL +
51893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            "Short version: Don't use this." + EOL + EOL +
52893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            "Slightly longer version: This tool is used by mainDexClasses script to build" + EOL +
53893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            "the main dex list." + EOL;
54893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
55a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel    /**
56a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel     * By default we force all classes annotated with runtime annotation to be kept in the
57a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel     * main dex list. This option disable the workaround, limiting the index pressure in the main
58a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel     * dex but exposing to the Dalvik resolution bug. The resolution bug occurs when accessing
59a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel     * annotations of a class that is not in the main dex and one of the annotations as an enum
60a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel     * parameter.
61a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel     *
62a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel     * @see <a href="https://code.google.com/p/android/issues/detail?id=78144">bug discussion</a>
63a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel     *
64a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel     */
65a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel    private static final String DISABLE_ANNOTATION_RESOLUTION_WORKAROUND =
66a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel            "--disable-annotation-resolution-workaround";
67a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel
68893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    private Set<String> filesToKeep = new HashSet<String>();
69893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
70893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    public static void main(String[] args) {
71893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
72a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel        int argIndex = 0;
73a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel        boolean keepAnnotated = true;
74a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel        while (argIndex < args.length -2) {
75a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel            if (args[argIndex].equals(DISABLE_ANNOTATION_RESOLUTION_WORKAROUND)) {
76a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel                keepAnnotated = false;
77a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel            } else {
78a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel                System.err.println("Invalid option " + args[argIndex]);
79a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel                printUsage();
80a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel                System.exit(STATUS_ERROR);
81a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel            }
82a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel            argIndex++;
83a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel        }
84a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel        if (args.length - argIndex != 2) {
85893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            printUsage();
86893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            System.exit(STATUS_ERROR);
87893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        }
88893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
89893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        try {
90a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel            MainDexListBuilder builder = new MainDexListBuilder(keepAnnotated, args[argIndex],
91a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel                    args[argIndex + 1]);
92893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            Set<String> toKeep = builder.getMainDexList();
93893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            printList(toKeep);
94893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        } catch (IOException e) {
95893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            System.err.println("A fatal error occured: " + e.getMessage());
96893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            System.exit(STATUS_ERROR);
97893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            return;
98893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        }
99893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    }
100893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
101a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel    public MainDexListBuilder(boolean keepAnnotated, String rootJar, String pathString)
102a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel            throws IOException {
103893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        ZipFile jarOfRoots = null;
104893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        Path path = null;
105893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        try {
106893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            try {
107893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                jarOfRoots = new ZipFile(rootJar);
108893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            } catch (IOException e) {
109893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                throw new IOException("\"" + rootJar + "\" can not be read as a zip archive. ("
110893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                        + e.getMessage() + ")", e);
111893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            }
112893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            path = new Path(pathString);
113893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
114893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            ClassReferenceListBuilder mainListBuilder = new ClassReferenceListBuilder(path);
115893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            mainListBuilder.addRoots(jarOfRoots);
116893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            for (String className : mainListBuilder.getClassNames()) {
117893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                filesToKeep.add(className + CLASS_EXTENSION);
118893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            }
119a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel            if (keepAnnotated) {
120a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel                keepAnnotated(path);
121a4a5e989c6baa8d8cac17483044f2076bf55ec80Yohann Roussel            }
122893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        } finally {
123893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            try {
124893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                jarOfRoots.close();
125893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            } catch (IOException e) {
126893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                // ignore
127893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            }
128893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            if (path != null) {
129893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                for (ClassPathElement element : path.elements) {
130893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                    try {
131893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                        element.close();
132893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                    } catch (IOException e) {
133893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                        // keep going, lets do our best.
134893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                    }
135893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                }
136893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            }
137893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        }
138893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    }
139893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
140893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    /**
141893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel     * Returns a list of classes to keep. This can be passed to dx as a file with --main-dex-list.
142893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel     */
143893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    public Set<String> getMainDexList() {
144893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        return filesToKeep;
145893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    }
146893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
147893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    private static void printUsage() {
148893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        System.err.print(USAGE_MESSAGE);
149893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    }
150893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
151893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    private static void printList(Set<String> fileNames) {
152893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        for (String fileName : fileNames) {
153893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            System.out.println(fileName);
154893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        }
155893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    }
156893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
157893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    /**
158893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel     * Keep classes annotated with runtime annotations.
159893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel     */
160893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    private void keepAnnotated(Path path) throws FileNotFoundException {
161893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        for (ClassPathElement element : path.getElements()) {
162893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel            forClazz:
163893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                for (String name : element.list()) {
164893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                    if (name.endsWith(CLASS_EXTENSION)) {
165893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                        DirectClassFile clazz = path.getClass(name);
166893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                        if (hasRuntimeVisibleAnnotation(clazz)) {
167893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                            filesToKeep.add(name);
168893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                        } else {
169893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                            MethodList methods = clazz.getMethods();
170893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                            for (int i = 0; i<methods.size(); i++) {
171893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                                if (hasRuntimeVisibleAnnotation(methods.get(i))) {
172893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                                    filesToKeep.add(name);
173893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                                    continue forClazz;
174893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                                }
175893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                            }
176893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                            FieldList fields = clazz.getFields();
177893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                            for (int i = 0; i<fields.size(); i++) {
178893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                                if (hasRuntimeVisibleAnnotation(fields.get(i))) {
179893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                                    filesToKeep.add(name);
180893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                                    continue forClazz;
181893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                                }
182893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                            }
183893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                        }
184893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                    }
185893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                }
186893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        }
187893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    }
188893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel
189893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    private boolean hasRuntimeVisibleAnnotation(HasAttribute element) {
190893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        Attribute att = element.getAttributes().findFirst(
191893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel                AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
192893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel        return (att != null && ((AttRuntimeVisibleAnnotations)att).getAnnotations().size()>0);
193893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel    }
194893795fc95fdd77d398ebb77a0fe336c45b596cfYohann Roussel}
195