diff options
Diffstat (limited to 'src/tools/android/java/com')
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java | 260 | ||||
-rw-r--r-- | src/tools/android/java/com/google/devtools/build/android/desugar/IndexedInputs.java | 67 |
2 files changed, 174 insertions, 153 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java index 22780ea790..1711f57a99 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java @@ -26,6 +26,7 @@ import com.google.devtools.build.android.Converters.PathConverter; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsParser; +import com.google.errorprone.annotations.MustBeClosed; import java.io.IOException; import java.io.InputStream; import java.nio.file.FileVisitResult; @@ -193,124 +194,137 @@ class Desugar { LambdaClassMaker lambdas = new LambdaClassMaker(dumpDirectory); - // Process each input separately - for (InputOutputPair inputOutputPair : toInputOutputPairs(options)) { - Path inputPath = inputOutputPair.getInput(); - Path outputPath = inputOutputPair.getOutput(); - checkState( - Files.isDirectory(inputPath) || !Files.isDirectory(outputPath), - "Input jar file requires an output jar file"); - - try (Closer closer = Closer.create(); - OutputFileProvider outputFileProvider = toOutputFileProvider(outputPath)) { - InputFileProvider appInputFiles = toInputFileProvider(closer, inputPath); - List<InputFileProvider> classpathInputFiles = - toInputFileProvider(closer, options.classpath); - IndexedInputs appIndexedInputs = new IndexedInputs(ImmutableList.of(appInputFiles)); - IndexedInputs appAndClasspathIndexedInputs = - new IndexedInputs(classpathInputFiles, appIndexedInputs); - ClassLoader loader = - createClassLoader( - rewriter, - toInputFileProvider(closer, options.bootclasspath), - appAndClasspathIndexedInputs); - - ClassReaderFactory readerFactory = - new ClassReaderFactory( - (options.copyBridgesFromClasspath && !allowDefaultMethods) - ? appAndClasspathIndexedInputs - : appIndexedInputs, - rewriter); - - ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder(); - - // Process inputs, desugaring as we go - for (String filename : appInputFiles) { - try (InputStream content = appInputFiles.getInputStream(filename)) { - // We can write classes uncompressed since they need to be converted to .dex format - // for Android anyways. Resources are written as they were in the input jar to avoid - // any danger of accidentally uncompressed resources ending up in an .apk. - if (filename.endsWith(".class")) { - ClassReader reader = rewriter.reader(content); - CoreLibraryRewriter.UnprefixingClassWriter writer = - rewriter.writer(ClassWriter.COMPUTE_MAXS /*for bridge methods*/); - ClassVisitor visitor = writer; - - if (!options.onlyDesugarJavac9ForLint) { - if (!allowDefaultMethods) { - visitor = new Java7Compatibility(visitor, readerFactory); + try (Closer closer = Closer.create()) { + IndexedInputs indexedClasspath = + new IndexedInputs(toRegisteredInputFileProvider(closer, options.classpath)); + // Use a classloader that as much as possible uses the provided bootclasspath instead of + // the tool's system classloader. Unfortunately we can't do that for java. classes. + ClassLoader bootclassloader = + options.bootclasspath.isEmpty() + ? new ThrowingClassLoader() + : new HeaderClassLoader( + new IndexedInputs(toRegisteredInputFileProvider(closer, options.bootclasspath)), + rewriter, + new ThrowingClassLoader()); + + // Process each input separately + for (InputOutputPair inputOutputPair : toInputOutputPairs(options)) { + Path inputPath = inputOutputPair.getInput(); + Path outputPath = inputOutputPair.getOutput(); + checkState( + Files.isDirectory(inputPath) || !Files.isDirectory(outputPath), + "Input jar file requires an output jar file"); + + try (OutputFileProvider outputFileProvider = toOutputFileProvider(outputPath); + InputFileProvider inputFiles = toInputFileProvider(inputPath)) { + IndexedInputs indexedInputFiles = new IndexedInputs(ImmutableList.of(inputFiles)); + // Prepend classpath with input file itself so LambdaDesugaring can load classes with + // lambdas. + IndexedInputs indexedClasspathAndInputFiles = + indexedClasspath.withParent(indexedInputFiles); + // Note that input file and classpath need to be in the same classloader because + // we typically get the header Jar for inputJar on the classpath and having the header + // Jar in a parent loader means the header version is preferred over the real thing. + ClassLoader loader = + new HeaderClassLoader(indexedClasspathAndInputFiles, rewriter, bootclassloader); + + ClassReaderFactory readerFactory = + new ClassReaderFactory( + (options.copyBridgesFromClasspath && !allowDefaultMethods) + ? indexedClasspathAndInputFiles + : indexedInputFiles, + rewriter); + + ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder(); + + // Process inputs, desugaring as we go + for (String filename : inputFiles) { + try (InputStream content = inputFiles.getInputStream(filename)) { + // We can write classes uncompressed since they need to be converted to .dex format + // for Android anyways. Resources are written as they were in the input jar to avoid + // any danger of accidentally uncompressed resources ending up in an .apk. + if (filename.endsWith(".class")) { + ClassReader reader = rewriter.reader(content); + CoreLibraryRewriter.UnprefixingClassWriter writer = + rewriter.writer(ClassWriter.COMPUTE_MAXS /*for bridge methods*/); + ClassVisitor visitor = writer; + + if (!options.onlyDesugarJavac9ForLint) { + if (!allowDefaultMethods) { + visitor = new Java7Compatibility(visitor, readerFactory); + } + + visitor = + new LambdaDesugaring( + visitor, + loader, + lambdas, + interfaceLambdaMethodCollector, + allowDefaultMethods); + } + + if (!allowCallsToObjectsNonNull) { + visitor = new ObjectsRequireNonNullMethodInliner(visitor); } + reader.accept(visitor, 0); + + outputFileProvider.write(filename, writer.toByteArray()); + } else { + outputFileProvider.copyFrom(filename, inputFiles); + } + } + } - visitor = - new LambdaDesugaring( - visitor, - loader, - lambdas, - interfaceLambdaMethodCollector, - allowDefaultMethods); + ImmutableSet<String> interfaceLambdaMethods = interfaceLambdaMethodCollector.build(); + checkState( + !allowDefaultMethods || interfaceLambdaMethods.isEmpty(), + "Desugaring with default methods enabled moved interface lambdas"); + + // Write out the lambda classes we generated along the way + ImmutableMap<Path, LambdaInfo> lambdaClasses = lambdas.drain(); + checkState( + !options.onlyDesugarJavac9ForLint || lambdaClasses.isEmpty(), + "There should be no lambda classes generated: %s", + lambdaClasses.keySet()); + + for (Map.Entry<Path, LambdaInfo> lambdaClass : lambdaClasses.entrySet()) { + try (InputStream bytecode = + Files.newInputStream(dumpDirectory.resolve(lambdaClass.getKey()))) { + ClassReader reader = rewriter.reader(bytecode); + CoreLibraryRewriter.UnprefixingClassWriter writer = + rewriter.writer(ClassWriter.COMPUTE_MAXS /*for invoking bridges*/); + ClassVisitor visitor = writer; + + if (!allowDefaultMethods) { + // null ClassReaderFactory b/c we don't expect to need it for lambda classes + visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null); } + visitor = + new LambdaClassFixer( + visitor, + lambdaClass.getValue(), + readerFactory, + interfaceLambdaMethods, + allowDefaultMethods); + // Send lambda classes through desugaring to make sure there's no invokedynamic + // instructions in generated lambda classes (checkState below will fail) + visitor = new LambdaDesugaring(visitor, loader, lambdas, null, allowDefaultMethods); if (!allowCallsToObjectsNonNull) { + // Not sure whether there will be implicit null check emitted by javac, so we rerun + // the inliner again visitor = new ObjectsRequireNonNullMethodInliner(visitor); } reader.accept(visitor, 0); - + String filename = + rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class"; outputFileProvider.write(filename, writer.toByteArray()); - } else { - outputFileProvider.copyFrom(filename, appInputFiles); } } - } - - ImmutableSet<String> interfaceLambdaMethods = interfaceLambdaMethodCollector.build(); - checkState( - !allowDefaultMethods || interfaceLambdaMethods.isEmpty(), - "Desugaring with default methods enabled moved interface lambdas"); - - // Write out the lambda classes we generated along the way - ImmutableMap<Path, LambdaInfo> lambdaClasses = lambdas.drain(); - checkState( - !options.onlyDesugarJavac9ForLint || lambdaClasses.isEmpty(), - "There should be no lambda classes generated: %s", - lambdaClasses.keySet()); - - for (Map.Entry<Path, LambdaInfo> lambdaClass : lambdaClasses.entrySet()) { - try (InputStream bytecode = - Files.newInputStream(dumpDirectory.resolve(lambdaClass.getKey()))) { - ClassReader reader = rewriter.reader(bytecode); - CoreLibraryRewriter.UnprefixingClassWriter writer = - rewriter.writer(ClassWriter.COMPUTE_MAXS /*for invoking bridges*/); - ClassVisitor visitor = writer; - - if (!allowDefaultMethods) { - // null ClassReaderFactory b/c we don't expect to need it for lambda classes - visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null); - } - visitor = - new LambdaClassFixer( - visitor, - lambdaClass.getValue(), - readerFactory, - interfaceLambdaMethods, - allowDefaultMethods); - // Send lambda classes through desugaring to make sure there's no invokedynamic - // instructions in generated lambda classes (checkState below will fail) - visitor = new LambdaDesugaring(visitor, loader, lambdas, null, allowDefaultMethods); - if (!allowCallsToObjectsNonNull) { - // Not sure whether there will be implicit null check emitted by javac, so we rerun - // the inliner again - visitor = new ObjectsRequireNonNullMethodInliner(visitor); - } - reader.accept(visitor, 0); - String filename = - rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class"; - outputFileProvider.write(filename, writer.toByteArray()); - } + Map<Path, LambdaInfo> leftBehind = lambdas.drain(); + checkState(leftBehind.isEmpty(), "Didn't process %s", leftBehind); } - - Map<Path, LambdaInfo> leftBehind = lambdas.drain(); - checkState(leftBehind.isEmpty(), "Didn't process %s", leftBehind); } } } @@ -325,24 +339,6 @@ class Desugar { return ioPairListbuilder.build(); } - private static ClassLoader createClassLoader( - CoreLibraryRewriter rewriter, - List<InputFileProvider> bootclasspath, - IndexedInputs appAndClasspathIndexedInputs) - throws IOException { - // Use a classloader that as much as possible uses the provided bootclasspath instead of - // the tool's system classloader. Unfortunately we can't do that for java. classes. - ClassLoader parent = new ThrowingClassLoader(); - if (!bootclasspath.isEmpty()) { - parent = new HeaderClassLoader(new IndexedInputs(bootclasspath), rewriter, parent); - } - // Prepend classpath with input jar itself so LambdaDesugaring can load classes with lambdas. - // Note that inputJar and classpath need to be in the same classloader because we typically get - // the header Jar for inputJar on the classpath and having the header Jar in a parent loader - // means the header version is preferred over the real thing. - return new HeaderClassLoader(appAndClasspathIndexedInputs, rewriter, parent); - } - private static class ThrowingClassLoader extends ClassLoader { @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { @@ -394,6 +390,7 @@ class Desugar { } /** Transform a Path to an {@link OutputFileProvider} */ + @MustBeClosed private static OutputFileProvider toOutputFileProvider(Path path) throws IOException { if (Files.isDirectory(path)) { @@ -403,17 +400,22 @@ class Desugar { } } - /** Transform a Path to an InputFileProvider and register it to close it at the end of desugar */ - private static InputFileProvider toInputFileProvider(Closer closer, Path path) + /** Transform a Path to an InputFileProvider that needs to be closed by the caller. */ + @MustBeClosed + private static InputFileProvider toInputFileProvider(Path path) throws IOException { if (Files.isDirectory(path)) { - return closer.register(new DirectoryInputFileProvider(path)); + return new DirectoryInputFileProvider(path); } else { - return closer.register(new ZipInputFileProvider(path)); + return new ZipInputFileProvider(path); } } - private static ImmutableList<InputFileProvider> toInputFileProvider( + /** + * Transform a list of Path to a list of ZipInputFileProvider and register them with the given + * closer. + */ + private static ImmutableList<InputFileProvider> toRegisteredInputFileProvider( Closer closer, List<Path> paths) throws IOException { ImmutableList.Builder<InputFileProvider> builder = new ImmutableList.Builder<>(); for (Path path : paths) { @@ -422,7 +424,7 @@ class Desugar { } return builder.build(); } - + /** * Pair input and output. */ diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/IndexedInputs.java b/src/tools/android/java/com/google/devtools/build/android/desugar/IndexedInputs.java index 58459ccbe5..33c6132020 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/IndexedInputs.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/IndexedInputs.java @@ -13,11 +13,14 @@ // limitations under the License. package com.google.devtools.build.android.desugar; -import com.google.common.base.Preconditions; -import java.io.IOException; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.ImmutableMap; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.CheckReturnValue; import javax.annotation.Nullable; /** @@ -27,36 +30,47 @@ import javax.annotation.Nullable; */ class IndexedInputs { - private final Map<String, InputFileProvider> inputFiles = new HashMap<>(); + private final ImmutableMap<String, InputFileProvider> inputFiles; - /** Parent indexed inputs to use before to search a file name into this indexed inputs. */ + /** + * Parent {@link IndexedInputs} to use before to search a file name into this {@link + * IndexedInputs}. + */ @Nullable - private final IndexedInputs parentIndexedInputs; + private final IndexedInputs parent; - /** Index a list of input files without a parent indexed inputs. */ - public IndexedInputs(List<InputFileProvider> inputProviders) throws IOException { - this(inputProviders, null); + /** Index a list of input files without a parent {@link IndexedInputs}. */ + public IndexedInputs(List<InputFileProvider> inputProviders) { + this.parent = null; + this.inputFiles = indexInputs(inputProviders); } /** - * Index a list of input files and set a parent indexed inputs that is firstly used during the - * search of a file name. + * Create a new {@link IndexedInputs} with input files previously indexed and with a parent {@link + * IndexedInputs}. */ - public IndexedInputs( - List<InputFileProvider> inputProviders, @Nullable IndexedInputs parentIndexedInputs) - throws IOException { - this.parentIndexedInputs = parentIndexedInputs; - for (InputFileProvider inputProvider : inputProviders) { - indexInput(inputProvider); - } + private IndexedInputs( + ImmutableMap<String, InputFileProvider> inputFiles, IndexedInputs parentIndexedInputs) { + this.parent = parentIndexedInputs; + this.inputFiles = inputFiles; + } + + /** + * Create a new {@link IndexedInputs} with input files already indexed and with a parent {@link + * IndexedInputs}. + */ + @CheckReturnValue + public IndexedInputs withParent(IndexedInputs parent) { + checkState(this.parent == null); + return new IndexedInputs(this.inputFiles, parent); } @Nullable public InputFileProvider getInputFileProvider(String filename) { - Preconditions.checkArgument(filename.endsWith(".class")); + checkArgument(filename.endsWith(".class")); - if (parentIndexedInputs != null) { - InputFileProvider inputFileProvider = parentIndexedInputs.getInputFileProvider(filename); + if (parent != null) { + InputFileProvider inputFileProvider = parent.getInputFileProvider(filename); if (inputFileProvider != null) { return inputFileProvider; } @@ -65,11 +79,16 @@ class IndexedInputs { return inputFiles.get(filename); } - private void indexInput(final InputFileProvider inputFileProvider) throws IOException { - for (String relativePath : inputFileProvider) { - if (relativePath.endsWith(".class") && !inputFiles.containsKey(relativePath)) { - inputFiles.put(relativePath, inputFileProvider); + private ImmutableMap<String, InputFileProvider> indexInputs( + List<InputFileProvider> inputProviders) { + Map<String, InputFileProvider> indexedInputs = new HashMap<>(); + for (InputFileProvider inputProvider : inputProviders) { + for (String relativePath : inputProvider) { + if (relativePath.endsWith(".class") && !indexedInputs.containsKey(relativePath)) { + indexedInputs.put(relativePath, inputProvider); + } } } + return ImmutableMap.copyOf(indexedInputs); } } |