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.java265
1 files changed, 193 insertions, 72 deletions
diff --git a/java/com/google/devtools/build/android/desugar/Desugar.java b/java/com/google/devtools/build/android/desugar/Desugar.java
index 5d3df4a..c176f9c 100644
--- a/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -17,7 +17,6 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.devtools.build.android.desugar.LambdaClassMaker.LAMBDA_METAFACTORY_DUMPER_PROPERTY;
-import static java.nio.charset.StandardCharsets.ISO_8859_1;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
@@ -28,17 +27,24 @@ 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.build.android.desugar.CoreLibraryRewriter.UnprefixingClassWriter;
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
+import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter.UnprefixingClassWriter;
+import com.google.devtools.build.android.desugar.io.HeaderClassLoader;
+import com.google.devtools.build.android.desugar.io.IndexedInputs;
+import com.google.devtools.build.android.desugar.io.InputFileProvider;
+import com.google.devtools.build.android.desugar.io.OutputFileProvider;
+import com.google.devtools.build.android.desugar.io.ThrowingClassLoader;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
-import com.google.devtools.common.options.Options;
import com.google.devtools.common.options.OptionsBase;
-import com.google.errorprone.annotations.MustBeClosed;
+import com.google.devtools.common.options.OptionsParser;
+import com.google.devtools.common.options.ShellQuotedParamsFilePreProcessor;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
+import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -195,6 +201,15 @@ class Desugar {
public boolean tolerateMissingDependencies;
@Option(
+ name = "desugar_supported_core_libs",
+ defaultValue = "false",
+ documentationCategory = OptionDocumentationCategory.UNCATEGORIZED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Enable core library desugaring, which requires configuration with related flags."
+ )
+ public boolean desugarCoreLibs;
+
+ @Option(
name = "desugar_interface_method_bodies_if_needed",
defaultValue = "true",
category = "misc",
@@ -248,6 +263,51 @@ class Desugar {
)
public boolean coreLibrary;
+ /** Type prefixes that we'll move to a custom package. */
+ @Option(
+ name = "rewrite_core_library_prefix",
+ defaultValue = "", // ignored
+ allowMultiple = true,
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Assume the given java.* prefixes are desugared."
+ )
+ public List<String> rewriteCoreLibraryPrefixes;
+
+ /** Interfaces whose default and static interface methods we'll emulate. */
+ @Option(
+ name = "emulate_core_library_interface",
+ defaultValue = "", // ignored
+ allowMultiple = true,
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Assume the given java.* interfaces are emulated."
+ )
+ public List<String> emulateCoreLibraryInterfaces;
+
+ /** Members that we will retarget to the given new owner. */
+ @Option(
+ name = "retarget_core_library_member",
+ defaultValue = "", // ignored
+ allowMultiple = true,
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Method invocations to retarget, given as \"class/Name#member->new/class/Name\". "
+ + "The new owner is blindly assumed to exist."
+ )
+ public List<String> retargetCoreLibraryMembers;
+
+ /** Members not to rewrite. */
+ @Option(
+ name = "dont_rewrite_core_library_invocation",
+ defaultValue = "", // ignored
+ allowMultiple = true,
+ documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+ effectTags = {OptionEffectTag.UNKNOWN},
+ help = "Method invocations not to rewrite, given as \"class/Name#method\"."
+ )
+ public List<String> dontTouchCoreLibraryMembers;
+
/** Set to work around b/62623509 with JaCoCo versions prior to 0.7.9. */
// TODO(kmb): Remove when Android Studio doesn't need it anymore (see b/37116789)
@Option(
@@ -265,7 +325,7 @@ class Desugar {
private final DesugarOptions options;
private final CoreLibraryRewriter rewriter;
private final LambdaClassMaker lambdas;
- private final GeneratedClassStore store;
+ private final GeneratedClassStore store = new GeneratedClassStore();
private final Set<String> visitedExceptionTypes = new HashSet<>();
/** The counter to record the times of try-with-resources desugaring is invoked. */
private final AtomicInteger numOfTryWithResourcesInvoked = new AtomicInteger();
@@ -282,7 +342,6 @@ class Desugar {
this.options = options;
this.rewriter = new CoreLibraryRewriter(options.coreLibrary ? "__desugar__/" : "");
this.lambdas = new LambdaClassMaker(dumpDirectory);
- this.store = new GeneratedClassStore();
this.outputJava7 = options.minSdkVersion < 24;
this.allowDefaultMethods =
options.desugarInterfaceMethodBodiesIfNeeded || options.minSdkVersion >= 24;
@@ -332,8 +391,8 @@ class Desugar {
Files.isDirectory(inputPath) || !Files.isDirectory(outputPath),
"Input jar file requires an output jar file");
- try (OutputFileProvider outputFileProvider = toOutputFileProvider(outputPath);
- InputFileProvider inputFiles = toInputFileProvider(inputPath)) {
+ try (OutputFileProvider outputFileProvider = OutputFileProvider.create(outputPath);
+ InputFileProvider inputFiles = InputFileProvider.open(inputPath)) {
DependencyCollector depsCollector = createDepsCollector();
IndexedInputs indexedInputFiles = new IndexedInputs(ImmutableList.of(inputFiles));
// Prepend classpath with input file itself so LambdaDesugaring can load classes with
@@ -358,6 +417,17 @@ class Desugar {
ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder();
ClassVsInterface interfaceCache = new ClassVsInterface(classpathReader);
+ CoreLibrarySupport coreLibrarySupport =
+ options.desugarCoreLibs
+ ? new CoreLibrarySupport(
+ rewriter,
+ loader,
+ options.rewriteCoreLibraryPrefixes,
+ options.emulateCoreLibraryInterfaces,
+ options.retargetCoreLibraryMembers,
+ options.dontTouchCoreLibraryMembers)
+ : null;
+
desugarClassesInInput(
inputFiles,
outputFileProvider,
@@ -365,6 +435,7 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ coreLibrarySupport,
interfaceCache,
interfaceLambdaMethodCollector);
@@ -374,11 +445,14 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ coreLibrarySupport,
interfaceCache,
interfaceLambdaMethodCollector.build(),
bridgeMethodReader);
- desugarAndWriteGeneratedClasses(outputFileProvider, bootclasspathReader);
+ desugarAndWriteGeneratedClasses(
+ outputFileProvider, loader, bootclasspathReader, coreLibrarySupport);
+
copyThrowableExtensionClass(outputFileProvider);
byte[] depsInfo = depsCollector.toByteArray();
@@ -394,7 +468,7 @@ class Desugar {
}
/**
- * Returns a dependency collector for use with a single input Jar. If
+ * Returns a dependency collector for use with a single input Jar. If
* {@link DesugarOptions#emitDependencyMetadata} is set, this method instantiates the collector
* reflectively to allow compiling and using the desugar tool without this mechanism.
*/
@@ -445,19 +519,20 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
+ @Nullable CoreLibrarySupport coreLibrarySupport,
ClassVsInterface interfaceCache,
ImmutableSet.Builder<String> interfaceLambdaMethodCollector)
throws IOException {
- for (String filename : inputFiles) {
- if (OutputFileProvider.DESUGAR_DEPS_FILENAME.equals(filename)) {
+ for (String inputFilename : inputFiles) {
+ if (OutputFileProvider.DESUGAR_DEPS_FILENAME.equals(inputFilename)) {
// TODO(kmb): rule out that this happens or merge input file with what's in depsCollector
continue; // skip as we're writing a new file like this at the end or don't want it
}
- try (InputStream content = inputFiles.getInputStream(filename)) {
+ try (InputStream content = inputFiles.getInputStream(inputFilename)) {
// 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")) {
+ if (inputFilename.endsWith(".class")) {
ClassReader reader = rewriter.reader(content);
UnprefixingClassWriter writer = rewriter.writer(ClassWriter.COMPUTE_MAXS);
ClassVisitor visitor =
@@ -466,19 +541,24 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ coreLibrarySupport,
interfaceCache,
interfaceLambdaMethodCollector,
writer,
reader);
if (writer == visitor) {
// Just copy the input if there are no rewritings
- outputFileProvider.write(filename, reader.b);
+ outputFileProvider.write(inputFilename, reader.b);
} else {
reader.accept(visitor, 0);
+ String filename = writer.getClassName() + ".class";
+ checkState(
+ (options.coreLibrary && coreLibrarySupport != null)
+ || filename.equals(inputFilename));
outputFileProvider.write(filename, writer.toByteArray());
}
} else {
- outputFileProvider.copyFrom(filename, inputFiles);
+ outputFileProvider.copyFrom(inputFilename, inputFiles);
}
}
}
@@ -494,6 +574,7 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
+ @Nullable CoreLibrarySupport coreLibrarySupport,
ClassVsInterface interfaceCache,
ImmutableSet<String> interfaceLambdaMethods,
@Nullable ClassReaderFactory bridgeMethodReader)
@@ -525,6 +606,7 @@ class Desugar {
classpathReader,
depsCollector,
bootclasspathReader,
+ coreLibrarySupport,
interfaceCache,
interfaceLambdaMethods,
bridgeMethodReader,
@@ -532,17 +614,26 @@ class Desugar {
writer,
reader);
reader.accept(visitor, 0);
- String filename =
- rewriter.unprefix(lambdaClass.getValue().desiredInternalName()) + ".class";
- outputFileProvider.write(filename, writer.toByteArray());
+ checkState(
+ (options.coreLibrary && coreLibrarySupport != null)
+ || rewriter
+ .unprefix(lambdaClass.getValue().desiredInternalName())
+ .equals(writer.getClassName()));
+ outputFileProvider.write(writer.getClassName() + ".class", writer.toByteArray());
}
}
}
private void desugarAndWriteGeneratedClasses(
- OutputFileProvider outputFileProvider, ClassReaderFactory bootclasspathReader)
+ OutputFileProvider outputFileProvider,
+ ClassLoader loader,
+ ClassReaderFactory bootclasspathReader,
+ @Nullable CoreLibrarySupport coreLibrarySupport)
throws IOException {
// Write out any classes we generated along the way
+ if (coreLibrarySupport != null) {
+ coreLibrarySupport.makeDispatchHelpers(store);
+ }
ImmutableMap<String, ClassNode> generatedClasses = store.drain();
checkState(
generatedClasses.isEmpty() || (allowDefaultMethods && outputJava7),
@@ -552,11 +643,39 @@ class Desugar {
UnprefixingClassWriter writer = rewriter.writer(ClassWriter.COMPUTE_MAXS);
// checkState above implies that we want Java 7 .class files, so send through that visitor.
// Don't need a ClassReaderFactory b/c static interface methods should've been moved.
- ClassVisitor visitor =
- new Java7Compatibility(writer, (ClassReaderFactory) null, bootclasspathReader);
+ ClassVisitor visitor = writer;
+ if (coreLibrarySupport != null) {
+ visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport);
+ visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
+ visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+ }
+
+ if (!allowTryWithResources) {
+ CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner();
+ generated.getValue().accept(closeResourceMethodScanner);
+ visitor =
+ new TryWithResourcesRewriter(
+ visitor,
+ loader,
+ visitedExceptionTypes,
+ numOfTryWithResourcesInvoked,
+ closeResourceMethodScanner.hasCloseResourceMethod());
+ }
+ if (!allowCallsToObjectsNonNull) {
+ // Not sure whether there will be implicit null check emitted by javac, so we rerun
+ // the inliner again
+ visitor = new ObjectsRequireNonNullMethodRewriter(visitor, rewriter);
+ }
+ if (!allowCallsToLongCompare) {
+ visitor = new LongCompareMethodRewriter(visitor, rewriter);
+ }
+
+ visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null, bootclasspathReader);
generated.getValue().accept(visitor);
- String filename = rewriter.unprefix(generated.getKey()) + ".class";
- outputFileProvider.write(filename, writer.toByteArray());
+ checkState(
+ (options.coreLibrary && coreLibrarySupport != null)
+ || rewriter.unprefix(generated.getKey()).equals(writer.getClassName()));
+ outputFileProvider.write(writer.getClassName() + ".class", writer.toByteArray());
}
}
@@ -569,6 +688,7 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
+ @Nullable CoreLibrarySupport coreLibrarySupport,
ClassVsInterface interfaceCache,
ImmutableSet<String> interfaceLambdaMethods,
@Nullable ClassReaderFactory bridgeMethodReader,
@@ -576,6 +696,13 @@ class Desugar {
UnprefixingClassWriter writer,
ClassReader input) {
ClassVisitor visitor = checkNotNull(writer);
+
+ if (coreLibrarySupport != null) {
+ visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport);
+ visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
+ visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+ }
+
if (!allowTryWithResources) {
CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner();
input.accept(closeResourceMethodScanner, ClassReader.SKIP_DEBUG);
@@ -590,10 +717,10 @@ class Desugar {
if (!allowCallsToObjectsNonNull) {
// Not sure whether there will be implicit null check emitted by javac, so we rerun
// the inliner again
- visitor = new ObjectsRequireNonNullMethodRewriter(visitor);
+ visitor = new ObjectsRequireNonNullMethodRewriter(visitor, rewriter);
}
if (!allowCallsToLongCompare) {
- visitor = new LongCompareMethodRewriter(visitor);
+ visitor = new LongCompareMethodRewriter(visitor, rewriter);
}
if (outputJava7) {
// null ClassReaderFactory b/c we don't expect to need it for lambda classes
@@ -601,17 +728,25 @@ class Desugar {
if (options.desugarInterfaceMethodBodiesIfNeeded) {
visitor =
new DefaultMethodClassFixer(
- visitor, classpathReader, depsCollector, bootclasspathReader, loader);
+ visitor,
+ classpathReader,
+ depsCollector,
+ coreLibrarySupport,
+ bootclasspathReader,
+ loader);
visitor =
new InterfaceDesugaring(
visitor,
interfaceCache,
depsCollector,
+ coreLibrarySupport,
bootclasspathReader,
+ loader,
store,
options.legacyJacocoFix);
}
}
+
visitor =
new LambdaClassFixer(
visitor,
@@ -639,11 +774,19 @@ class Desugar {
@Nullable ClassReaderFactory classpathReader,
DependencyCollector depsCollector,
ClassReaderFactory bootclasspathReader,
+ @Nullable CoreLibrarySupport coreLibrarySupport,
ClassVsInterface interfaceCache,
ImmutableSet.Builder<String> interfaceLambdaMethodCollector,
UnprefixingClassWriter writer,
ClassReader input) {
ClassVisitor visitor = checkNotNull(writer);
+
+ if (coreLibrarySupport != null) {
+ visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport);
+ visitor = new CorePackageRenamer(visitor, coreLibrarySupport);
+ visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport);
+ }
+
if (!allowTryWithResources) {
CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner();
input.accept(closeResourceMethodScanner, ClassReader.SKIP_DEBUG);
@@ -656,10 +799,10 @@ class Desugar {
closeResourceMethodScanner.hasCloseResourceMethod());
}
if (!allowCallsToObjectsNonNull) {
- visitor = new ObjectsRequireNonNullMethodRewriter(visitor);
+ visitor = new ObjectsRequireNonNullMethodRewriter(visitor, rewriter);
}
if (!allowCallsToLongCompare) {
- visitor = new LongCompareMethodRewriter(visitor);
+ visitor = new LongCompareMethodRewriter(visitor, rewriter);
}
if (!options.onlyDesugarJavac9ForLint) {
if (outputJava7) {
@@ -667,17 +810,25 @@ class Desugar {
if (options.desugarInterfaceMethodBodiesIfNeeded) {
visitor =
new DefaultMethodClassFixer(
- visitor, classpathReader, depsCollector, bootclasspathReader, loader);
+ visitor,
+ classpathReader,
+ depsCollector,
+ coreLibrarySupport,
+ bootclasspathReader,
+ loader);
visitor =
new InterfaceDesugaring(
visitor,
interfaceCache,
depsCollector,
+ coreLibrarySupport,
bootclasspathReader,
+ loader,
store,
options.legacyJacocoFix);
}
}
+
// LambdaDesugaring is relatively expensive, so check first whether we need it. Additionally,
// we need to collect lambda methods referenced by invokedynamic instructions up-front anyway.
// TODO(kmb): Scan constant pool instead of visiting the class to find bootstrap methods etc.
@@ -730,7 +881,7 @@ class Desugar {
} catch (ReflectiveOperationException e) {
// We do not want to crash Desugar, if we cannot load or access these classes or fields.
// We aim to provide better diagnostics. If we cannot, just let it go.
- e.printStackTrace();
+ e.printStackTrace(System.err); // To silence error-prone's complaint.
}
}
@@ -760,13 +911,12 @@ class Desugar {
return dumpDirectory;
}
- private static DesugarOptions parseCommandLineOptions(String[] args) throws IOException {
- if (args.length == 1 && args[0].startsWith("@")) {
- args = Files.readAllLines(Paths.get(args[0].substring(1)), ISO_8859_1).toArray(new String[0]);
- }
- DesugarOptions options =
- Options.parseAndExitUponError(DesugarOptions.class, /*allowResidue=*/ false, args)
- .getOptions();
+ private static DesugarOptions parseCommandLineOptions(String[] args) {
+ OptionsParser parser = OptionsParser.newOptionsParser(DesugarOptions.class);
+ parser.setAllowResidue(false);
+ parser.enableParamsFileSupport(new ShellQuotedParamsFilePreProcessor(FileSystems.getDefault()));
+ parser.parseAndExitUponError(args);
+ DesugarOptions options = parser.getOptions(DesugarOptions.class);
checkArgument(!options.inputJars.isEmpty(), "--input is required");
checkArgument(
@@ -780,6 +930,10 @@ class Desugar {
for (Path path : options.bootclasspath) {
checkArgument(!Files.isDirectory(path), "Bootclasspath entry must be a jar file: %s", path);
}
+ checkArgument(!options.desugarCoreLibs
+ || !options.rewriteCoreLibraryPrefixes.isEmpty()
+ || !options.emulateCoreLibraryInterfaces.isEmpty(),
+ "--desugar_supported_core_libs requires specifying renamed and/or emulated core libraries");
return options;
}
@@ -793,19 +947,6 @@ class Desugar {
return ioPairListbuilder.build();
}
- @VisibleForTesting
- static class ThrowingClassLoader extends ClassLoader {
- @Override
- protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
- if (name.startsWith("java.")) {
- // Use system class loader for java. classes, since ClassLoader.defineClass gets
- // grumpy when those don't come from the standard place.
- return super.loadClass(name, resolve);
- }
- throw new ClassNotFoundException();
- }
- }
-
private static void deleteTreeOnExit(final Path directory) {
Thread shutdownHook =
new Thread() {
@@ -844,26 +985,6 @@ class Desugar {
}
}
- /** Transform a Path to an {@link OutputFileProvider} */
- @MustBeClosed
- 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 that needs to be closed by the caller. */
- @MustBeClosed
- private static InputFileProvider toInputFileProvider(Path path) throws IOException {
- if (Files.isDirectory(path)) {
- return new DirectoryInputFileProvider(path);
- } else {
- return new ZipInputFileProvider(path);
- }
- }
-
/**
* Transform a list of Path to a list of InputFileProvider and register them with the given
* closer.
@@ -874,7 +995,7 @@ class Desugar {
Closer closer, List<Path> paths) throws IOException {
ImmutableList.Builder<InputFileProvider> builder = new ImmutableList.Builder<>();
for (Path path : paths) {
- builder.add(closer.register(toInputFileProvider(path)));
+ builder.add(closer.register(InputFileProvider.open(path)));
}
return builder.build();
}