summaryrefslogtreecommitdiff
path: root/java/com/google/devtools/build/android/desugar/Desugar.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/devtools/build/android/desugar/Desugar.java')
-rw-r--r--java/com/google/devtools/build/android/desugar/Desugar.java189
1 files changed, 113 insertions, 76 deletions
diff --git a/java/com/google/devtools/build/android/desugar/Desugar.java b/java/com/google/devtools/build/android/desugar/Desugar.java
index 6f9e0f0..22780ea 100644
--- a/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -18,14 +18,14 @@ 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.ImmutableMap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.io.ByteStreams;
+import com.google.common.io.Closer;
import com.google.devtools.build.android.Converters.ExistingPathConverter;
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 java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileVisitResult;
@@ -34,14 +34,9 @@ import java.nio.file.Path;
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;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
@@ -62,8 +57,8 @@ class Desugar {
converter = ExistingPathConverter.class,
abbrev = 'i',
help =
- "Input Jar with classes to desugar (required, the n-th input is paired with the n-th "
- + "output)."
+ "Input Jar or directory with classes to desugar (required, the n-th input is paired with"
+ + "the n-th output)."
)
public List<Path> inputJars;
@@ -96,6 +91,15 @@ class Desugar {
public boolean allowEmptyBootclasspath;
@Option(
+ name = "only_desugar_javac9_for_lint",
+ defaultValue = "false",
+ help =
+ "A temporary flag specifically for android lint, subject to removal anytime (DO NOT USE)",
+ category = "undocumented"
+ )
+ public boolean onlyDesugarJavac9ForLint;
+
+ @Option(
name = "output",
allowMultiple = true,
defaultValue = "",
@@ -103,8 +107,8 @@ class Desugar {
converter = PathConverter.class,
abbrev = 'o',
help =
- "Output Jar to write desugared classes into (required, the n-th output is paired with "
- + "the n-th input)."
+ "Output Jar or directory to write desugared classes into (required, the n-th output is "
+ + "paired with the n-th input, output must be a Jar if input is a Jar)."
)
public List<Path> outputJars;
@@ -171,7 +175,12 @@ class Desugar {
"Desugar requires the same number of inputs and outputs to pair them");
checkState(!options.bootclasspath.isEmpty() || options.allowEmptyBootclasspath,
"At least one --bootclasspath_entry is required");
-
+ for (Path path : options.classpath) {
+ checkState(!Files.isDirectory(path), "Classpath entry must be a jar file: %s", path);
+ }
+ for (Path path : options.bootclasspath) {
+ checkState(!Files.isDirectory(path), "Bootclasspath entry must be a jar file: %s", path);
+ }
if (options.verbose) {
System.out.printf("Lambda classes will be written under %s%n", dumpDirectory);
}
@@ -186,72 +195,86 @@ class Desugar {
// 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())))) {
+ 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)
- ? appAndClasspathIndexedJars
- : appIndexedJar,
+ ? appAndClasspathIndexedInputs
+ : appIndexedInputs,
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")) {
+ // 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 (!allowDefaultMethods) {
- visitor = new Java7Compatibility(visitor, readerFactory);
- }
- visitor =
- new LambdaDesugaring(
- visitor, loader, lambdas, interfaceLambdaMethodCollector,
- allowDefaultMethods);
+ 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);
- writeStoredEntry(out, entry.getName(), writer.toByteArray());
+ outputFileProvider.write(filename, 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();
+ outputFileProvider.copyFrom(filename, appInputFiles);
}
}
}
ImmutableSet<String> interfaceLambdaMethods = interfaceLambdaMethodCollector.build();
- if (allowDefaultMethods) {
- checkState(
- interfaceLambdaMethods.isEmpty(),
- "Desugaring with default methods enabled moved interface lambdas");
- }
+ checkState(
+ !allowDefaultMethods || 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()) {
+ 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);
@@ -282,7 +305,7 @@ class Desugar {
reader.accept(visitor, 0);
String filename =
rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class";
- writeStoredEntry(out, filename, writer.toByteArray());
+ outputFileProvider.write(filename, writer.toByteArray());
}
}
@@ -292,7 +315,7 @@ class Desugar {
}
}
- private static List<InputOutputPair> toInputOutputPairs(Options options) {
+ 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();
@@ -302,38 +325,22 @@ class Desugar {
return ioPairListbuilder.build();
}
- private static void writeStoredEntry(ZipOutputStream out, String filename, byte[] content)
+ private static ClassLoader createClassLoader(
+ CoreLibraryRewriter rewriter,
+ List<InputFileProvider> bootclasspath,
+ IndexedInputs appAndClasspathIndexedInputs)
throws IOException {
- // Need to pre-compute checksum for STORED (uncompressed) entries)
- CRC32 checksum = new CRC32();
- checksum.update(content);
-
- ZipEntry result = new ZipEntry(filename);
- result.setTime(0L); // Use stable timestamp Jan 1 1980
- result.setCrc(checksum.getValue());
- result.setSize(content.length);
- result.setCompressedSize(content.length);
- // Write uncompressed, since this is just an intermediary artifact that we will convert to .dex
- result.setMethod(ZipEntry.STORED);
-
- out.putNextEntry(result);
- out.write(content);
- out.closeEntry();
- }
-
- private static ClassLoader createClassLoader(CoreLibraryRewriter rewriter,
- List<Path> bootclasspath, IndexedJars appAndClasspathIndexedJars) 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 IndexedJars(bootclasspath), rewriter, parent);
+ 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(appAndClasspathIndexedJars, rewriter, parent);
+ return new HeaderClassLoader(appAndClasspathIndexedInputs, rewriter, parent);
}
private static class ThrowingClassLoader extends ClassLoader {
@@ -386,6 +393,36 @@ class Desugar {
}
}
+ /** Transform a Path to an {@link OutputFileProvider} */
+ private static OutputFileProvider toOutputFileProvider(Path path)
+ throws IOException {
+ if (Files.isDirectory(path)) {
+ return new DirectoryOutputFileProvider(path);
+ } else {
+ return new ZipOutputFileProvider(path);
+ }
+ }
+
+ /** 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)
+ throws IOException {
+ if (Files.isDirectory(path)) {
+ return closer.register(new DirectoryInputFileProvider(path));
+ } else {
+ return closer.register(new ZipInputFileProvider(path));
+ }
+ }
+
+ private static ImmutableList<InputFileProvider> toInputFileProvider(
+ Closer closer, List<Path> paths) throws IOException {
+ ImmutableList.Builder<InputFileProvider> builder = new ImmutableList.Builder<>();
+ for (Path path : paths) {
+ checkState(!Files.isDirectory(path), "Directory is not supported: %s", path);
+ builder.add(closer.register(new ZipInputFileProvider(path)));
+ }
+ return builder.build();
+ }
+
/**
* Pair input and output.
*/