diff options
author | Googler <noreply@google.com> | 2016-09-16 22:38:35 +0000 |
---|---|---|
committer | Laszlo Csomor <laszlocsomor@google.com> | 2016-09-19 07:35:26 +0000 |
commit | a51ade354499c084b71030192d8e2f79dbfdc299 (patch) | |
tree | 163e8f30b58dae15ab5fd085d88685b727d8482a /src/main | |
parent | ae2197de3af4ebfac8d6ba073667b0948845b522 (diff) |
Java 8 support for Android builds, initially with incremental dexing only.
--
MOS_MIGRATED_REVID=133436157
Diffstat (limited to 'src/main')
4 files changed, 192 insertions, 40 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java index 0b6b467aea..ed545f87f1 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java @@ -866,7 +866,7 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { NestedSet<Artifact> libraryResourceJars = libraryResourceJarsBuilder.build(); Iterable<Artifact> filteredJars = ImmutableList.copyOf( - filter(jars, not(in(libraryResourceJars.toSet())))); + filter(jars, not(in(libraryResourceJars.toSet())))); AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); @@ -1276,11 +1276,14 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { } /** - * Returns a {@link DexArchiveProvider} of all transitively generated dex archives as well as - * dex archives for the Jars produced by the binary target itself. + * Returns a {@link DexArchiveProvider} of all transitively generated dex archives as well as dex + * archives for the Jars produced by the binary target itself. */ private static Function<Artifact, Artifact> collectDexArchives( - RuleContext ruleContext, AndroidCommon common, List<String> dexopts) { + RuleContext ruleContext, + AndroidCommon common, + List<String> dexopts, + JavaTargetAttributes attributes) { DexArchiveProvider.Builder result = new DexArchiveProvider.Builder() // Use providers from all attributes that declare DexArchiveAspect .addTransitiveProviders( @@ -1289,15 +1292,30 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { DexArchiveAspect.incrementalDexopts(ruleContext, dexopts); for (Artifact jar : common.getJarsProducedForRuntime()) { // Create dex archives next to all Jars produced by AndroidCommon for this rule. We need to - // do this (instead of placing dex archives into the _dx subdirectory like DexArchiveAspect - // does because for "legacy" ResourceApks, AndroidCommon produces Jars per resource dependency - // that can theoretically have duplicate basenames, so they go into special directories, and - // we piggyback on that naming scheme here by placing dex archives into the same directories. + // do this (instead of placing dex archives into the _dx subdirectory like DexArchiveAspect) + // because for "legacy" ResourceApks, AndroidCommon produces Jars per resource dependency that + // can theoretically have duplicate basenames, so they go into special directories, and we + // piggyback on that naming scheme here by placing dex archives into the same directories. PathFragment jarPath = jar.getRootRelativePath(); - Artifact dexArchive = ruleContext.getDerivedArtifact( - jarPath.replaceName(jarPath.getBaseName() + ".dex.zip"), - jar.getRoot()); - DexArchiveAspect.createDexArchiveAction(ruleContext, jar, dexArchive, incrementalDexopts); + Artifact jarToDex = jar; + if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8()) { + // desugar jars first if desired... + jarToDex = + DexArchiveAspect.desugar( + ruleContext, + jar, + attributes.getBootClassPath(), + attributes.getCompileTimeClassPath(), + ruleContext.getDerivedArtifact( + jarPath.replaceName(jarPath.getBaseName() + "_desugared.jar"), jar.getRoot())); + } + Artifact dexArchive = + DexArchiveAspect.createDexArchiveAction( + ruleContext, + jarToDex, + incrementalDexopts, + ruleContext.getDerivedArtifact( + jarPath.replaceName(jarPath.getBaseName() + ".dex.zip"), jar.getRoot())); result.addDexArchive(incrementalDexopts, dexArchive, jar); } return result.build().archivesForDexopts(incrementalDexopts); @@ -1355,7 +1373,7 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { // there should be very few or no Jar files that still end up in shards. The dexing // step below will have to deal with those in addition to merging .dex files together. classpath = Iterables - .transform(classpath, collectDexArchives(ruleContext, common, dexopts)); + .transform(classpath, collectDexArchives(ruleContext, common, dexopts, attributes)); shardCommandLine.add("--split_dexed_classes"); } shardCommandLine.addBeforeEachExecPath("--input_jar", classpath); diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java index c7b3650796..90cd5ca391 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java @@ -306,6 +306,16 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment { help = "Enables sanity checks for Jack and Jill compilation.") public boolean jackSanityChecks; + // For desugaring lambdas when compiling Java 8 sources. Do not use on the command line. + // The idea is that once this option works, we'll flip the default value in a config file, then + // once it is proven that it works, remove it from Bazel and said config file. + @Option(name = "experimental_desugar_for_android", + defaultValue = "false", + category = "undocumented", + implicitRequirements = "--noexperimental_android_use_jack_for_dexing", + help = "Whether to desugar Java 8 bytecode before dexing.") + public boolean desugarJava8; + @Option(name = "experimental_incremental_dexing", defaultValue = "off", category = "undocumented", @@ -467,6 +477,7 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment { private final ImmutableSet<AndroidBinaryType> incrementalDexingBinaries; private final ImmutableList<String> dexoptsSupportedInIncrementalDexing; private final ImmutableList<String> targetDexoptsThatPreventIncrementalDexing; + private final boolean desugarJava8; private final boolean allowAndroidLibraryDepsWithoutSrcs; private final boolean useAndroidResourceShrinking; private final boolean useRClassGenerator; @@ -491,6 +502,7 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment { ImmutableList.copyOf(options.dexoptsSupportedInIncrementalDexing); this.targetDexoptsThatPreventIncrementalDexing = ImmutableList.copyOf(options.nonIncrementalPerTargetDexopts); + this.desugarJava8 = options.desugarJava8; this.allowAndroidLibraryDepsWithoutSrcs = options.allowAndroidLibraryDepsWithoutSrcs; this.useAndroidResourceShrinking = options.useAndroidResourceShrinking; this.useRClassGenerator = options.useRClassGenerator; @@ -553,6 +565,10 @@ public class AndroidConfiguration extends BuildConfiguration.Fragment { return targetDexoptsThatPreventIncrementalDexing; } + public boolean desugarJava8() { + return desugarJava8; + } + public boolean allowSrcsLessAndroidLibraryDeps() { return allowAndroidLibraryDepsWithoutSrcs; } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java index 3500ebc5fe..9033b1a87a 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java @@ -629,6 +629,8 @@ public final class AndroidRuleClasses { .value(env.getToolsLabel(DEFAULT_INCREMENTAL_STUB_APPLICATION))) .add(attr("$incremental_split_stub_application", LABEL) .value(env.getToolsLabel(DEFAULT_INCREMENTAL_SPLIT_STUB_APPLICATION))) + .add(attr("$desugar", LABEL).cfg(HOST).exec() + .value(env.getToolsLabel("//tools/android:desugar_java8"))) /* <!-- #BLAZE_RULE($android_binary_base).ATTRIBUTE(dexopts) --> Additional command-line flags for the dx tool when generating classes.dex. Subject to <a href="${link make-variables}">"Make variable"</a> substitution and diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java b/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java index c805da5553..fdfe7c216a 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java @@ -23,6 +23,7 @@ import static com.google.devtools.build.lib.rules.android.AndroidCommon.getAndro import static java.nio.charset.StandardCharsets.ISO_8859_1; import com.google.common.base.Function; +import com.google.common.base.Functions; import com.google.common.base.Joiner; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; @@ -41,6 +42,7 @@ import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; import com.google.devtools.build.lib.analysis.actions.ParameterFileWriteAction; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.cmdline.Label; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.packages.AspectDefinition; import com.google.devtools.build.lib.packages.AspectParameters; import com.google.devtools.build.lib.packages.AttributeMap; @@ -50,7 +52,9 @@ import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.TriState; import com.google.devtools.build.lib.rules.java.JavaCommon; import com.google.devtools.build.lib.rules.java.JavaCompilationArgsProvider; +import com.google.devtools.build.lib.rules.java.JavaCompilationInfoProvider; import com.google.devtools.build.lib.rules.java.JavaRuntimeJarProvider; +import java.util.LinkedHashMap; import java.util.Set; import java.util.TreeSet; @@ -63,7 +67,7 @@ public final class DexArchiveAspect extends NativeAspectClass implements Configu * Function that returns a {@link Rule}'s {@code incremental_dexing} attribute for use by this * aspect. Must be provided when attaching this aspect to a rule. */ - static final Function<Rule, AspectParameters> PARAM_EXTRACTOR = + public static final Function<Rule, AspectParameters> PARAM_EXTRACTOR = new Function<Rule, AspectParameters>() { @Override public AspectParameters apply(Rule rule) { @@ -74,9 +78,13 @@ public final class DexArchiveAspect extends NativeAspectClass implements Configu return result.build(); } }; + /** Aspect-only label for dexbuidler executable, to avoid name clashes with labels on rules. */ private static final String ASPECT_DEXBUILDER_PREREQ = "$dex_archive_dexbuilder"; + /** Aspect-only label for desugaring executable, to avoid name clashes with labels on rules. */ + private static final String ASPECT_DESUGAR_PREREQ = "$aspect_desugar"; private static final ImmutableList<String> TRANSITIVE_ATTRIBUTES = ImmutableList.of("deps", "exports", "runtime_deps"); + private final String toolsRepository; public DexArchiveAspect(String toolsRepository) { @@ -88,9 +96,11 @@ public final class DexArchiveAspect extends NativeAspectClass implements Configu AspectDefinition.Builder result = new AspectDefinition.Builder(NAME) // Actually we care about JavaRuntimeJarProvider, but rules don't advertise that provider. .requireProvider(JavaCompilationArgsProvider.class) + // Parse labels since we don't have RuleDefinitionEnvironment.getLabel like in a rule .add(attr(ASPECT_DEXBUILDER_PREREQ, LABEL).cfg(HOST).exec() - // Parse label here since we don't have RuleDefinitionEnvironment.getLabel like in a rule .value(Label.parseAbsoluteUnchecked(toolsRepository + "//tools/android:dexbuilder"))) + .add(attr(ASPECT_DESUGAR_PREREQ, LABEL).cfg(HOST).exec() + .value(Label.parseAbsoluteUnchecked(toolsRepository + "//tools/android:desugar_java8"))) .requiresConfigurationFragments(AndroidConfiguration.class); for (String attr : TRANSITIVE_ATTRIBUTES) { result.attributeAspect(attr, this); @@ -121,10 +131,23 @@ public final class DexArchiveAspect extends NativeAspectClass implements Configu DexArchiveProvider.Builder result = createArchiveProviderBuilderFromDeps(ruleContext); JavaRuntimeJarProvider jarProvider = base.getProvider(JavaRuntimeJarProvider.class); if (jarProvider != null) { + Function<Artifact, Artifact> desugaredJars = + desugarJarsIfRequested(base, ruleContext, jarProvider); Set<Set<String>> aspectDexopts = aspectDexopts(ruleContext); for (Artifact jar : jarProvider.getRuntimeJars()) { for (Set<String> incrementalDexopts : aspectDexopts) { - Artifact dexArchive = createDexArchiveAction(ruleContext, jar, incrementalDexopts); + // Since we're potentially dexing the same jar multiple times with different flags, we + // need to write unique artifacts for each flag combination. Here, it is convenient to + // distinguish them by putting the flags that were used for creating the artifacts into + // their filenames. + String filename = jar.getFilename() + Joiner.on("").join(incrementalDexopts) + ".dex.zip"; + Artifact dexArchive = + createDexArchiveAction( + ruleContext, + ASPECT_DEXBUILDER_PREREQ, + desugaredJars.apply(jar), + incrementalDexopts, + AndroidBinary.getDxArtifact(ruleContext, filename)); result.addDexArchive(incrementalDexopts, dexArchive, jar); } } @@ -132,6 +155,32 @@ public final class DexArchiveAspect extends NativeAspectClass implements Configu return new ConfiguredAspect.Builder(NAME, ruleContext).addProvider(result.build()).build(); } + /** + * Runs Jars in {@code jarProvider} through desugaring action if flag is set. Note that this + * cannot happen in a separate aspect because aspects don't see providers added by other aspects + * executed on the same target. + */ + private Function<Artifact, Artifact> desugarJarsIfRequested( + ConfiguredTarget base, RuleContext ruleContext, JavaRuntimeJarProvider jarProvider) { + if (!getAndroidConfig(ruleContext).desugarJava8()) { + return Functions.identity(); + } + // These are all transitive hjars of dependencies and hjar of the jar itself + NestedSet<Artifact> compileTimeClasspath = base + .getProvider(JavaCompilationArgsProvider.class) // aspect definition requires this + .getRecursiveJavaCompilationArgs() + .getCompileTimeJars(); + // For android_* targets we need to honor their bootclasspath (nicer in general to do so) + ImmutableList<Artifact> bootclasspath = getBootclasspath(base); + LinkedHashMap<Artifact, Artifact> desugaredJars = new LinkedHashMap<>(); + for (Artifact jar : jarProvider.getRuntimeJars()) { + Artifact desugared = createDesugarAction(ruleContext, jar, bootclasspath, + compileTimeClasspath); + desugaredJars.put(jar, desugared); + } + return Functions.forMap(desugaredJars); + } + private static DexArchiveProvider.Builder createArchiveProviderBuilderFromDeps( RuleContext ruleContext) { DexArchiveProvider.Builder result = new DexArchiveProvider.Builder(); @@ -144,22 +193,85 @@ public final class DexArchiveAspect extends NativeAspectClass implements Configu return result; } - private static Artifact createDexArchiveAction(RuleContext ruleContext, Artifact jar, - Set<String> incrementalDexopts) { - // Since we're potentially dexing the same jar multiple times with different flags, we need to - // write out unique artifacts for each flag combinations. Here, it is convenient to distinguish - // them by putting the flags that were used for creating the artifacts into their filenames. - String filename = jar.getFilename() + Joiner.on("").join(incrementalDexopts) + ".dex.zip"; - Artifact result = AndroidBinary.getDxArtifact(ruleContext, filename); - // Aspect must use attribute name for dexbuilder prereq that's different from the prerequisite - // declared on AndroidBinaryBaseRule because the two prereq's can otherwise name-clash when - // android_binary targets are built as part of an android_test: building android_test causes - // the aspect to apply to the android_binary target, but android_binary itself also declares - // a $dexbuilder prerequisite, so if the aspect also used $dexbuilder then - // RuleContext.getExecutablePrerequisite would fail with "$dexbuilder produces multiple prereqs" - // (note they both resolve to the same artifact but that doesn't seem to prevent the exception - // from being thrown). - createDexArchiveAction(ruleContext, ASPECT_DEXBUILDER_PREREQ, jar, result, incrementalDexopts); + private static ImmutableList<Artifact> getBootclasspath(ConfiguredTarget base) { + JavaCompilationInfoProvider compilationInfo = + base.getProvider(JavaCompilationInfoProvider.class); + if (compilationInfo == null) { + return ImmutableList.of(); + } + return compilationInfo.getBootClasspath(); + } + + private Artifact createDesugarAction( + RuleContext ruleContext, + Artifact jar, + ImmutableList<Artifact> bootclasspath, + NestedSet<Artifact> compileTimeClasspath) { + return createDesugarAction( + ruleContext, + ASPECT_DESUGAR_PREREQ, + jar, + bootclasspath, + compileTimeClasspath, + AndroidBinary.getDxArtifact(ruleContext, jar.getFilename() + "_desugared.jar")); + } + + /** + * Desugars the given Jar using an executable prerequisite {@code "$dexbuilder"}. Rules + * calling this method must declare the appropriate prerequisite, similar to how + * {@link #getDefinition} does it for {@link DexArchiveAspect} under a different name. + * + * <p>It's useful to have this action separately since callers need to look up classpath and + * bootclasspath in a different way than this aspect does it. + * + * @return the artifact given as {@code result}, which can simplify calling code + */ + static Artifact desugar( + RuleContext ruleContext, + Artifact jar, + ImmutableList<Artifact> bootclasspath, + NestedSet<Artifact> classpath, + Artifact result) { + return createDesugarAction(ruleContext, "$desugar", jar, bootclasspath, classpath, result); + } + + private static Artifact createDesugarAction( + RuleContext ruleContext, + String desugarPrereqName, + Artifact jar, + ImmutableList<Artifact> bootclasspath, + NestedSet<Artifact> classpath, + Artifact result) { + CustomCommandLine args = new CustomCommandLine.Builder() + .addExecPath("--input", jar) + .addExecPath("--output", result) + .addBeforeEachExecPath("--classpath_entry", classpath) + .addBeforeEachExecPath("--bootclasspath_entry", bootclasspath) + .build(); + + // Just use params file, since classpaths can get long + Artifact paramFile = + ruleContext.getDerivedArtifact( + ParameterFile.derivePath(result.getRootRelativePath()), result.getRoot()); + ruleContext.registerAction( + new ParameterFileWriteAction( + ruleContext.getActionOwner(), + paramFile, + args, + ParameterFile.ParameterFileType.UNQUOTED, + ISO_8859_1)); + ruleContext.registerAction( + new SpawnAction.Builder() + .setExecutable(ruleContext.getExecutablePrerequisite(desugarPrereqName, Mode.HOST)) + .addArgument("@" + paramFile.getExecPathString()) + .addInput(jar) + .addInput(paramFile) + .addInputs(bootclasspath) + .addTransitiveInputs(classpath) + .addOutput(result) + .setMnemonic("Desugar") + .setProgressMessage("Desugaring " + jar.prettyPrint() + " for Android") + .build(ruleContext)); return result; } @@ -167,24 +279,27 @@ public final class DexArchiveAspect extends NativeAspectClass implements Configu * Creates a dex archive using an executable prerequisite called {@code "$dexbuilder"}. Rules * calling this method must declare the appropriate prerequisite, similar to how * {@link #getDefinition} does it for {@link DexArchiveAspect} under a different name. + * + * @return the artifact given as {@code result}, which can simplify calling code */ // Package-private method for use in AndroidBinary - static void createDexArchiveAction(RuleContext ruleContext, Artifact jar, Artifact dexArchive, - Set<String> tokenizedDexopts) { - createDexArchiveAction(ruleContext, "$dexbuilder", jar, dexArchive, tokenizedDexopts); + static Artifact createDexArchiveAction(RuleContext ruleContext, Artifact jar, + Set<String> tokenizedDexopts, Artifact result) { + return createDexArchiveAction(ruleContext, "$dexbuilder", jar, tokenizedDexopts, result); } /** * Creates a dexbuilder action with the given input, output, and flags. Flags must have been * filtered and normalized to a set that the dexbuilder tool can understand. */ - private static void createDexArchiveAction(RuleContext ruleContext, String dexbuilderPrereq, - Artifact jar, Artifact dexArchive, Set<String> incrementalDexopts) { + private static Artifact createDexArchiveAction(RuleContext ruleContext, String dexbuilderPrereq, + Artifact jar, Set<String> incrementalDexopts, Artifact dexArchive) { // Write command line arguments into a params file for compatibility with WorkerSpawnStrategy - CustomCommandLine.Builder args = new CustomCommandLine.Builder() + CustomCommandLine args = new CustomCommandLine.Builder() .addExecPath("--input_jar", jar) .addExecPath("--output_zip", dexArchive) - .add(incrementalDexopts); + .add(incrementalDexopts) + .build(); Artifact paramFile = ruleContext.getDerivedArtifact( ParameterFile.derivePath(dexArchive.getRootRelativePath()), dexArchive.getRoot()); @@ -192,7 +307,7 @@ public final class DexArchiveAspect extends NativeAspectClass implements Configu new ParameterFileWriteAction( ruleContext.getActionOwner(), paramFile, - args.build(), + args, ParameterFile.ParameterFileType.UNQUOTED, ISO_8859_1)); SpawnAction.Builder dexbuilder = @@ -208,6 +323,7 @@ public final class DexArchiveAspect extends NativeAspectClass implements Configu .setProgressMessage( "Dexing " + jar.prettyPrint() + " with applicable dexopts " + incrementalDexopts); ruleContext.registerAction(dexbuilder.build(ruleContext)); + return dexArchive; } private static Set<Set<String>> aspectDexopts(RuleContext ruleContext) { |