From c31e8e53b7cda8ad574a7d4adc582e5287221c3b Mon Sep 17 00:00:00 2001 From: Googler Date: Wed, 15 Mar 2017 11:04:52 +0000 Subject: Allow to have several inputs and outputs RELNOTES: Allow to have several inputs and outputs -- PiperOrigin-RevId: 150176100 MOS_MIGRATED_REVID=150176100 GitOrigin-RevId: eb46bedecfb54584905b800a493962351a4c0a51 Change-Id: I5a0db10aef9f005b5c61a03f598f1bbfdf2f3181 --- .../devtools/build/android/desugar/Desugar.java | 230 ++++++++++++--------- 1 file changed, 137 insertions(+), 93 deletions(-) diff --git a/java/com/google/devtools/build/android/desugar/Desugar.java b/java/com/google/devtools/build/android/desugar/Desugar.java index 3ef5489..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 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 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,108 +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 interfaceLambdaMethodCollector = ImmutableSet.builder(); - - // Process input Jar, desugaring as we go - for (Enumeration 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 interfaceLambdaMethodCollector = ImmutableSet.builder(); + + // Process input Jar, desugaring as we go + for (Enumeration 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 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 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 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 lambdaClass : lambdas.drain().entrySet()) { - try (InputStream bytecode = Files.newInputStream(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 leftBehind = lambdas.drain(); + checkState(leftBehind.isEmpty(), "Didn't process %s", leftBehind); } + } + } - Map leftBehind = lambdas.drain(); - checkState(leftBehind.isEmpty(), "Didn't process %s", leftBehind); + private static List toInputOutputPairs(Options options) { + final ImmutableList.Builder ioPairListbuilder = ImmutableList.builder(); + for (Iterator 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) @@ -356,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(); + } } -- cgit v1.2.3