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