| // Copyright 2016 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.android.desugar; |
| |
| 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.common.collect.ImmutableList.toImmutableList; |
| import static com.google.devtools.build.android.desugar.LambdaClassMaker.LAMBDA_METAFACTORY_DUMPER_PROPERTY; |
| import static com.google.devtools.build.android.desugar.retarget.ReplacementRange.DESUGAR_JAVA8_CORE_LIBS; |
| import static com.google.devtools.build.android.desugar.retarget.ReplacementRange.DESUGAR_JAVA8_LIBS; |
| import static com.google.devtools.build.android.desugar.retarget.ReplacementRange.REPLACE_CALLS_TO_LONG_UNSIGNED; |
| import static com.google.devtools.build.android.desugar.retarget.ReplacementRange.REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS; |
| import static com.google.devtools.build.android.desugar.strconcat.IndyStringConcatDesugaring.INVOKE_JDK11_STRING_CONCAT; |
| |
| import com.google.auto.value.AutoValue; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Throwables; |
| import com.google.common.collect.ConcurrentHashMultiset; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.flogger.GoogleLogger; |
| import com.google.common.io.ByteStreams; |
| import com.google.common.io.Closer; |
| import com.google.common.io.Resources; |
| import com.google.devtools.build.android.desugar.config.DesugarOptions; |
| import com.google.devtools.build.android.desugar.corelibadapter.InvocationSiteTransformationRecord; |
| import com.google.devtools.build.android.desugar.corelibadapter.InvocationSiteTransformationRecord.InvocationSiteTransformationRecordBuilder; |
| import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiAdaptersGenerator; |
| import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiInvocationSite; |
| import com.google.devtools.build.android.desugar.corelibadapter.ShadowedApiInvocationSite.ImmutableLabelRemover; |
| import com.google.devtools.build.android.desugar.covariantreturn.NioBufferRefConverter; |
| import com.google.devtools.build.android.desugar.io.BootClassPathDigest; |
| 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.FileBasedTypeReferenceClosure; |
| import com.google.devtools.build.android.desugar.io.FileContentProvider; |
| 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.JarDigest; |
| import com.google.devtools.build.android.desugar.io.OutputFileProvider; |
| import com.google.devtools.build.android.desugar.io.ResourceBasedClassFiles; |
| import com.google.devtools.build.android.desugar.io.ThrowingClassLoader; |
| import com.google.devtools.build.android.desugar.langmodel.ClassAttributeRecord; |
| import com.google.devtools.build.android.desugar.langmodel.ClassMemberRecord; |
| import com.google.devtools.build.android.desugar.langmodel.ClassMemberUseCounter; |
| import com.google.devtools.build.android.desugar.langmodel.ClassName; |
| import com.google.devtools.build.android.desugar.langmodel.DesugarMethodAttribute; |
| import com.google.devtools.build.android.desugar.nest.NestAnalyzer; |
| import com.google.devtools.build.android.desugar.nest.NestDesugaring; |
| import com.google.devtools.build.android.desugar.nest.NestDigest; |
| import com.google.devtools.build.android.desugar.preanalysis.InputPreAnalyzer; |
| import com.google.devtools.build.android.desugar.retarget.ClassMemberRetargetConfig; |
| import com.google.devtools.build.android.desugar.retarget.ClassMemberRetargetRewriter; |
| import com.google.devtools.build.android.desugar.retarget.ReplacementRange; |
| import com.google.devtools.build.android.desugar.strconcat.IndyStringConcatDesugaring; |
| import com.google.devtools.build.android.desugar.typeannotation.LocalTypeAnnotationUse; |
| import com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchy; |
| import com.google.devtools.build.android.desugar.typehierarchy.TypeHierarchyScavenger; |
| import com.google.devtools.build.android.r8.DependencyCollector; |
| import com.google.devtools.build.lib.worker.WorkerProtocol.WorkRequest; |
| import com.google.devtools.build.lib.worker.WorkerProtocol.WorkResponse; |
| import java.io.IOError; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.Field; |
| import java.nio.file.FileVisitResult; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.SimpleFileVisitor; |
| import java.nio.file.attribute.BasicFileAttributes; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.function.Consumer; |
| import java.util.stream.Collectors; |
| import javax.annotation.Nullable; |
| import org.objectweb.asm.Attribute; |
| import org.objectweb.asm.ClassReader; |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.ClassWriter; |
| import org.objectweb.asm.tree.ClassNode; |
| |
| /** |
| * Command-line tool to desugar Java 8 constructs that dx doesn't know what to do with, in |
| * particular lambdas and method references. |
| */ |
| public class Desugar { |
| |
| // It is important that this method is called first. See its javadoc. |
| private static final Path DUMP_DIRECTORY = createAndRegisterLambdaDumpDirectory(); |
| private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); |
| private static final ResourceBasedClassFiles resourceBasedClassFiles = |
| new ResourceBasedClassFiles(); |
| private static final String RUNTIME_LIB_PACKAGE = |
| "com/google/devtools/build/android/desugar/runtime/"; |
| private static final Attribute[] customAttributes = {new DesugarMethodAttribute()}; |
| private final DesugarOptions options; |
| private final CoreLibraryRewriter rewriter; |
| private final LambdaClassMaker lambdas; |
| private final GeneratedClassStore store = new GeneratedClassStore(); |
| private final ClassMemberUseCounter classMemberUseCounter = |
| new ClassMemberUseCounter(ConcurrentHashMultiset.create()); |
| private final Set<String> visitedExceptionTypes = new LinkedHashSet<>(); |
| /** The counter to record the times of try-with-resources desugaring is invoked. */ |
| private final AtomicInteger numOfTryWithResourcesInvoked = new AtomicInteger(); |
| |
| private final boolean outputJava7; |
| private final boolean allowDefaultMethods; |
| private final boolean allowTryWithResources; |
| private final boolean allowCallsToObjectsNonNull; |
| private final boolean allowCallsToLongCompare; |
| |
| private final ImmutableSet<ReplacementRange> enabledInvocationReplacementRanges; |
| /** An instance of Desugar is expected to be used ONLY ONCE */ |
| private boolean used; |
| |
| @Nullable private TypeHierarchy typeHierarchy; |
| @Nullable private BootClassPathDigest bootClassPathDigest; |
| |
| private Desugar(DesugarOptions options, Path dumpDirectory) { |
| this.options = options; |
| this.rewriter = new CoreLibraryRewriter(options.coreLibrary ? ClassName.IN_PROCESS_LABEL : ""); |
| this.lambdas = new LambdaClassMaker(dumpDirectory); |
| this.outputJava7 = options.minSdkVersion < 24; |
| this.allowDefaultMethods = |
| options.desugarInterfaceMethodBodiesIfNeeded || options.minSdkVersion >= 24; |
| this.allowTryWithResources = |
| !options.desugarTryWithResourcesIfNeeded || options.minSdkVersion >= 19; |
| |
| this.allowCallsToObjectsNonNull = options.minSdkVersion >= 19; |
| this.allowCallsToLongCompare = options.minSdkVersion >= 19 && !options.alwaysRewriteLongCompare; |
| boolean allowCallsToLongUnsigned = options.minSdkVersion >= 26; |
| boolean allowCallsToPrimitiveWrappers = options.minSdkVersion >= 24; |
| |
| ImmutableSet.Builder<ReplacementRange> invocationReplacementRangesBuilder = |
| ImmutableSet.builder(); |
| |
| // Exclude the dependency on desugar runtime libs from desugar_jdk_libs. |
| if (!allowCallsToLongUnsigned && !this.options.coreLibrary) { |
| invocationReplacementRangesBuilder.add(REPLACE_CALLS_TO_LONG_UNSIGNED); |
| } |
| // Exclude the dependency on desugar runtime libs from desugar_jdk_libs. |
| if (!allowCallsToPrimitiveWrappers && !this.options.coreLibrary) { |
| invocationReplacementRangesBuilder.add(REPLACE_CALLS_TO_PRIMITIVE_WRAPPERS); |
| } |
| |
| if (options.desugarCoreLibs && options.autoDesugarShadowedApiUse) { |
| invocationReplacementRangesBuilder.add(DESUGAR_JAVA8_LIBS); |
| } |
| |
| if (options.coreLibrary) { |
| invocationReplacementRangesBuilder.add(DESUGAR_JAVA8_CORE_LIBS); |
| } |
| |
| enabledInvocationReplacementRanges = invocationReplacementRangesBuilder.build(); |
| this.used = false; |
| } |
| |
| private void desugar() throws Exception { |
| checkState(!this.used, "This Desugar instance has been used. Please create another one."); |
| this.used = true; |
| |
| List<Path> platformJars = |
| options.classpath.stream() |
| .filter(path -> path.toString().endsWith(".jar")) |
| .filter(path -> JarDigest.fromPath(path).isPlatformJar()) |
| .collect(Collectors.toList()); |
| if (!platformJars.isEmpty()) { |
| if (options.verbose) { |
| logger.atInfo().log( |
| "Platform Jars in class path added to boot class path: %s", platformJars); |
| } |
| options.bootclasspath = |
| ImmutableList.<Path>builder().addAll(options.bootclasspath).addAll(platformJars).build(); |
| } |
| |
| if (options.autoDesugarShadowedApiUse) { |
| typeHierarchy = |
| TypeHierarchyScavenger.analyze( |
| ImmutableList.<Path>builder() |
| .addAll(options.inputJars) |
| .addAll(options.classpath) |
| .addAll(options.bootclasspath) |
| .build(), |
| /* requireTypeResolutionComplete= */ false); |
| bootClassPathDigest = BootClassPathDigest.create(ImmutableList.copyOf(options.bootclasspath)); |
| |
| if (options.verbose) { |
| ImmutableList<ClassName> shadowedTypes = |
| typeHierarchy.methodMetadata().values().stream() |
| .filter(method -> method.owner().isAndroidDomainType()) |
| .flatMap(methodDeclInfo -> methodDeclInfo.headerTypeNameSet().stream()) |
| .filter(ClassName::isDesugarShadowedType) |
| .distinct() |
| .sorted() |
| .collect(toImmutableList()); |
| logger.atInfo().log( |
| "---> Total number of boot class entries(%d) from packages: %s, from jars %s on input" |
| + " %s.", |
| bootClassPathDigest.resourceEntrySize(), |
| bootClassPathDigest.listPackageLeadingPrefixes(), |
| bootClassPathDigest, |
| options.inputJars); |
| logger.atInfo().log( |
| "<shadowed size=%d>\n%s\n</shadowed>", shadowedTypes.size(), shadowedTypes); |
| } |
| } |
| |
| ClassMemberRetargetConfig classMemberRetargetConfig = |
| ClassMemberRetargetConfig.builder() |
| .addInvocationReplacementConfigUrl(ClassMemberRetargetConfig.DEFAULT_PROTO_URL) |
| .addAllEnabledInvocationReplacementRange(enabledInvocationReplacementRanges) |
| .build(); |
| |
| try (Closer closer = Closer.create()) { |
| IndexedInputs indexedBootclasspath = |
| new IndexedInputs(toRegisteredInputFileProvider(closer, options.bootclasspath)); |
| // 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(indexedBootclasspath, rewriter, new ThrowingClassLoader()); |
| IndexedInputs indexedClasspath = |
| new IndexedInputs(toRegisteredInputFileProvider(closer, options.classpath)); |
| // Process each input separately |
| for (InputOutputPair inputOutputPair : toInputOutputPairs(options)) { |
| desugarOneInput( |
| inputOutputPair, |
| indexedClasspath, |
| bootclassloader, |
| new ClassReaderFactory(indexedBootclasspath, rewriter), |
| bootClassPathDigest, |
| classMemberRetargetConfig); |
| } |
| } |
| } |
| |
| private void desugarOneInput( |
| InputOutputPair inputOutputPair, |
| IndexedInputs indexedClasspath, |
| ClassLoader bootclassloader, |
| ClassReaderFactory bootclasspathReader, |
| BootClassPathDigest bootClassPathDigest, |
| ClassMemberRetargetConfig classMemberRetargetConfig) |
| throws Exception { |
| Path inputPath = inputOutputPair.getInput(); // the jar |
| Path outputPath = inputOutputPair.getOutput(); |
| checkArgument( |
| Files.isDirectory(inputPath) || !Files.isDirectory(outputPath), |
| "Input jar file requires an output jar file"); |
| |
| 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 |
| // 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 classpathReader = null; |
| ClassReaderFactory bridgeMethodReader = null; |
| if (outputJava7) { |
| classpathReader = new ClassReaderFactory(indexedClasspathAndInputFiles, rewriter); |
| if (options.copyBridgesFromClasspath) { |
| bridgeMethodReader = classpathReader; |
| } else { |
| bridgeMethodReader = new ClassReaderFactory(indexedInputFiles, rewriter); |
| } |
| } |
| |
| ImmutableSet.Builder<String> interfaceLambdaMethodCollector = ImmutableSet.builder(); |
| ClassVsInterface interfaceCache = new ClassVsInterface(classpathReader); |
| final CoreLibrarySupport coreLibrarySupport = |
| options.desugarCoreLibs |
| ? new CoreLibrarySupport( |
| rewriter, |
| loader, |
| options.rewriteCoreLibraryPrefixes, |
| options.emulateCoreLibraryInterfaces, |
| options.dontTouchCoreLibraryMembers, |
| classMemberRetargetConfig) |
| : null; |
| |
| InvocationSiteTransformationRecordBuilder callSiteTransCollector = |
| InvocationSiteTransformationRecord.builder(); |
| ImmutableSet.Builder<ClassName> requiredRuntimeSupportTypes = ImmutableSet.builder(); |
| |
| InputPreAnalyzer inputPreAnalyzer = |
| new InputPreAnalyzer(inputFiles.toInputFileStreams(), customAttributes); |
| |
| inputPreAnalyzer.process(); |
| ClassAttributeRecord classAttributeRecord = inputPreAnalyzer.getClassAttributeRecord(); |
| ClassMemberRecord classMemberRecord = inputPreAnalyzer.getClassMemberRecord(); |
| |
| // Apply core library type name remapping to the digest instance produced by the nest |
| // analyzer, since the analysis-oriented nest analyzer visits core library classes without |
| // name remapping as those transformation-oriented visitors. |
| NestDigest nestDigest = |
| NestAnalyzer.digest(classAttributeRecord, classMemberRecord) |
| .acceptTypeMapper(rewriter.getPrefixer()); |
| |
| desugarClassesInInput( |
| inputFiles, |
| outputFileProvider, |
| loader, |
| classpathReader, |
| depsCollector, |
| bootclasspathReader, |
| coreLibrarySupport, |
| interfaceCache, |
| interfaceLambdaMethodCollector, |
| callSiteTransCollector, |
| bootClassPathDigest, |
| classAttributeRecord, |
| nestDigest, |
| requiredRuntimeSupportTypes, |
| classMemberRetargetConfig); |
| |
| desugarAndWriteDumpedLambdaClassesToOutput( |
| outputFileProvider, |
| loader, |
| classpathReader, |
| depsCollector, |
| bootclasspathReader, |
| coreLibrarySupport, |
| interfaceCache, |
| interfaceLambdaMethodCollector.build(), |
| bridgeMethodReader, |
| callSiteTransCollector, |
| bootClassPathDigest, |
| classAttributeRecord, |
| requiredRuntimeSupportTypes, |
| classMemberRetargetConfig); |
| |
| desugarAndWriteGeneratedClasses( |
| outputFileProvider, |
| loader, |
| classpathReader, |
| depsCollector, |
| bootclasspathReader, |
| coreLibrarySupport, |
| callSiteTransCollector, |
| bootClassPathDigest, |
| classAttributeRecord, |
| requiredRuntimeSupportTypes, |
| classMemberRetargetConfig); |
| |
| copyRuntimeClasses( |
| outputFileProvider, coreLibrarySupport, requiredRuntimeSupportTypes.build()); |
| |
| ShadowedApiAdaptersGenerator adaptersGenerator = |
| ShadowedApiAdaptersGenerator.create(callSiteTransCollector.build()); |
| adaptersGenerator.getApiAdapters().sink(outputFileProvider); |
| copyTypeConverterClasses( |
| outputFileProvider, ImmutableSet.copyOf(adaptersGenerator.getTypeConverters())); |
| |
| byte[] depsInfo = depsCollector.toByteArray(); |
| if (depsInfo != null) { |
| outputFileProvider.write(OutputFileProvider.DESUGAR_DEPS_FILENAME, depsInfo); |
| } |
| } |
| |
| ImmutableMap<Path, LambdaInfo> lambdasLeftBehind = lambdas.drain(); |
| checkState(lambdasLeftBehind.isEmpty(), "Didn't process %s", lambdasLeftBehind); |
| ImmutableMap<String, ClassNode> generatedLeftBehind = store.drain(); |
| checkState(generatedLeftBehind.isEmpty(), "Didn't process %s", generatedLeftBehind.keySet()); |
| } |
| |
| private static void copyTypeConverterClasses( |
| OutputFileProvider outputFileProvider, ImmutableSet<ClassName> initialTypes) { |
| FileBasedTypeReferenceClosure typeReferenceClosure = |
| new FileBasedTypeReferenceClosure( |
| ClassName::isInDesugarRuntimeLibrary, resourceBasedClassFiles); |
| typeReferenceClosure.findReachableReferencedTypes(initialTypes).stream() |
| .map(resourceBasedClassFiles::getContent) |
| .forEach(fileContent -> fileContent.sink(outputFileProvider)); |
| } |
| |
| /** |
| * 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. |
| */ |
| private DependencyCollector createDepsCollector() { |
| if (options.emitDependencyMetadata) { |
| try { |
| return (DependencyCollector) |
| Thread.currentThread() |
| .getContextClassLoader() |
| .loadClass( |
| "com.google.devtools.build.android.desugar.dependencies.MetadataCollector") |
| .getConstructor(Boolean.TYPE) |
| .newInstance(options.tolerateMissingDependencies); |
| } catch (ReflectiveOperationException | SecurityException e) { |
| throw new IllegalStateException("Can't emit desugaring metadata as requested", e); |
| } |
| } else if (options.tolerateMissingDependencies) { |
| return DependencyCollector.NoWriteCollectors.NOOP; |
| } else { |
| return DependencyCollector.NoWriteCollectors.FAIL_ON_MISSING; |
| } |
| } |
| |
| private void copyRuntimeClasses( |
| OutputFileProvider outputFileProvider, |
| @Nullable CoreLibrarySupport coreLibrarySupport, |
| ImmutableSet<ClassName> requiredRuntimeSupportTypes) { |
| // 1. Copy any runtime classes needed due to core library desugaring. |
| if (coreLibrarySupport != null) { |
| coreLibrarySupport.usedRuntimeHelpers().stream() |
| .filter(className -> className.startsWith(RUNTIME_LIB_PACKAGE)) |
| .distinct() |
| .forEach( |
| className -> { |
| // We want core libraries to remain self-contained, so fail if we get here. |
| checkState(!options.coreLibrary, "Core library shouldn't depend on %s", className); |
| try (InputStream stream = |
| Desugar.class.getClassLoader().getResourceAsStream(className + ".class")) { |
| outputFileProvider.write( |
| className + ".class", |
| ByteStreams.toByteArray( |
| checkNotNull(stream, "Resource Not Found for %s.", className))); |
| } catch (IOException e) { |
| throw new IOError(e); |
| } |
| }); |
| } |
| |
| // 2. Write required types in runtime library to output. |
| requiredRuntimeSupportTypes.forEach( |
| type -> resourceBasedClassFiles.getContent(type).sink(outputFileProvider)); |
| |
| // 3. See if we need to copy StringConcats methods for Indify string desugaring. |
| if (!options.coreLibrary |
| && classMemberUseCounter.getMemberUseCount(INVOKE_JDK11_STRING_CONCAT) > 0) { |
| String resourceName = "com/google/devtools/build/android/desugar/runtime/StringConcats.class"; |
| try (InputStream stream = Resources.getResource(resourceName).openStream()) { |
| outputFileProvider.write(resourceName, ByteStreams.toByteArray(stream)); |
| } catch (IOException e) { |
| throw new IOError(e); |
| } |
| } |
| |
| // 4. See if we need to copy try-with-resources runtime library |
| if (allowTryWithResources || options.desugarTryWithResourcesOmitRuntimeClasses) { |
| // try-with-resources statements are okay in the output jar. |
| return; |
| } |
| if (numOfTryWithResourcesInvoked.get() <= 0) { |
| // the try-with-resources desugaring pass does nothing, so no need to copy these class files. |
| return; |
| } |
| for (String className : |
| TryWithResourcesRewriter.THROWABLE_EXT_CLASS_INTERNAL_NAMES_WITH_CLASS_EXT) { |
| try (InputStream stream = Desugar.class.getClassLoader().getResourceAsStream(className)) { |
| outputFileProvider.write(className, ByteStreams.toByteArray(stream)); |
| } catch (IOException e) { |
| throw new IOError(e); |
| } |
| } |
| } |
| |
| /** Desugar the classes that are in the inputs specified in the command line arguments. */ |
| private void desugarClassesInInput( |
| InputFileProvider inputFiles, |
| OutputFileProvider outputFileProvider, |
| ClassLoader loader, |
| @Nullable ClassReaderFactory classpathReader, |
| DependencyCollector depsCollector, |
| ClassReaderFactory bootclasspathReader, |
| @Nullable CoreLibrarySupport coreLibrarySupport, |
| ClassVsInterface interfaceCache, |
| ImmutableSet.Builder<String> interfaceLambdaMethodCollector, |
| InvocationSiteTransformationRecordBuilder callSiteRecord, |
| BootClassPathDigest bootClassPathDigest, |
| ClassAttributeRecord classAttributeRecord, |
| NestDigest nestDigest, |
| ImmutableSet.Builder<ClassName> requiredRuntimeSupportTypes, |
| ClassMemberRetargetConfig classMemberRetargetConfig) |
| throws IOException { |
| |
| for (FileContentProvider<? extends InputStream> inputFileProvider : |
| Iterables.concat(inputFiles.toInputFileStreams(), nestDigest.getCompanionFileProviders())) { |
| String inputFilename = inputFileProvider.getBinaryPathName(); |
| if ("module-info.class".equals(inputFilename) |
| || inputFilename.startsWith("META-INF/versions/")) { |
| continue; // drop module-info.class and META-INF/versions/ since d8 will drop them anyway |
| } |
| 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 = inputFileProvider.get()) { |
| // 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. We also simply |
| // copy classes from Desugar's runtime library, which we build so they need no desugaring. |
| // The runtime library typically uses constructs we'd otherwise desugar, so it's easier |
| // to just skip it should it appear as a regular input (for idempotency). |
| if (inputFilename.endsWith(".class") |
| && ClassName.fromClassFileName(inputFilename).isDesugarEligible()) { |
| ClassReader reader = rewriter.reader(content); |
| UnprefixingClassWriter writer = rewriter.writer(ClassWriter.COMPUTE_MAXS); |
| ClassVisitor visitor = |
| createClassVisitorsForClassesInInputs( |
| loader, |
| classpathReader, |
| depsCollector, |
| bootclasspathReader, |
| coreLibrarySupport, |
| interfaceCache, |
| interfaceLambdaMethodCollector, |
| writer, |
| reader, |
| nestDigest, |
| callSiteRecord, |
| bootClassPathDigest, |
| classAttributeRecord, |
| requiredRuntimeSupportTypes, |
| classMemberRetargetConfig); |
| if (writer == visitor) { |
| // Just copy the input if there are no rewritings |
| outputFileProvider.write(inputFilename, reader.b); |
| } else { |
| reader.accept(visitor, customAttributes, ClassReader.EXPAND_FRAMES); |
| String filename = writer.getClassName() + ".class"; |
| checkState( |
| (options.coreLibrary && coreLibrarySupport != null) |
| || filename.equals(inputFilename)); |
| outputFileProvider.write(filename, writer.toByteArray()); |
| } |
| } else { |
| // Most other files (and directories) we want to just copy, but... |
| String outputFilename = inputFilename; |
| if (options.coreLibrary && coreLibrarySupport != null && inputFilename.endsWith("/")) { |
| // rename core library directories together with files in them |
| outputFilename = coreLibrarySupport.renameCoreLibrary(inputFilename); |
| } else if (coreLibrarySupport != null |
| && !inputFilename.endsWith("/") |
| && inputFilename.startsWith("META-INF/services/")) { |
| // rename j.u.ServiceLoader files for renamed core libraries so they're found |
| String serviceName = inputFilename.substring("META-INF/services/".length()); |
| if (!serviceName.contains("/") |
| && coreLibrarySupport.isRenamedCoreLibrary(serviceName.replace('.', '/'))) { |
| outputFilename = |
| "META-INF/services/" |
| + coreLibrarySupport |
| .renameCoreLibrary(serviceName.replace('.', '/')) |
| .replace('/', '.'); |
| } |
| } |
| outputFileProvider.copyFrom(inputFilename, inputFiles, outputFilename); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Desugar the classes that are generated on the fly when we are desugaring the classes in the |
| * specified inputs. |
| */ |
| private void desugarAndWriteDumpedLambdaClassesToOutput( |
| OutputFileProvider outputFileProvider, |
| ClassLoader loader, |
| @Nullable ClassReaderFactory classpathReader, |
| DependencyCollector depsCollector, |
| ClassReaderFactory bootclasspathReader, |
| @Nullable CoreLibrarySupport coreLibrarySupport, |
| ClassVsInterface interfaceCache, |
| ImmutableSet<String> interfaceLambdaMethods, |
| @Nullable ClassReaderFactory bridgeMethodReader, |
| InvocationSiteTransformationRecordBuilder callSiteTransCollector, |
| BootClassPathDigest bootClassPathDigest, |
| ClassAttributeRecord classAttributeRecord, |
| ImmutableSet.Builder<ClassName> requiredRuntimeSupportTypes, |
| ClassMemberRetargetConfig classMemberRetargetConfig) |
| throws IOException { |
| 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(lambdaClass.getKey())) { |
| ClassReader reader = rewriter.reader(bytecode); |
| InvokeDynamicLambdaMethodCollector collector = new InvokeDynamicLambdaMethodCollector(); |
| reader.accept( |
| collector, customAttributes, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); |
| ImmutableSet<MethodInfo> lambdaMethods = collector.getLambdaMethodsUsedInInvokeDynamics(); |
| checkState( |
| lambdaMethods.isEmpty(), |
| "Didn't expect to find lambda methods but found %s", |
| lambdaMethods); |
| UnprefixingClassWriter writer = |
| rewriter.writer(ClassWriter.COMPUTE_MAXS /*for invoking bridges*/); |
| ClassVisitor visitor = |
| createClassVisitorsForDumpedLambdaClasses( |
| loader, |
| classpathReader, |
| depsCollector, |
| bootclasspathReader, |
| coreLibrarySupport, |
| interfaceCache, |
| interfaceLambdaMethods, |
| bridgeMethodReader, |
| lambdaClass.getValue(), |
| writer, |
| reader, |
| callSiteTransCollector, |
| bootClassPathDigest, |
| classAttributeRecord, |
| requiredRuntimeSupportTypes, |
| classMemberRetargetConfig); |
| reader.accept(visitor, customAttributes, ClassReader.EXPAND_FRAMES); |
| 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, |
| ClassLoader loader, |
| @Nullable ClassReaderFactory classpathReader, |
| DependencyCollector depsCollector, |
| ClassReaderFactory bootclasspathReader, |
| @Nullable CoreLibrarySupport coreLibrarySupport, |
| InvocationSiteTransformationRecordBuilder callSiteTransCollector, |
| BootClassPathDigest bootClassPathDigest, |
| ClassAttributeRecord classAttributeRecord, |
| ImmutableSet.Builder<ClassName> requiredRuntimeSupportTypes, |
| ClassMemberRetargetConfig classMemberRetargetConfig) |
| 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), |
| "Didn't expect generated classes but got %s", |
| generatedClasses.keySet()); |
| for (Map.Entry<String, ClassNode> generated : generatedClasses.entrySet()) { |
| 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 = writer; |
| visitor = |
| createTypeBasedClassVisitorsForClassesInInputs( |
| loader, |
| coreLibrarySupport, |
| visitor, |
| callSiteTransCollector, |
| bootClassPathDigest, |
| classAttributeRecord, |
| closeResourceMethodScanner -> generated.getValue().accept(closeResourceMethodScanner), |
| requiredRuntimeSupportTypes, |
| classMemberRetargetConfig); |
| |
| visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null, bootclasspathReader); |
| if (options.generateBaseClassesForDefaultMethods) { |
| // Use DefaultMethodClassFixer to make generated base classes extend other base classes if |
| // possible and add any stubs from extended interfaces |
| visitor = |
| new DefaultMethodClassFixer( |
| visitor, |
| /*useGeneratedBaseClasses=*/ true, |
| classpathReader, |
| depsCollector, |
| coreLibrarySupport, |
| bootclasspathReader, |
| loader); |
| } |
| generated.getValue().accept(visitor); |
| checkState( |
| (options.coreLibrary && coreLibrarySupport != null) |
| || rewriter.unprefix(generated.getKey()).equals(writer.getClassName())); |
| outputFileProvider.write(writer.getClassName() + ".class", writer.toByteArray()); |
| } |
| } |
| |
| /** |
| * Create the class visitors for the lambda classes that are generated on the fly. If no new class |
| * visitors are not generated, then the passed-in {@code writer} will be returned. |
| */ |
| private ClassVisitor createClassVisitorsForDumpedLambdaClasses( |
| ClassLoader loader, |
| @Nullable ClassReaderFactory classpathReader, |
| DependencyCollector depsCollector, |
| ClassReaderFactory bootclasspathReader, |
| @Nullable CoreLibrarySupport coreLibrarySupport, |
| ClassVsInterface interfaceCache, |
| ImmutableSet<String> interfaceLambdaMethods, |
| @Nullable ClassReaderFactory bridgeMethodReader, |
| LambdaInfo lambdaClass, |
| UnprefixingClassWriter writer, |
| ClassReader input, |
| InvocationSiteTransformationRecordBuilder callSiteRecord, |
| BootClassPathDigest bootClassPathDigest, |
| ClassAttributeRecord classAttributeRecord, |
| ImmutableSet.Builder<ClassName> requiredRuntimeSupportTypes, |
| ClassMemberRetargetConfig classMemberRetargetConfig) { |
| ClassVisitor visitor = checkNotNull(writer); |
| |
| visitor = |
| createTypeBasedClassVisitorsForClassesInInputs( |
| loader, |
| coreLibrarySupport, |
| visitor, |
| callSiteRecord, |
| bootClassPathDigest, |
| classAttributeRecord, |
| closeResourceMethodScanner -> |
| input.accept(closeResourceMethodScanner, customAttributes, ClassReader.SKIP_DEBUG), |
| requiredRuntimeSupportTypes, |
| classMemberRetargetConfig); |
| |
| if (outputJava7) { |
| // null ClassReaderFactory b/c we don't expect to need it for lambda classes |
| visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null, bootclasspathReader); |
| if (options.desugarInterfaceMethodBodiesIfNeeded) { |
| visitor = |
| new DefaultMethodClassFixer( |
| visitor, |
| options.generateBaseClassesForDefaultMethods, |
| classpathReader, |
| depsCollector, |
| coreLibrarySupport, |
| bootclasspathReader, |
| loader); |
| visitor = |
| new InterfaceDesugaring( |
| visitor, |
| options.generateBaseClassesForDefaultMethods, |
| interfaceCache, |
| depsCollector, |
| coreLibrarySupport, |
| bootclasspathReader, |
| loader, |
| store, |
| options.legacyJacocoFix); |
| } |
| } |
| |
| visitor = |
| new LambdaClassFixer( |
| visitor, |
| lambdaClass, |
| bridgeMethodReader, |
| loader, |
| interfaceLambdaMethods, |
| allowDefaultMethods, |
| outputJava7); |
| // 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, |
| ImmutableSet.of(), |
| classAttributeRecord, |
| allowDefaultMethods); |
| return visitor; |
| } |
| |
| /** |
| * Create the class visitors for the classes which are in the inputs. If new visitors are created, |
| * then all these visitors and the passed-in writer will be chained together. If no new visitor is |
| * created, then the passed-in {@code writer} will be returned. |
| */ |
| private ClassVisitor createClassVisitorsForClassesInInputs( |
| ClassLoader loader, |
| @Nullable ClassReaderFactory classpathReader, |
| DependencyCollector depsCollector, |
| ClassReaderFactory bootclasspathReader, |
| @Nullable CoreLibrarySupport coreLibrarySupport, |
| ClassVsInterface interfaceCache, |
| ImmutableSet.Builder<String> interfaceLambdaMethodCollector, |
| UnprefixingClassWriter writer, |
| ClassReader input, |
| NestDigest nestDigest, |
| InvocationSiteTransformationRecordBuilder callSiteRecord, |
| BootClassPathDigest bootClassPathDigest, |
| ClassAttributeRecord classAttributeRecord, |
| ImmutableSet.Builder<ClassName> requiredRuntimeSupportTypes, |
| ClassMemberRetargetConfig classMemberRetargetConfig) { |
| ClassVisitor visitor = checkNotNull(writer); |
| |
| visitor = |
| createTypeBasedClassVisitorsForClassesInInputs( |
| loader, |
| coreLibrarySupport, |
| visitor, |
| callSiteRecord, |
| bootClassPathDigest, |
| classAttributeRecord, |
| closeResourceMethodScanner -> |
| input.accept(closeResourceMethodScanner, customAttributes, ClassReader.SKIP_DEBUG), |
| requiredRuntimeSupportTypes, |
| classMemberRetargetConfig); |
| |
| if (!options.onlyDesugarJavac9ForLint) { |
| if (outputJava7) { |
| visitor = new Java7Compatibility(visitor, classpathReader, bootclasspathReader); |
| if (options.desugarInterfaceMethodBodiesIfNeeded) { |
| visitor = |
| new DefaultMethodClassFixer( |
| visitor, |
| options.generateBaseClassesForDefaultMethods, |
| classpathReader, |
| depsCollector, |
| coreLibrarySupport, |
| bootclasspathReader, |
| loader); |
| visitor = |
| new InterfaceDesugaring( |
| visitor, |
| options.generateBaseClassesForDefaultMethods, |
| 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. |
| InvokeDynamicLambdaMethodCollector collector = new InvokeDynamicLambdaMethodCollector(); |
| input.accept(collector, customAttributes, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); |
| ImmutableSet<MethodInfo> methodsUsedInInvokeDynamics = |
| collector.getLambdaMethodsUsedInInvokeDynamics(); |
| if (!methodsUsedInInvokeDynamics.isEmpty() || collector.needOuterClassRewrite()) { |
| visitor = |
| new LambdaDesugaring( |
| visitor, |
| loader, |
| lambdas, |
| interfaceLambdaMethodCollector, |
| methodsUsedInInvokeDynamics, |
| classAttributeRecord, |
| allowDefaultMethods); |
| } |
| } |
| |
| if (options.desugarNestBasedPrivateAccess) { |
| visitor = new NestDesugaring(visitor, nestDigest); |
| } |
| |
| if (options.desugarIndifyStringConcat) { |
| visitor = new IndyStringConcatDesugaring(classMemberUseCounter, visitor, options.coreLibrary); |
| } |
| |
| visitor = new LocalTypeAnnotationUse(visitor); |
| |
| return visitor; |
| } |
| |
| /** |
| * Create a series of class visitors which support types in later JDK core libraries on early Java |
| * platforms. |
| */ |
| private ClassVisitor createTypeBasedClassVisitorsForClassesInInputs( |
| ClassLoader loader, |
| @Nullable CoreLibrarySupport coreLibrarySupport, |
| ClassVisitor baseClassVisitor, |
| InvocationSiteTransformationRecordBuilder callSiteRecord, |
| BootClassPathDigest bootClassPathDigest, |
| ClassAttributeRecord classAttributeRecord, |
| Consumer<CloseResourceMethodScanner> closeResourceMethodScannerConsumer, |
| ImmutableSet.Builder<ClassName> requiredRuntimeSupportTypes, |
| ClassMemberRetargetConfig classMemberRetargetConfig) { |
| ClassVisitor visitor = baseClassVisitor; |
| |
| if (coreLibrarySupport != null) { |
| visitor = new ImmutableLabelRemover(visitor); |
| visitor = new EmulatedInterfaceRewriter(visitor, coreLibrarySupport); |
| visitor = new CorePackageRenamer(visitor, coreLibrarySupport); |
| visitor = new CoreLibraryInvocationRewriter(visitor, coreLibrarySupport); |
| if (options.autoDesugarShadowedApiUse) { |
| visitor = |
| new ShadowedApiInvocationSite( |
| visitor, callSiteRecord, bootClassPathDigest, classAttributeRecord, typeHierarchy); |
| } |
| } |
| |
| if (!allowTryWithResources) { |
| CloseResourceMethodScanner closeResourceMethodScanner = new CloseResourceMethodScanner(); |
| closeResourceMethodScannerConsumer.accept(closeResourceMethodScanner); |
| visitor = |
| new TryWithResourcesRewriter( |
| visitor, |
| loader, |
| visitedExceptionTypes, |
| numOfTryWithResourcesInvoked, |
| closeResourceMethodScanner.hasCloseResourceMethod()); |
| } |
| |
| if (!allowCallsToObjectsNonNull) { |
| visitor = new ObjectsRequireNonNullMethodRewriter(visitor, rewriter); |
| } |
| |
| if (!allowCallsToLongCompare) { |
| visitor = new LongCompareMethodRewriter(visitor, rewriter); |
| } |
| |
| visitor = |
| new ClassMemberRetargetRewriter( |
| visitor, classMemberRetargetConfig, requiredRuntimeSupportTypes); |
| |
| visitor = NioBufferRefConverter.create(visitor, rewriter.getPrefixer()); |
| |
| return visitor; |
| } |
| |
| public static void main(String[] args) throws Exception { |
| verifyLambdaDumpDirectoryRegistered(DUMP_DIRECTORY); |
| |
| // In persistent worker mode, Bazel sends the list of arguments |
| // both over argv to the Java process, and over stdin as a serialized |
| // proto. We check that it's running in the persistent worker mode |
| // by checking the first arg of the list, and parse it into DesugarOptions |
| // from the proto in runPersistentWorker later. |
| if (args.length > 0 && "--persistent_worker".equals(args[0])) { |
| runPersistentWorker(DUMP_DIRECTORY); |
| } else { |
| // If not, parse it regularly from argv. |
| DesugarOptions options = DesugarOptions.parseCommandLineOptions(args); |
| processRequest(options, DUMP_DIRECTORY); |
| } |
| } |
| |
| private static void runPersistentWorker(Path dumpDirectory) throws Exception { |
| while (true) { |
| WorkRequest request = WorkRequest.parseDelimitedFrom(System.in); |
| if (request == null) { |
| break; |
| } |
| |
| String[] argList = new String[request.getArgumentsCount()]; |
| argList = request.getArgumentsList().toArray(argList); |
| |
| DesugarOptions options = DesugarOptions.parseCommandLineOptions(argList); |
| |
| WorkResponse wr; |
| try { |
| processRequest(options, dumpDirectory); |
| wr = WorkResponse.newBuilder().setExitCode(0).build(); |
| logger.atInfo().log("Processing Request success: %s", wr); |
| } catch (Exception e) { |
| wr = |
| WorkResponse.newBuilder() |
| .setExitCode(1) |
| .setOutput(Throwables.getStackTraceAsString(e)) |
| .build(); |
| logger.atWarning().withCause(e).log("Processing Request exception: %s", wr); |
| } |
| |
| // We are in persistent worker mode, so send the persistent worker response back to Bazel |
| // through stdout. Without this, Bazel will timeout while waiting for the worker's response. |
| wr.writeDelimitedTo(System.out); |
| } |
| } |
| |
| private static void processRequest(DesugarOptions options, Path dumpDirectory) throws Exception { |
| checkArgument(!options.inputJars.isEmpty(), "--input is required"); |
| checkArgument( |
| options.inputJars.size() == options.outputJars.size(), |
| "Desugar requires the same number of inputs and outputs to pair them. #input=%s,#output=%s", |
| options.inputJars.size(), |
| options.outputJars.size()); |
| checkArgument( |
| !options.bootclasspath.isEmpty() || options.allowEmptyBootclasspath, |
| "At least one --bootclasspath_entry is required"); |
| 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"); |
| |
| if (options.verbose) { |
| logger.atInfo().log("Lambda classes will be written under %s%n", dumpDirectory); |
| } |
| new Desugar(options, dumpDirectory).desugar(); |
| } |
| |
| static void verifyLambdaDumpDirectoryRegistered(Path dumpDirectory) throws IOException { |
| try { |
| Class<?> klass = Class.forName("java.lang.invoke.InnerClassLambdaMetafactory"); |
| Field dumperField = klass.getDeclaredField("dumper"); |
| dumperField.setAccessible(true); |
| Object dumperValue = dumperField.get(null); |
| checkNotNull(dumperValue, "Failed to register lambda dump directory '%s'", dumpDirectory); |
| |
| Field dumperPathField = dumperValue.getClass().getDeclaredField("dumpDir"); |
| dumperPathField.setAccessible(true); |
| Object dumperPath = dumperPathField.get(dumperValue); |
| checkState( |
| dumperPath instanceof Path && Files.isSameFile(dumpDirectory, (Path) dumperPath), |
| "Inconsistent lambda dump directories. real='%s', expected='%s'", |
| dumperPath, |
| dumpDirectory); |
| } 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. |
| logger.atWarning().withCause(e).log( |
| "Failed to verify lambda dump directory due to unavailable class/field access. \n" |
| + "Continue desugaring..."); |
| } |
| } |
| |
| /** |
| * LambdaClassMaker generates lambda classes for us, but it does so by essentially simulating the |
| * call to LambdaMetafactory that the JVM would make when encountering an invokedynamic. |
| * LambdaMetafactory is in the JDK and its implementation has a property to write out ("dump") |
| * generated classes, which we take advantage of here. This property can be set externally, and in |
| * that case the specified directory is used as a temporary dir. Otherwise, it will be set here, |
| * before doing anything else since the property is read in the static initializer. |
| */ |
| static Path createAndRegisterLambdaDumpDirectory() { |
| String propertyValue = System.getProperty(LAMBDA_METAFACTORY_DUMPER_PROPERTY); |
| if (propertyValue != null) { |
| Path path = Paths.get(propertyValue); |
| checkState(Files.isDirectory(path), "The path '%s' is not a directory.", path); |
| // It is not necessary to check whether 'path' is an empty directory. It is possible that |
| // LambdaMetafactory is loaded before this class, and there are already lambda classes dumped |
| // into the 'path' folder. |
| // TODO(kmb): Maybe we can empty the folder here. |
| return path; |
| } |
| |
| Path dumpDirectory; |
| try { |
| dumpDirectory = Files.createTempDirectory("lambdas"); |
| } catch (IOException e) { |
| throw new IOError(e); |
| } |
| System.setProperty(LAMBDA_METAFACTORY_DUMPER_PROPERTY, dumpDirectory.toString()); |
| deleteTreeOnExit(dumpDirectory); |
| return dumpDirectory; |
| } |
| |
| private static ImmutableList<InputOutputPair> toInputOutputPairs(DesugarOptions 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 deleteTreeOnExit(final Path directory) { |
| Thread shutdownHook = |
| new Thread() { |
| @Override |
| public void run() { |
| try { |
| deleteTree(directory); |
| } catch (IOException e) { |
| throw new RuntimeException("Failed to delete " + directory, e); |
| } |
| } |
| }; |
| Runtime.getRuntime().addShutdownHook(shutdownHook); |
| } |
| |
| /** Recursively delete a directory. */ |
| private static void deleteTree(final Path directory) throws IOException { |
| if (directory.toFile().exists()) { |
| Files.walkFileTree( |
| directory, |
| new SimpleFileVisitor<Path>() { |
| @Override |
| public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) |
| throws IOException { |
| Files.delete(file); |
| return FileVisitResult.CONTINUE; |
| } |
| |
| @Override |
| public FileVisitResult postVisitDirectory(Path dir, IOException exc) |
| throws IOException { |
| Files.delete(dir); |
| return FileVisitResult.CONTINUE; |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Transform a list of Path to a list of InputFileProvider and register them with the given |
| * closer. |
| */ |
| @SuppressWarnings("MustBeClosedChecker") |
| @VisibleForTesting |
| static ImmutableList<InputFileProvider> toRegisteredInputFileProvider( |
| Closer closer, List<Path> paths) throws IOException { |
| ImmutableList.Builder<InputFileProvider> builder = new ImmutableList.Builder<>(); |
| for (Path path : paths) { |
| builder.add(closer.register(InputFileProvider.open(path))); |
| } |
| return builder.build(); |
| } |
| |
| /** 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(); |
| } |
| } |