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 /** 57 * By default we force all classes annotated with runtime annotation to be kept in the 58 * main dex list. This option disable the workaround, limiting the index pressure in the main 59 * dex but exposing to the Dalvik resolution bug. The resolution bug occurs when accessing 60 * annotations of a class that is not in the main dex and one of the annotations as an enum 61 * parameter. 62 * 63 * @see <a href="https://code.google.com/p/android/issues/detail?id=78144">bug discussion</a> 64 * 65 */ 66 private static final String DISABLE_ANNOTATION_RESOLUTION_WORKAROUND = 67 "--disable-annotation-resolution-workaround"; 68 69 private Set<String> filesToKeep = new HashSet<String>(); 70 main(String[] args)71 public static void main(String[] args) { 72 73 int argIndex = 0; 74 boolean keepAnnotated = true; 75 while (argIndex < args.length -2) { 76 if (args[argIndex].equals(DISABLE_ANNOTATION_RESOLUTION_WORKAROUND)) { 77 keepAnnotated = false; 78 } else { 79 System.err.println("Invalid option " + args[argIndex]); 80 printUsage(); 81 System.exit(STATUS_ERROR); 82 } 83 argIndex++; 84 } 85 if (args.length - argIndex != 2) { 86 printUsage(); 87 System.exit(STATUS_ERROR); 88 } 89 90 try { 91 MainDexListBuilder builder = new MainDexListBuilder(keepAnnotated, args[argIndex], 92 args[argIndex + 1]); 93 Set<String> toKeep = builder.getMainDexList(); 94 printList(toKeep); 95 } catch (IOException e) { 96 System.err.println("A fatal error occured: " + e.getMessage()); 97 System.exit(STATUS_ERROR); 98 return; 99 } 100 } 101 MainDexListBuilder(boolean keepAnnotated, String rootJar, String pathString)102 public MainDexListBuilder(boolean keepAnnotated, String rootJar, String pathString) 103 throws IOException { 104 ZipFile jarOfRoots = null; 105 Path path = null; 106 try { 107 try { 108 jarOfRoots = new ZipFile(rootJar); 109 } catch (IOException e) { 110 throw new IOException("\"" + rootJar + "\" can not be read as a zip archive. (" 111 + e.getMessage() + ")", e); 112 } 113 path = new Path(pathString); 114 115 ClassReferenceListBuilder mainListBuilder = new ClassReferenceListBuilder(path); 116 mainListBuilder.addRoots(jarOfRoots); 117 for (String className : mainListBuilder.getClassNames()) { 118 filesToKeep.add(className + CLASS_EXTENSION); 119 } 120 if (keepAnnotated) { 121 keepAnnotated(path); 122 } 123 } finally { 124 try { 125 jarOfRoots.close(); 126 } catch (IOException e) { 127 // ignore 128 } 129 if (path != null) { 130 for (ClassPathElement element : path.elements) { 131 try { 132 element.close(); 133 } catch (IOException e) { 134 // keep going, lets do our best. 135 } 136 } 137 } 138 } 139 } 140 141 /** 142 * Returns a list of classes to keep. This can be passed to dx as a file with --main-dex-list. 143 */ getMainDexList()144 public Set<String> getMainDexList() { 145 return filesToKeep; 146 } 147 printUsage()148 private static void printUsage() { 149 System.err.print(USAGE_MESSAGE); 150 } 151 printList(Set<String> fileNames)152 private static void printList(Set<String> fileNames) { 153 for (String fileName : fileNames) { 154 System.out.println(fileName); 155 } 156 } 157 158 /** 159 * Keep classes annotated with runtime annotations. 160 */ keepAnnotated(Path path)161 private void keepAnnotated(Path path) throws FileNotFoundException { 162 for (ClassPathElement element : path.getElements()) { 163 forClazz: 164 for (String name : element.list()) { 165 if (name.endsWith(CLASS_EXTENSION)) { 166 DirectClassFile clazz = path.getClass(name); 167 if (hasRuntimeVisibleAnnotation(clazz)) { 168 filesToKeep.add(name); 169 } else { 170 MethodList methods = clazz.getMethods(); 171 for (int i = 0; i<methods.size(); i++) { 172 if (hasRuntimeVisibleAnnotation(methods.get(i))) { 173 filesToKeep.add(name); 174 continue forClazz; 175 } 176 } 177 FieldList fields = clazz.getFields(); 178 for (int i = 0; i<fields.size(); i++) { 179 if (hasRuntimeVisibleAnnotation(fields.get(i))) { 180 filesToKeep.add(name); 181 continue forClazz; 182 } 183 } 184 } 185 } 186 } 187 } 188 } 189 hasRuntimeVisibleAnnotation(HasAttribute element)190 private boolean hasRuntimeVisibleAnnotation(HasAttribute element) { 191 Attribute att = element.getAttributes().findFirst( 192 AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME); 193 return (att != null && ((AttRuntimeVisibleAnnotations)att).getAnnotations().size()>0); 194 } 195 } 196