diff options
author | kmb <kmb@google.com> | 2017-10-03 02:14:25 +0200 |
---|---|---|
committer | Klaus Aehlig <aehlig@google.com> | 2017-10-06 19:42:37 +0200 |
commit | bdb12ceeb7c23d7d2293e8006d0aa7127a91b973 (patch) | |
tree | 6b2a83baa22f5a8523fe33fb3706932e5c37d0b2 /src/tools | |
parent | 36e2a1c80c0d2bd4455c960833c27b19c0cb6586 (diff) |
add flags to desugar to emit metadata that can be used for double-checking correctness of default and static interface desugaring.
RELNOTES: none
PiperOrigin-RevId: 170779637
Diffstat (limited to 'src/tools')
9 files changed, 356 insertions, 25 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD index eacb620843..a75afb2f04 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD @@ -10,16 +10,31 @@ filegroup( ) java_library( + name = "deps_collector_api", + srcs = ["DependencyCollector.java"], + visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android:__subpackages__", + ], + deps = [ + "//third_party:jsr305", + ], +) + +java_library( name = "desugar", srcs = glob(["*.java"]), visibility = [ "//src/test/java/com/google/devtools/build/android/desugar:__pkg__", "//src/tools/android/java/com/google/devtools/build/android:__pkg__", ], - runtime_deps = ["//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:throwable_extension"], + runtime_deps = [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/dependencies", + "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:throwable_extension", + ], deps = [ + ":deps_collector_api", "//src/main/java/com/google/devtools/common/options", - "//src/main/protobuf:worker_protocol_java_proto", "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib", "//third_party:asm", "//third_party:asm-commons", @@ -42,6 +57,7 @@ java_binary( filegroup( name = "srcs", srcs = glob(["**"]) + [ + "//src/tools/android/java/com/google/devtools/build/android/desugar/dependencies:srcs", "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:srcs", ], visibility = ["//src/tools/android/java/com/google/devtools/build/android:__pkg__"], diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java b/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java index d7d46a1919..2d89e8b2b1 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/DefaultMethodClassFixer.java @@ -43,6 +43,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { private final ClassReaderFactory classpath; private final ClassReaderFactory bootclasspath; private final ClassLoader targetLoader; + private final DependencyCollector depsCollector; private final HashSet<String> instanceMethods = new HashSet<>(); private boolean isInterface; @@ -55,12 +56,14 @@ public class DefaultMethodClassFixer extends ClassVisitor { public DefaultMethodClassFixer( ClassVisitor dest, ClassReaderFactory classpath, + DependencyCollector depsCollector, ClassReaderFactory bootclasspath, ClassLoader targetLoader) { super(Opcodes.ASM5, dest); this.classpath = classpath; this.bootclasspath = bootclasspath; this.targetLoader = targetLoader; + this.depsCollector = depsCollector; } @Override @@ -310,8 +313,17 @@ public class DefaultMethodClassFixer extends ClassVisitor { */ private boolean defaultMethodsDefined(ImmutableList<String> interfaces) { for (String implemented : interfaces) { + if (bootclasspath.isKnown(implemented)) { + continue; + } ClassReader bytecode = classpath.readIfKnown(implemented); - if (bytecode != null && !bootclasspath.isKnown(implemented)) { + if (bytecode == null) { + // Interface isn't on the classpath, which indicates incomplete classpaths. Record missing + // dependency so we can check it later. If we don't check then we may get runtime failures + // or wrong behavior from default methods that should've been stubbed in. + // TODO(kmb): Print a warning so people can start fixing their deps? + depsCollector.missingImplementedInterface(internalName, implemented); + } else { // Class in classpath and bootclasspath is a bad idea but in any event, assume the // bootclasspath will take precedence like in a classloader. // We can skip code attributes as we just need to find default methods to stub. @@ -321,10 +333,6 @@ public class DefaultMethodClassFixer extends ClassVisitor { return true; } } - // Else interface isn't on the classpath, which indicates incomplete classpaths. For now - // we'll just assume the missing interfaces don't declare default methods but if they do - // we'll end up with concrete classes that don't implement an abstract method, which can - // cause runtime failures. The classpath needs to be fixed in this case. } return false; } @@ -405,6 +413,8 @@ public class DefaultMethodClassFixer extends ClassVisitor { // definitions conflict, but see stubMissingDefaultMethods() for how we deal with default // methods redefined in interfaces extending another. recordIfInstanceMethod(access, name, desc); + depsCollector.assumeCompanionClass( + internalName, InterfaceDesugaring.getCompanionClassName(interfaceName)); // Add this method to the class we're desugaring and stub in a body to call the default // implementation in the interface's companion class. ijar omits these methods when setting @@ -423,7 +433,7 @@ public class DefaultMethodClassFixer extends ClassVisitor { } stubMethod.visitMethodInsn( Opcodes.INVOKESTATIC, - interfaceName + InterfaceDesugaring.COMPANION_SUFFIX, + InterfaceDesugaring.getCompanionClassName(interfaceName), name, InterfaceDesugaring.companionDefaultMethodDescriptor(interfaceName, desc), /*itf*/ false); @@ -434,6 +444,8 @@ public class DefaultMethodClassFixer extends ClassVisitor { return null; } else if (shouldStubAsBridgeDefaultMethod(access, name, desc)) { recordIfInstanceMethod(access, name, desc); + depsCollector.assumeCompanionClass( + internalName, InterfaceDesugaring.getCompanionClassName(interfaceName)); // For bridges we just copy their bodies instead of going through the companion class. // Meanwhile, we also need to desugar the copied method bodies, so that any calls to // interface methods are correctly handled. diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/DependencyCollector.java b/src/tools/android/java/com/google/devtools/build/android/desugar/DependencyCollector.java new file mode 100644 index 0000000000..272a273598 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/DependencyCollector.java @@ -0,0 +1,98 @@ +// Copyright 2017 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 javax.annotation.Nullable; + +/** + * Interface for collecting desugaring metadata that we can use to double-check correct desugaring + * at the binary level by looking at the metadata written for all Jars on the runtime classpath + * (b/65645388). Use {@link NoWriteCollectors} for "no-op" collectors and {@link + * com.google.devtools.build.android.desugar.dependencies.MetadataCollector} for writing out + * metadata files. + */ +// TODO(kmb): There could conceivably be a "self-contained" version where we check at the end that +// we actually saw all the companion classes (in recordDefaultMethods) that we "assumed"; useful +// for one-shot runs over an entire binary. +@SuppressWarnings("unused") // default implementations consist of empty method stubs +public interface DependencyCollector { + + /** Class name suffix used for interface companion classes. */ + public String INTERFACE_COMPANION_SUFFIX = "$$CC"; + + /** + * Records that {@code origin} depends on companion class {@code target}. For the resulting + * binary to be valid, {@code target} needs to exist, which isn't the case if the corresponding + * interface is only available as a compile-time ("neverlink") dependency. + */ + default void assumeCompanionClass(String origin, String target) {} + + /** + * Records that {@code origin} transitively implements {@code target} but {@code target} isn't + * in the classpath. This can lead to wrong desugarings if {@code target} or an interface it + * extends defines default methods. + */ + default void missingImplementedInterface(String origin, String target) {} + + /** + * Records that the given interface extends the given interfaces. + * + * <p>This information is useful reference to double-check {@link #missingImplementedInterface}s + * without reading and parsing .class files, specifically if default methods are defined in + * interfaces that a missing interface transitively extends. + */ + default void recordExtendedInterfaces(String origin, String... targets) {} + + /** + * Records that the given interface has a companion class that includes the given number of + * default methods (0 if there were only static methods). This method should not be called for + * purely abstract interfaces, to allow verifying available companion classes against this. + * + * <p>This information is useful reference to double-check {@link #missingImplementedInterface}s + * without reading and parsing .class files with better precision than just looking for + * companion classes on the runtime classpath (which may only contain static methods). + */ + default void recordDefaultMethods(String origin, int count) {} + + /** + * Returns metadata to include into the desugaring output or {@code null} if none. Returning + * anything but {@code null} will cause an extra file to be written into the output, including + * an empty array. + */ + @Nullable public byte[] toByteArray(); + + /** Simple collectors that don't collect any information. */ + public enum NoWriteCollectors implements DependencyCollector { + /** Singleton instance that does nothing. */ + NOOP, + /** + * Singleton instance that does nothing besides throwing if {@link #missingImplementedInterface} + * is called. + */ + FAIL_ON_MISSING { + @Override + public void missingImplementedInterface(String origin, String target) { + throw new IllegalStateException( + String.format( + "Couldn't find interface %s on the classpath for desugaring %s", target, origin)); + } + }; + + @Override + @Nullable + public final byte[] toByteArray() { + return null; + } + } +} diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java index 31c362e67a..07702fed06 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java @@ -176,6 +176,26 @@ class Desugar { public int minSdkVersion; @Option( + name = "emit_dependency_metadata_as_needed", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Whether to emit META-INF/desugar_deps as needed for later consistency checking." + ) + public boolean emitDependencyMetadata; + + @Option( + name = "best_effort_tolerate_missing_deps", + defaultValue = "true", + category = "misc", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.UNKNOWN}, + help = "Whether to tolerate missing dependencies on the classpath in some cases. You should " + + "strive to set this flag to false." + ) + public boolean tolerateMissingDependencies; + + @Option( name = "desugar_interface_method_bodies_if_needed", defaultValue = "true", category = "misc", @@ -316,6 +336,7 @@ class Desugar { try (OutputFileProvider outputFileProvider = toOutputFileProvider(outputPath); InputFileProvider inputFiles = toInputFileProvider(inputPath)) { + DependencyCollector depsCollector = createDepsCollector(); IndexedInputs indexedInputFiles = new IndexedInputs(ImmutableList.of(inputFiles)); // Prepend classpath with input file itself so LambdaDesugaring can load classes with // lambdas. @@ -344,6 +365,7 @@ class Desugar { outputFileProvider, loader, classpathReader, + depsCollector, bootclasspathReader, interfaceLambdaMethodCollector); @@ -351,12 +373,18 @@ class Desugar { outputFileProvider, loader, classpathReader, + depsCollector, bootclasspathReader, interfaceLambdaMethodCollector.build(), bridgeMethodReader); desugarAndWriteGeneratedClasses(outputFileProvider); copyThrowableExtensionClass(outputFileProvider); + + byte[] depsInfo = depsCollector.toByteArray(); + if (depsInfo != null) { + outputFileProvider.write(OutputFileProvider.DESUGAR_DEPS_FILENAME, depsInfo); + } } ImmutableMap<Path, LambdaInfo> lambdasLeftBehind = lambdas.drain(); @@ -365,6 +393,32 @@ class Desugar { checkState(generatedLeftBehind.isEmpty(), "Didn't process %s", generatedLeftBehind.keySet()); } + /** + * 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"); + } + } else if (options.tolerateMissingDependencies) { + return DependencyCollector.NoWriteCollectors.NOOP; + } else { + return DependencyCollector.NoWriteCollectors.FAIL_ON_MISSING; + } + } + private void copyThrowableExtensionClass(OutputFileProvider outputFileProvider) { if (allowTryWithResources || options.desugarTryWithResourcesOmitRuntimeClasses) { // try-with-resources statements are okay in the output jar. @@ -390,10 +444,15 @@ class Desugar { OutputFileProvider outputFileProvider, ClassLoader loader, @Nullable ClassReaderFactory classpathReader, + DependencyCollector depsCollector, ClassReaderFactory bootclasspathReader, Builder<String> interfaceLambdaMethodCollector) throws IOException { for (String filename : inputFiles) { + if (OutputFileProvider.DESUGAR_DEPS_FILENAME.equals(filename)) { + // 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)) { // 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 @@ -405,6 +464,7 @@ class Desugar { createClassVisitorsForClassesInInputs( loader, classpathReader, + depsCollector, bootclasspathReader, interfaceLambdaMethodCollector, writer, @@ -431,6 +491,7 @@ class Desugar { OutputFileProvider outputFileProvider, ClassLoader loader, @Nullable ClassReaderFactory classpathReader, + DependencyCollector depsCollector, ClassReaderFactory bootclasspathReader, ImmutableSet<String> interfaceLambdaMethods, @Nullable ClassReaderFactory bridgeMethodReader) @@ -460,6 +521,7 @@ class Desugar { createClassVisitorsForDumpedLambdaClasses( loader, classpathReader, + depsCollector, bootclasspathReader, interfaceLambdaMethods, bridgeMethodReader, @@ -499,6 +561,7 @@ class Desugar { private ClassVisitor createClassVisitorsForDumpedLambdaClasses( ClassLoader loader, @Nullable ClassReaderFactory classpathReader, + DependencyCollector depsCollector, ClassReaderFactory bootclasspathReader, ImmutableSet<String> interfaceLambdaMethods, @Nullable ClassReaderFactory bridgeMethodReader, @@ -523,9 +586,11 @@ class Desugar { visitor = new Java7Compatibility(visitor, (ClassReaderFactory) null); if (options.desugarInterfaceMethodBodiesIfNeeded) { visitor = - new DefaultMethodClassFixer(visitor, classpathReader, bootclasspathReader, loader); + new DefaultMethodClassFixer( + visitor, classpathReader, depsCollector, bootclasspathReader, loader); visitor = - new InterfaceDesugaring(visitor, bootclasspathReader, store, options.legacyJacocoFix); + new InterfaceDesugaring( + visitor, depsCollector, bootclasspathReader, store, options.legacyJacocoFix); } } visitor = @@ -553,6 +618,7 @@ class Desugar { private ClassVisitor createClassVisitorsForClassesInInputs( ClassLoader loader, @Nullable ClassReaderFactory classpathReader, + DependencyCollector depsCollector, ClassReaderFactory bootclasspathReader, Builder<String> interfaceLambdaMethodCollector, UnprefixingClassWriter writer, @@ -574,9 +640,11 @@ class Desugar { visitor = new Java7Compatibility(visitor, classpathReader); if (options.desugarInterfaceMethodBodiesIfNeeded) { visitor = - new DefaultMethodClassFixer(visitor, classpathReader, bootclasspathReader, loader); + new DefaultMethodClassFixer( + visitor, classpathReader, depsCollector, bootclasspathReader, loader); visitor = - new InterfaceDesugaring(visitor, bootclasspathReader, store, options.legacyJacocoFix); + new InterfaceDesugaring( + visitor, depsCollector, bootclasspathReader, store, options.legacyJacocoFix); } } // LambdaDesugaring is relatively expensive, so check first whether we need it. Additionally, diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java b/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java index 6bac9edb26..0f03236e72 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/InterfaceDesugaring.java @@ -42,9 +42,9 @@ class InterfaceDesugaring extends ClassVisitor { static final String COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_NAME = "$$triggerInterfaceInit"; static final String COMPANION_METHOD_TO_TRIGGER_INTERFACE_CLINIT_DESC = "()V"; - static final String COMPANION_SUFFIX = "$$CC"; static final String INTERFACE_STATIC_COMPANION_METHOD_SUFFIX = "$$STATIC$$"; + private final DependencyCollector depsCollector; private final ClassReaderFactory bootclasspath; private final GeneratedClassStore store; private final boolean legacyJaCoCo; @@ -52,13 +52,18 @@ class InterfaceDesugaring extends ClassVisitor { private String internalName; private int bytecodeVersion; private int accessFlags; + private int numberOfDefaultMethods; @Nullable private ClassVisitor companion; @Nullable private FieldInfo interfaceFieldToAccessInCompanionMethodToTriggerInterfaceClinit; public InterfaceDesugaring( - ClassVisitor dest, ClassReaderFactory bootclasspath, GeneratedClassStore store, + ClassVisitor dest, + DependencyCollector depsCollector, + ClassReaderFactory bootclasspath, + GeneratedClassStore store, boolean legacyJaCoCo) { super(Opcodes.ASM5, dest); + this.depsCollector = depsCollector; this.bootclasspath = bootclasspath; this.store = store; this.legacyJaCoCo = legacyJaCoCo; @@ -73,15 +78,26 @@ class InterfaceDesugaring extends ClassVisitor { String superName, String[] interfaces) { companion = null; + numberOfDefaultMethods = 0; internalName = name; bytecodeVersion = version; accessFlags = access; + if (isInterface()) { + // Record interface hierarchy. This helps avoid parsing .class files when double-checking + // desugaring results later using collected dependency information. + depsCollector.recordExtendedInterfaces(name, interfaces); + } super.visit(version, access, name, signature, superName, interfaces); } @Override public void visitEnd() { if (companion != null) { + // Record classes with default methods. This increases precision when double-checking + // desugaring results later, without parsing .class files again, compared to just looking + // for companion classes in a given desugared Jar which may only contain static methods. + depsCollector.recordDefaultMethods(internalName, numberOfDefaultMethods); + // Emit a method to access the fields of the interfaces that need initialization. emitInterfaceFieldAccessInCompanionMethodToTriggerInterfaceClinit(); companion.visitEnd(); @@ -185,6 +201,7 @@ class InterfaceDesugaring extends ClassVisitor { name, internalName, desc); + ++numberOfDefaultMethods; abstractDest = super.visitMethod(access | Opcodes.ACC_ABSTRACT, name, desc, signature, exceptions); } @@ -228,7 +245,7 @@ class InterfaceDesugaring extends ClassVisitor { // Rename lambda method to reflect the new owner. Not doing so confuses LambdaDesugaring // if it's run over this class again. LambdaDesugaring has already renamed the method from // its original name to include the interface name at this point. - suffix = COMPANION_SUFFIX; + suffix = DependencyCollector.INTERFACE_COMPANION_SUFFIX; } else if (isStatic) { suffix = INTERFACE_STATIC_COMPANION_METHOD_SUFFIX; } else { @@ -238,7 +255,7 @@ class InterfaceDesugaring extends ClassVisitor { } static String getCompanionClassName(String interfaceName) { - return interfaceName + COMPANION_SUFFIX; + return interfaceName + DependencyCollector.INTERFACE_COMPANION_SUFFIX; } /** @@ -329,8 +346,8 @@ class InterfaceDesugaring extends ClassVisitor { name = normalizeInterfaceMethodName(name, isLambda, opcode == Opcodes.INVOKESTATIC); if (isLambda) { // Redirect lambda invocations to completely remove all lambda methods from interfaces. - checkArgument( - !owner.endsWith(COMPANION_SUFFIX), "shouldn't consider %s an interface", owner); + checkArgument(!owner.endsWith(DependencyCollector.INTERFACE_COMPANION_SUFFIX), + "shouldn't consider %s an interface", owner); checkArgument(!bootclasspath.isKnown(owner)); // must be in current input if (opcode == Opcodes.INVOKEINTERFACE) { opcode = Opcodes.INVOKESTATIC; @@ -344,7 +361,7 @@ class InterfaceDesugaring extends ClassVisitor { name); } // Reflect that InterfaceDesugaring moves and renames the lambda body method - owner += COMPANION_SUFFIX; + owner += DependencyCollector.INTERFACE_COMPANION_SUFFIX; String expectedLambdaMethodName = LambdaDesugaring.uniqueInPackage(owner, name); checkState( name.equals(expectedLambdaMethodName), @@ -355,14 +372,14 @@ class InterfaceDesugaring extends ClassVisitor { itf = false; } else if ((opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL) && !bootclasspath.isKnown(owner)) { - checkArgument( - !owner.endsWith(COMPANION_SUFFIX), "shouldn't consider %s an interface", owner); + checkArgument(!owner.endsWith(DependencyCollector.INTERFACE_COMPANION_SUFFIX), + "shouldn't consider %s an interface", owner); if (opcode == Opcodes.INVOKESPECIAL) { // Turn Interface.super.m() into Interface$$CC.m(receiver) opcode = Opcodes.INVOKESTATIC; desc = companionDefaultMethodDescriptor(owner, desc); } - owner += COMPANION_SUFFIX; + owner += DependencyCollector.INTERFACE_COMPANION_SUFFIX; itf = false; } } @@ -384,7 +401,8 @@ class InterfaceDesugaring extends ClassVisitor { @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { if ("$jacocoData".equals(name)) { - checkState(!owner.endsWith(COMPANION_SUFFIX), "Expected interface: %s", owner); + checkState(!owner.endsWith(DependencyCollector.INTERFACE_COMPANION_SUFFIX), + "Expected interface: %s", owner); owner = getCompanionClassName(owner); } super.visitFieldInsn(opcode, owner, name, desc); diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/OutputFileProvider.java b/src/tools/android/java/com/google/devtools/build/android/desugar/OutputFileProvider.java index bf3b710746..7a590ef2ba 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/OutputFileProvider.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/OutputFileProvider.java @@ -18,6 +18,9 @@ import java.io.IOException; /** Output file provider allows to write files in directory or jar files. */ interface OutputFileProvider extends AutoCloseable { + /** Filename to use to write out dependency metadata for later consistency checking. */ + public static final String DESUGAR_DEPS_FILENAME = "META-INF/desugar_deps"; + /** * Copy {@code filename} from {@code inputFileProvider} to this output. If input file provider is * a {@link ZipInputFileProvider} then the metadata of the zip entry are kept. diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/ZipOutputFileProvider.java b/src/tools/android/java/com/google/devtools/build/android/desugar/ZipOutputFileProvider.java index 3f4f344954..8d6501d198 100644 --- a/src/tools/android/java/com/google/devtools/build/android/desugar/ZipOutputFileProvider.java +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/ZipOutputFileProvider.java @@ -13,7 +13,8 @@ // limitations under the License. package com.google.devtools.build.android.desugar; -import com.google.common.base.Preconditions; +import static com.google.common.base.Preconditions.checkArgument; + import com.google.common.io.ByteStreams; import java.io.BufferedOutputStream; import java.io.IOException; @@ -45,7 +46,8 @@ public class ZipOutputFileProvider implements OutputFileProvider { @Override public void write(String filename, byte[] content) throws IOException { - Preconditions.checkArgument(filename.endsWith(".class")); + checkArgument(filename.equals(DESUGAR_DEPS_FILENAME) || filename.endsWith(".class"), + "Expect file to be copied: %s", filename); writeStoredEntry(out, filename, content); } diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/dependencies/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/dependencies/BUILD new file mode 100644 index 0000000000..a34488d0d4 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/dependencies/BUILD @@ -0,0 +1,26 @@ +# Description: +# Dependency tracking helper library for desugar that we package separately +# for the benefit of desugar users who want to compile the tool from source. + +package( + default_visibility = [ + "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__", + "//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__", + ], +) + +java_library( + name = "dependencies", + srcs = glob(["*.java"]), + deps = [ + "//src/main/protobuf:desugar_deps_java_proto", + "//src/tools/android/java/com/google/devtools/build/android/desugar:deps_collector_api", + "//third_party:guava", + "//third_party:jsr305", + ], +) + +filegroup( + name = "srcs", + srcs = glob(["**"]), +) diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/dependencies/MetadataCollector.java b/src/tools/android/java/com/google/devtools/build/android/desugar/dependencies/MetadataCollector.java new file mode 100644 index 0000000000..732ef50721 --- /dev/null +++ b/src/tools/android/java/com/google/devtools/build/android/desugar/dependencies/MetadataCollector.java @@ -0,0 +1,88 @@ +// Copyright 2017 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.dependencies; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; + +import com.google.devtools.build.android.desugar.DependencyCollector; +import com.google.devtools.build.android.desugar.proto.DesugarDeps; +import com.google.devtools.build.android.desugar.proto.DesugarDeps.Dependency; +import com.google.devtools.build.android.desugar.proto.DesugarDeps.DesugarDepsInfo; +import com.google.devtools.build.android.desugar.proto.DesugarDeps.InterfaceDetails; +import com.google.devtools.build.android.desugar.proto.DesugarDeps.InterfaceWithCompanion; +import com.google.devtools.build.android.desugar.proto.DesugarDeps.Type; +import javax.annotation.Nullable; + +/** Dependency collector that emits collected metadata as a {@link DesugarDepsInfo} proto. */ +public final class MetadataCollector implements DependencyCollector { + + private final boolean tolerateMissingDeps; + private final DesugarDepsInfo.Builder info = DesugarDeps.DesugarDepsInfo.newBuilder(); + + public MetadataCollector(boolean tolerateMissingDeps) { + this.tolerateMissingDeps = tolerateMissingDeps; + } + + @Override + public void assumeCompanionClass(String origin, String target) { + checkArgument(target.endsWith(INTERFACE_COMPANION_SUFFIX), + "target not a companion: %s -> %s", origin, target); + info.addAssumePresent( + Dependency.newBuilder().setOrigin(wrapType(origin)).setTarget(wrapType(target))); + } + + @Override + public void missingImplementedInterface(String origin, String target) { + checkArgument(!target.endsWith(INTERFACE_COMPANION_SUFFIX), + "target seems to be a companion: %s -> %s", origin, target); + checkState(tolerateMissingDeps, + "Couldn't find interface %s on the classpath for desugaring %s", target, origin); + info.addMissingInterface( + Dependency.newBuilder().setOrigin(wrapType(origin)).setTarget(wrapType(target))); + } + + @Override + public void recordExtendedInterfaces(String origin, String... targets) { + if (targets.length > 0) { + InterfaceDetails.Builder details = + InterfaceDetails.newBuilder().setOrigin(wrapType(origin)); + for (String target : targets) { + details.addExtendedInterface(wrapType(target)); + } + info.addInterfaceWithSupertypes(details); + } + } + + @Override + public void recordDefaultMethods(String origin, int count) { + checkArgument(!origin.endsWith(INTERFACE_COMPANION_SUFFIX), + "seems to be a companion: %s", origin); + info.addInterfaceWithCompanion( + InterfaceWithCompanion.newBuilder() + .setOrigin(wrapType(origin)) + .setNumDefaultMethods(count)); + } + + @Override + @Nullable + public byte[] toByteArray() { + DesugarDepsInfo result = info.build(); + return DesugarDepsInfo.getDefaultInstance().equals(result) ? null : result.toByteArray(); + } + + private static Type wrapType(String internalName) { + return Type.newBuilder().setBinaryName(internalName).build(); + } +}
\ No newline at end of file |