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