diff options
author | Colin Cross <ccross@android.com> | 2017-03-16 18:52:23 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-03-16 18:52:23 +0000 |
commit | 20dfe722ed256b520e85e6265971410f0af87a87 (patch) | |
tree | 0c0263f40a2f6a94f5558e455a8e40630841a1ae | |
parent | 698a5c8ed016f4001bbba6c8bf71b61d42d0b719 (diff) | |
parent | a81405c6ddb7334c1a0feb0e7e10447279f713e8 (diff) | |
download | desugar-20dfe722ed256b520e85e6265971410f0af87a87.tar.gz |
Merge remote-tracking branch 'aosp/upstream-master' into master
am: a81405c6dd
Change-Id: Icdb30ac508d36dadcbfff35f75f1e29ab5d88c3d
3 files changed, 144 insertions, 101 deletions
diff --git a/java/com/google/devtools/build/android/desugar/Desugar.java b/java/com/google/devtools/build/android/desugar/Desugar.java index 877d230..6f9e0f0 100644 --- a/java/com/google/devtools/build/android/desugar/Desugar.java +++ b/java/com/google/devtools/build/android/desugar/Desugar.java @@ -16,6 +16,7 @@ package com.google.devtools.build.android.desugar; import static com.google.common.base.Preconditions.checkState; import static java.nio.charset.StandardCharsets.ISO_8859_1; +import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.io.ByteStreams; @@ -34,6 +35,7 @@ import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Enumeration; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.zip.CRC32; @@ -54,13 +56,16 @@ class Desugar { public static class Options extends OptionsBase { @Option( name = "input", - defaultValue = "null", + allowMultiple = true, + defaultValue = "", category = "input", converter = ExistingPathConverter.class, abbrev = 'i', - help = "Input Jar with classes to desugar (required)." + help = + "Input Jar with classes to desugar (required, the n-th input is paired with the n-th " + + "output)." ) - public Path inputJar; + public List<Path> inputJars; @Option( name = "classpath_entry", @@ -92,13 +97,16 @@ class Desugar { @Option( name = "output", - defaultValue = "null", + allowMultiple = true, + defaultValue = "", category = "output", converter = PathConverter.class, abbrev = 'o', - help = "Output Jar to write desugared classes into (required)." + help = + "Output Jar to write desugared classes into (required, the n-th output is paired with " + + "the n-th input)." ) - public Path outputJar; + public List<Path> outputJars; @Option( name = "verbose", @@ -157,10 +165,12 @@ class Desugar { optionsParser.parseAndExitUponError(args); Options options = optionsParser.getOptions(Options.class); - checkState(options.inputJar != null, "--input is required"); - checkState(options.outputJar != null, "--output is required"); + checkState(!options.inputJars.isEmpty(), "--input is required"); + checkState( + options.inputJars.size() == options.outputJars.size(), + "Desugar requires the same number of inputs and outputs to pair them"); checkState(!options.bootclasspath.isEmpty() || options.allowEmptyBootclasspath, - "Bootclasspath required to desugar %s", options.inputJar); + "At least one --bootclasspath_entry is required"); if (options.verbose) { System.out.printf("Lambda classes will be written under %s%n", dumpDirectory); @@ -169,109 +179,127 @@ class Desugar { CoreLibraryRewriter rewriter = new CoreLibraryRewriter(options.coreLibrary ? "__desugar__/" : ""); - IndexedJars appIndexedJar = new IndexedJars(ImmutableList.of(options.inputJar)); - IndexedJars appAndClasspathIndexedJars = new IndexedJars(options.classpath, appIndexedJar); - ClassLoader loader = - createClassLoader(rewriter, options.bootclasspath, appAndClasspathIndexedJars); boolean allowDefaultMethods = options.minSdkVersion >= 24; boolean allowCallsToObjectsNonNull = options.minSdkVersion >= 19; - try (ZipFile in = new ZipFile(options.inputJar.toFile()); - ZipOutputStream out = - new ZipOutputStream( - new BufferedOutputStream(Files.newOutputStream(options.outputJar)))) { - LambdaClassMaker lambdas = new LambdaClassMaker(dumpDirectory); - ClassReaderFactory readerFactory = - new ClassReaderFactory( - (options.copyBridgesFromClasspath && !allowDefaultMethods) - ? appAndClasspathIndexedJars - : appIndexedJar, - rewriter); - - ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder(); - - // Process input Jar, desugaring as we go - for (Enumeration<? extends ZipEntry> entries = in.entries(); entries.hasMoreElements(); ) { - ZipEntry entry = entries.nextElement(); - try (InputStream content = in.getInputStream(entry)) { - // 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 (entry.getName().endsWith(".class")) { - ClassReader reader = rewriter.reader(content); + + LambdaClassMaker lambdas = new LambdaClassMaker(dumpDirectory); + + // Process each input separately + for (InputOutputPair inputOutputPair : toInputOutputPairs(options)) { + Path inputJar = inputOutputPair.getInput(); + IndexedJars appIndexedJar = new IndexedJars(ImmutableList.of(inputJar)); + IndexedJars appAndClasspathIndexedJars = new IndexedJars(options.classpath, appIndexedJar); + ClassLoader loader = + createClassLoader(rewriter, options.bootclasspath, appAndClasspathIndexedJars); + + try (ZipFile in = new ZipFile(inputJar.toFile()); + ZipOutputStream out = + new ZipOutputStream( + new BufferedOutputStream(Files.newOutputStream(inputOutputPair.getOutput())))) { + ClassReaderFactory readerFactory = + new ClassReaderFactory( + (options.copyBridgesFromClasspath && !allowDefaultMethods) + ? appAndClasspathIndexedJars + : appIndexedJar, + rewriter); + + ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder(); + + // Process input Jar, desugaring as we go + for (Enumeration<? extends ZipEntry> entries = in.entries(); entries.hasMoreElements(); ) { + ZipEntry entry = entries.nextElement(); + try (InputStream content = in.getInputStream(entry)) { + // 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 (entry.getName().endsWith(".class")) { + ClassReader reader = rewriter.reader(content); + CoreLibraryRewriter.UnprefixingClassWriter writer = + rewriter.writer(ClassWriter.COMPUTE_MAXS /*for bridge methods*/); + ClassVisitor visitor = writer; + 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); + + writeStoredEntry(out, entry.getName(), writer.toByteArray()); + } else { + // TODO(bazel-team): Avoid de- and re-compressing resource files + ZipEntry destEntry = new ZipEntry(entry); + destEntry.setCompressedSize(-1); + out.putNextEntry(destEntry); + ByteStreams.copy(content, out); + out.closeEntry(); + } + } + } + + ImmutableSet<String> interfaceLambdaMethods = interfaceLambdaMethodCollector.build(); + if (allowDefaultMethods) { + checkState( + interfaceLambdaMethods.isEmpty(), + "Desugaring with default methods enabled moved interface lambdas"); + } + + // Write out the lambda classes we generated along the way + for (Map.Entry<Path, LambdaInfo> lambdaClass : lambdas.drain().entrySet()) { + try (InputStream bytecode = + Files.newInputStream(dumpDirectory.resolve(lambdaClass.getKey()))) { + ClassReader reader = rewriter.reader(bytecode); CoreLibraryRewriter.UnprefixingClassWriter writer = - rewriter.writer(ClassWriter.COMPUTE_MAXS /*for bridge methods*/); + rewriter.writer(ClassWriter.COMPUTE_MAXS /*for invoking bridges*/); ClassVisitor visitor = writer; + if (!allowDefaultMethods) { - visitor = new Java7Compatibility(visitor, readerFactory); + // null ClassReaderFactory b/c we don't expect to need it for lambda classes + visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null); } visitor = - new LambdaDesugaring( - visitor, loader, lambdas, interfaceLambdaMethodCollector, allowDefaultMethods); - + 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); - - writeStoredEntry(out, entry.getName(), writer.toByteArray()); - } else { - // TODO(bazel-team): Avoid de- and re-compressing resource files - ZipEntry destEntry = new ZipEntry(entry); - destEntry.setCompressedSize(-1); - out.putNextEntry(destEntry); - ByteStreams.copy(content, out); - out.closeEntry(); + String filename = + rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class"; + writeStoredEntry(out, filename, writer.toByteArray()); } } - } - ImmutableSet<String> interfaceLambdaMethods = interfaceLambdaMethodCollector.build(); - if (allowDefaultMethods) { - checkState( - interfaceLambdaMethods.isEmpty(), - "Desugaring with default methods enabled moved interface lambdas"); - } - - // Write out the lambda classes we generated along the way - for (Map.Entry<Path, LambdaInfo> lambdaClass : lambdas.drain().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"; - writeStoredEntry(out, 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); + private static List<InputOutputPair> toInputOutputPairs(Options options) { + final ImmutableList.Builder<InputOutputPair> ioPairListbuilder = ImmutableList.builder(); + for (Iterator<Path> inputIt = options.inputJars.iterator(), + outputIt = options.outputJars.iterator(); + inputIt.hasNext();) { + ioPairListbuilder.add(InputOutputPair.create(inputIt.next(), outputIt.next())); } + return ioPairListbuilder.build(); } private static void writeStoredEntry(ZipOutputStream out, String filename, byte[] content) @@ -357,4 +385,19 @@ class Desugar { }); } } + + /** + * Pair input and output. + */ + @AutoValue + abstract static class InputOutputPair { + + static InputOutputPair create(Path input, Path output) { + return new AutoValue_Desugar_InputOutputPair(input, output); + } + + abstract Path getInput(); + + abstract Path getOutput(); + } } diff --git a/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java b/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java index d8f2e28..2a9f9be 100644 --- a/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java +++ b/java/com/google/devtools/build/android/desugar/LambdaClassMaker.java @@ -54,7 +54,7 @@ class LambdaClassMaker { } /** - * Returns relative paths to .class files generated since the last call to this method together + * Returns absolute paths to .class files generated since the last call to this method together * with a string descriptor of the factory method. */ public Map<Path, LambdaInfo> drain() { @@ -68,12 +68,12 @@ class LambdaClassMaker { // will not contain '/' and searches will fail. So, construct an absolute path from the given // string and use its string representation to find the file we need regardless of host // system's file system - final String rootPathPrefixStr = rootDirectory.resolve(pathPrefix).toString(); + Path rootPathPrefix = rootDirectory.resolve(pathPrefix); + final String rootPathPrefixStr = rootPathPrefix.toString(); - // TODO(kmb): Investigate making this faster in the case of many lambdas // TODO(bazel-team): This could be much nicer with lambdas - try (Stream<Path> results = - Files.walk(rootDirectory) + try (Stream<Path> paths = + Files.list(rootPathPrefix.getParent()) .filter( new Predicate<Path>() { @Override @@ -82,7 +82,7 @@ class LambdaClassMaker { && !generatedClasses.containsKey(path); } })) { - return Iterators.getOnlyElement(results.iterator()); + return Iterators.getOnlyElement(paths.iterator()); } } } diff --git a/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java b/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java index c52c4a4..c808831 100644 --- a/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java +++ b/java/com/google/devtools/build/android/desugar/LambdaDesugaring.java @@ -220,7 +220,7 @@ class LambdaDesugaring extends ClassVisitor { return result; // we're already queued up a bridge method for this method reference } - String name = "bridge$lambda$" + bridgeMethods.size(); + String name = uniqueInPackage(internalName, "bridge$lambda$" + bridgeMethods.size()); Handle bridgeMethod; switch (invokedMethod.getTag()) { case Opcodes.H_INVOKESTATIC: |