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