blob: e0cd34cbbd05d352e67109f29db68461bc7e01c7 [file] [log] [blame] [edit]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.multidex;
import com.android.dx.cf.attrib.AttRuntimeVisibleAnnotations;
import com.android.dx.cf.direct.DirectClassFile;
import com.android.dx.cf.iface.Attribute;
import com.android.dx.cf.iface.FieldList;
import com.android.dx.cf.iface.HasAttribute;
import com.android.dx.cf.iface.MethodList;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.ZipFile;
/**
* This is a command line tool used by mainDexClasses script to build a main dex classes list. First
* argument of the command line is an archive, each class file contained in this archive is used to
* identify a class that can be used during secondary dex installation, those class files
* are not opened by this tool only their names matter. Other arguments must be zip files or
* directories, they constitute in a classpath in with the classes named by the first argument
* will be searched. Each searched class must be found. On each of this classes are searched for
* their dependencies to other classes. The tool also browses for classes annotated by runtime
* visible annotations and adds them to the list/ Finally the tools prints on standard output a list
* of class files names suitable as content of the file argument --main-dex-list of dx.
*/
public class MainDexListBuilder {
private static final String CLASS_EXTENSION = ".class";
private static final int STATUS_ERROR = 1;
private static final String EOL = System.getProperty("line.separator");
private static final String USAGE_MESSAGE =
"Usage:" + EOL + EOL +
"Short version: Don't use this." + EOL + EOL +
"Slightly longer version: This tool is used by mainDexClasses script to build" + EOL +
"the main dex list." + EOL;
/**
* By default we force all classes annotated with runtime annotation to be kept in the
* main dex list. This option disable the workaround, limiting the index pressure in the main
* dex but exposing to the Dalvik resolution bug. The resolution bug occurs when accessing
* annotations of a class that is not in the main dex and one of the annotations as an enum
* parameter.
*
* @see <a href="https://code.google.com/p/android/issues/detail?id=78144">bug discussion</a>
*
*/
private static final String DISABLE_ANNOTATION_RESOLUTION_WORKAROUND =
"--disable-annotation-resolution-workaround";
private Set<String> filesToKeep = new HashSet<String>();
public static void main(String[] args) {
int argIndex = 0;
boolean keepAnnotated = true;
while (argIndex < args.length -2) {
if (args[argIndex].equals(DISABLE_ANNOTATION_RESOLUTION_WORKAROUND)) {
keepAnnotated = false;
} else {
System.err.println("Invalid option " + args[argIndex]);
printUsage();
System.exit(STATUS_ERROR);
}
argIndex++;
}
if (args.length - argIndex != 2) {
printUsage();
System.exit(STATUS_ERROR);
}
try {
MainDexListBuilder builder = new MainDexListBuilder(keepAnnotated, args[argIndex],
args[argIndex + 1]);
Set<String> toKeep = builder.getMainDexList();
printList(toKeep);
} catch (IOException e) {
System.err.println("A fatal error occured: " + e.getMessage());
System.exit(STATUS_ERROR);
return;
}
}
public MainDexListBuilder(boolean keepAnnotated, String rootJar, String pathString)
throws IOException {
ZipFile jarOfRoots = null;
Path path = null;
try {
try {
jarOfRoots = new ZipFile(rootJar);
} catch (IOException e) {
throw new IOException("\"" + rootJar + "\" can not be read as a zip archive. ("
+ e.getMessage() + ")", e);
}
path = new Path(pathString);
ClassReferenceListBuilder mainListBuilder = new ClassReferenceListBuilder(path);
mainListBuilder.addRoots(jarOfRoots);
for (String className : mainListBuilder.getClassNames()) {
filesToKeep.add(className + CLASS_EXTENSION);
}
if (keepAnnotated) {
keepAnnotated(path);
}
} finally {
try {
jarOfRoots.close();
} catch (IOException e) {
// ignore
}
if (path != null) {
for (ClassPathElement element : path.elements) {
try {
element.close();
} catch (IOException e) {
// keep going, lets do our best.
}
}
}
}
}
/**
* Returns a list of classes to keep. This can be passed to dx as a file with --main-dex-list.
*/
public Set<String> getMainDexList() {
return filesToKeep;
}
private static void printUsage() {
System.err.print(USAGE_MESSAGE);
}
private static void printList(Set<String> fileNames) {
for (String fileName : fileNames) {
System.out.println(fileName);
}
}
/**
* Keep classes annotated with runtime annotations.
*/
private void keepAnnotated(Path path) throws FileNotFoundException {
for (ClassPathElement element : path.getElements()) {
forClazz:
for (String name : element.list()) {
if (name.endsWith(CLASS_EXTENSION)) {
DirectClassFile clazz = path.getClass(name);
if (hasRuntimeVisibleAnnotation(clazz)) {
filesToKeep.add(name);
} else {
MethodList methods = clazz.getMethods();
for (int i = 0; i<methods.size(); i++) {
if (hasRuntimeVisibleAnnotation(methods.get(i))) {
filesToKeep.add(name);
continue forClazz;
}
}
FieldList fields = clazz.getFields();
for (int i = 0; i<fields.size(); i++) {
if (hasRuntimeVisibleAnnotation(fields.get(i))) {
filesToKeep.add(name);
continue forClazz;
}
}
}
}
}
}
}
private boolean hasRuntimeVisibleAnnotation(HasAttribute element) {
Attribute att = element.getAttributes().findFirst(
AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME);
return (att != null && ((AttRuntimeVisibleAnnotations)att).getAnnotations().size()>0);
}
}