| // 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.lib.rules.android; |
| |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| import static com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType.UNQUOTED; |
| import static com.google.devtools.build.lib.packages.Attribute.attr; |
| import static com.google.devtools.build.lib.packages.BuildType.LABEL; |
| import static com.google.devtools.build.lib.packages.BuildType.NODEP_LABEL; |
| import static com.google.devtools.build.lib.packages.BuildType.TRISTATE; |
| import static com.google.devtools.build.lib.packages.StarlarkProviderIdentifier.forKey; |
| import static com.google.devtools.build.lib.packages.Type.INTEGER; |
| import static com.google.devtools.build.lib.rules.android.AndroidCommon.getAndroidConfig; |
| import static com.google.devtools.build.lib.rules.android.AndroidSdkProvider.ANDROID_SDK_TOOLCHAIN_TYPE_ATTRIBUTE_NAME; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Functions; |
| import com.google.common.base.Joiner; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Predicates; |
| import com.google.common.collect.ImmutableCollection; |
| 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.collect.Sets; |
| import com.google.common.collect.Streams; |
| import com.google.devtools.build.lib.actions.ActionConflictException; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.ExecutionRequirements; |
| import com.google.devtools.build.lib.actions.ParamFileInfo; |
| import com.google.devtools.build.lib.analysis.Allowlist; |
| import com.google.devtools.build.lib.analysis.ConfiguredAspect; |
| import com.google.devtools.build.lib.analysis.ConfiguredAspectFactory; |
| import com.google.devtools.build.lib.analysis.ConfiguredTarget; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; |
| import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; |
| import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorArg; |
| import com.google.devtools.build.lib.analysis.actions.SpawnAction; |
| import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory; |
| import com.google.devtools.build.lib.analysis.config.ToolchainTypeRequirement; |
| import com.google.devtools.build.lib.analysis.platform.ToolchainInfo; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.packages.AspectDefinition; |
| import com.google.devtools.build.lib.packages.AspectParameters; |
| import com.google.devtools.build.lib.packages.AttributeMap; |
| import com.google.devtools.build.lib.packages.NativeAspectClass; |
| import com.google.devtools.build.lib.packages.NonconfigurableAttributeMapper; |
| import com.google.devtools.build.lib.packages.Rule; |
| import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; |
| import com.google.devtools.build.lib.packages.TargetUtils; |
| 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.JavaInfo; |
| import com.google.devtools.build.lib.rules.java.JavaRuleOutputJarsProvider.JavaOutput; |
| import com.google.devtools.build.lib.rules.proto.ProtoInfo; |
| import com.google.devtools.build.lib.rules.proto.ProtoLangToolchainProvider; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationConstant; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import javax.annotation.Nullable; |
| |
| /** Aspect to {@link DexArchiveProvider build .dex Archives} from Jars. */ |
| public class DexArchiveAspect extends NativeAspectClass implements ConfiguredAspectFactory { |
| public static final String NAME = "DexArchiveAspect"; |
| |
| /** |
| * 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 target. |
| */ |
| @SerializationConstant |
| public static final Function<Rule, AspectParameters> PARAM_EXTRACTOR = |
| (Rule rule) -> { |
| AttributeMap attributes = NonconfigurableAttributeMapper.of(rule); |
| AspectParameters.Builder result = new AspectParameters.Builder(); |
| TriState incrementalAttr = attributes.get("incremental_dexing", TRISTATE); |
| result.addAttribute("incremental_dexing", incrementalAttr.name()); |
| result.addAttribute( |
| "min_sdk_version", attributes.get("min_sdk_version", INTEGER).toString()); |
| result.addAttribute("toprule_kind", rule.getRuleClass()); |
| return result.build(); |
| }; |
| |
| /** |
| * Function that limits this aspect to Java 8 desugaring (disabling incremental dexing) when |
| * attaching this aspect to a target. This is intended for implicit attributes like the stub APKs |
| * for {@code bazel mobile-install}. |
| */ |
| @SerializationConstant |
| static final Function<Rule, AspectParameters> ONLY_DESUGAR_JAVA8 = |
| (Rule rule) -> |
| new AspectParameters.Builder() |
| .addAttribute("incremental_dexing", TriState.NO.name()) |
| .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", |
| // Propagate the aspect down legacy toolchain dependencies. This won't work for platform- |
| // based toolchains, which aren't connected to an attribute. See |
| // propagateDownLegacyToolchain for how this distinction is handled. |
| ":android_sdk", |
| "aidl_lib", // for the aidl runtime in the android_sdk rule |
| "$toolchain", // this is _toolchain in Starlark rules (b/78647825) |
| "$build_stamp_deps", // for build stamp runtime class deps |
| "$build_stamp_mergee_manifest_lib", // for empty build stamp Service class implementation |
| // To get from proto_library through proto_lang_toolchain rule to proto runtime library. |
| ":aspect_proto_toolchain_for_javalite", |
| "runtime"); |
| |
| private static final FlagMatcher DEXOPTS_SUPPORTED_IN_DEXBUILDER = |
| new FlagMatcher( |
| ImmutableList.of("--no-locals", "--no-optimize", "--no-warnings", "--positions")); |
| |
| private final RepositoryName toolsRepository; |
| private final String sdkToolchainLabel; |
| |
| public DexArchiveAspect(RepositoryName toolsRepository, String sdkToolchainLabel) { |
| this.toolsRepository = toolsRepository; |
| this.sdkToolchainLabel = sdkToolchainLabel; |
| } |
| |
| @Override |
| public AspectDefinition getDefinition(AspectParameters params) { |
| Label toolchainType = Label.parseCanonicalUnchecked(toolsRepository + sdkToolchainLabel); |
| AspectDefinition.Builder result = |
| new AspectDefinition.Builder(this) |
| .requireStarlarkProviders(forKey(JavaInfo.PROVIDER.getKey())) |
| // Latch onto Starlark toolchains in case they have a "runtime" (b/78647825) |
| .requireStarlarkProviders(forKey(ToolchainInfo.PROVIDER.getKey())) |
| // For android_sdk rules, where we just want to get at aidl runtime deps. |
| .requireStarlarkProviders(forKey(AndroidSdkProvider.PROVIDER.getKey())) |
| .requireStarlarkProviders(forKey(ProtoInfo.PROVIDER.getKey())) |
| .requireStarlarkProviderSets( |
| ImmutableList.of( |
| // For proto_lang_toolchain rules, where we just want to get at their runtime |
| // deps. |
| ImmutableSet.of(ProtoLangToolchainProvider.PROVIDER_ID))) |
| .add(attr(ANDROID_SDK_TOOLCHAIN_TYPE_ATTRIBUTE_NAME, NODEP_LABEL).value(toolchainType)) |
| .addToolchainTypes( |
| ToolchainTypeRequirement.builder(toolchainType).mandatory(true).build()) |
| // Parse labels since we don't have RuleDefinitionEnvironment.getLabel like in a rule |
| .add( |
| attr(ASPECT_DESUGAR_PREREQ, LABEL) |
| .cfg(ExecutionTransitionFactory.createFactory()) |
| .exec() |
| .value( |
| Label.parseCanonicalUnchecked( |
| toolsRepository + "//tools/android:desugar_java8"))) |
| // Access to --android_sdk so we can stub in a bootclasspath for desugaring if missing |
| // Remove this entirely when we remove --android_sdk support. |
| .add( |
| attr(":dex_archive_android_sdk", LABEL) |
| .allowedRuleClasses("android_sdk", "filegroup") |
| .value( |
| AndroidRuleClasses.getAndroidSdkLabel( |
| Label.parseCanonicalUnchecked( |
| toolsRepository + AndroidRuleClasses.DEFAULT_SDK)))) |
| .add( |
| Allowlist.getAttributeFromAllowlistName("enable_starlark_dex_desugar_proguard") |
| .value( |
| Label.parseCanonicalUnchecked( |
| toolsRepository |
| + "//tools/allowlists/android_binary_allowlist:enable_starlark_dex_desugar_proguard"))) |
| .requiresConfigurationFragments(AndroidConfiguration.class) |
| .requireAspectsWithProviders( |
| ImmutableList.of(ImmutableSet.of(forKey(JavaInfo.PROVIDER.getKey())))); |
| if (TriState.valueOf(params.getOnlyValueOfAttribute("incremental_dexing")) != TriState.NO) { |
| // Marginally improves "query2" precision for targets that disable incremental dexing |
| result.add( |
| attr(ASPECT_DEXBUILDER_PREREQ, LABEL) |
| .cfg(ExecutionTransitionFactory.createFactory()) |
| .exec() |
| .value( |
| Label.parseCanonicalUnchecked(toolsRepository + "//tools/android:dexbuilder"))); |
| } |
| for (String attr : TRANSITIVE_ATTRIBUTES) { |
| result.propagateAlongAttribute(attr); |
| } |
| return result.build(); |
| } |
| |
| /** |
| * Returns toolchain .jars that need dexing for platform-based toolchains. |
| * |
| * <p>Legacy toolchains handle these .jars recursively by propagating the aspect down the |
| * ":android_sdk" attribute. So they don't need this method. |
| */ |
| private static ImmutableList<Artifact> getPlatformBasedToolchainJars(RuleContext ruleContext) |
| throws RuleErrorException { |
| if (!ruleContext.attributes().has(":android_sdk")) { |
| // If we're dexing a non-Android target (like a java_library), there's no Android toolchain to |
| // include. |
| return ImmutableList.of(); |
| } |
| |
| AndroidSdkProvider androidSdk = AndroidSdkProvider.fromRuleContext(ruleContext); |
| if (androidSdk == null || androidSdk.getAidlLib() == null) { |
| // If the Android SDK is null, we don't have a valid toolchain. Expect a rule error reported |
| // from AndroidSdkProvider. |
| return ImmutableList.of(); |
| } |
| return ImmutableList.copyOf( |
| JavaInfo.getJavaInfo(androidSdk.getAidlLib()).getDirectRuntimeJars()); |
| } |
| |
| @Override |
| @Nullable |
| public ConfiguredAspect create( |
| Label targetLabel, |
| ConfiguredTarget ct, |
| RuleContext ruleContext, |
| AspectParameters params, |
| RepositoryName toolsRepository) |
| throws InterruptedException, ActionConflictException { |
| |
| ConfiguredAspect.Builder result = new ConfiguredAspect.Builder(ruleContext); |
| |
| // No-op out of the aspect in the android_binary rule if the Starlark dex/desugar will execute |
| // to avoid registering duplicate actions and bloating memory. |
| if (!params.getAttribute("toprule_kind").isEmpty() |
| && params.getOnlyValueOfAttribute("toprule_kind").equals("android_binary") |
| && Allowlist.hasAllowlist(ruleContext, "enable_starlark_dex_desugar_proguard") |
| && Allowlist.isAvailable(ruleContext, "enable_starlark_dex_desugar_proguard")) { |
| return result.build(); |
| } |
| |
| int minSdkVersion = 0; |
| if (!params.getAttribute("min_sdk_version").isEmpty()) { |
| minSdkVersion = Integer.valueOf(params.getOnlyValueOfAttribute("min_sdk_version")); |
| } |
| |
| Function<Artifact, Artifact> desugaredJars; |
| ImmutableList<Artifact> extraToolchainJars; |
| ImmutableCollection<Artifact> runtimeJars; |
| try { |
| extraToolchainJars = getPlatformBasedToolchainJars(ruleContext); |
| desugaredJars = |
| desugarJarsIfRequested(ct, ruleContext, minSdkVersion, result, extraToolchainJars); |
| runtimeJars = getProducedRuntimeJars(ct, ruleContext, extraToolchainJars); |
| } catch (RuleErrorException e) { |
| ruleContext.ruleError(e.getMessage()); |
| return null; |
| } |
| |
| TriState incrementalAttr = |
| TriState.valueOf(params.getOnlyValueOfAttribute("incremental_dexing")); |
| if (incrementalAttr == TriState.NO |
| || (!getAndroidConfig(ruleContext).useIncrementalDexing() |
| && incrementalAttr == TriState.AUTO)) { |
| // Dex archives will never be used, so don't bother setting them up. |
| return result.build(); |
| } |
| |
| if (JavaCommon.isNeverLink(ruleContext)) { |
| return result.addProvider(DexArchiveProvider.NEVERLINK).build(); |
| } |
| |
| DexArchiveProvider.Builder dexArchives = new DexArchiveProvider.Builder(); |
| collectPrerequisites( |
| ruleContext, DexArchiveProvider.class, dexArchives::addTransitiveProviders); |
| if (runtimeJars != null) { |
| boolean basenameClash = checkBasenameClash(runtimeJars); |
| Set<Set<String>> aspectDexopts = aspectDexopts(ruleContext); |
| String minSdkFilenamePart = minSdkVersion > 0 ? "--min_sdk_version=" + minSdkVersion : ""; |
| for (Artifact jar : runtimeJars) { |
| Artifact desugaredJar = desugaredJars.apply(jar); |
| for (Set<String> incrementalDexopts : aspectDexopts) { |
| // 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. Since min_sdk_version is a parameter to the aspect from the |
| // android_binary target that the aspect originates from, it's handled separately so that |
| // the correct min sdk value is used. |
| String uniqueFilename = |
| (basenameClash ? jar.getRootRelativePathString() : jar.getFilename()) |
| + Joiner.on("").join(incrementalDexopts) |
| + minSdkFilenamePart |
| + ".dex.zip"; |
| Artifact dexArchive = |
| createDexArchiveAction( |
| ruleContext, |
| ASPECT_DEXBUILDER_PREREQ, |
| desugaredJar, |
| incrementalDexopts, |
| minSdkVersion, |
| AndroidBinary.getDxArtifact(ruleContext, uniqueFilename)); |
| dexArchives.addDexArchive(incrementalDexopts, dexArchive, jar); |
| } |
| } |
| } |
| return result.addProvider(dexArchives.build()).build(); |
| } |
| |
| /** |
| * Runs Jars in {@link JavaInfo#getDirectRuntimeJars()} through desugaring action if flag is set |
| * and adds the result to {@code result}. 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, |
| int minSdkVersion, |
| ConfiguredAspect.Builder result, |
| Iterable<Artifact> extraToolchainJars) |
| throws RuleErrorException { |
| if (!getAndroidConfig(ruleContext).desugarJava8()) { |
| return Functions.identity(); |
| } |
| Map<Artifact, Artifact> newlyDesugared = new HashMap<>(); |
| if (JavaCommon.isNeverLink(ruleContext)) { |
| result.addProvider(AndroidRuntimeJarProvider.NEVERLINK); |
| return Functions.forMap(newlyDesugared); |
| } |
| AndroidRuntimeJarProvider.Builder desugaredJars = new AndroidRuntimeJarProvider.Builder(); |
| collectPrerequisites( |
| ruleContext, AndroidRuntimeJarProvider.class, desugaredJars::addTransitiveProviders); |
| if (isProtoLibrary(ruleContext)) { |
| // TODO(b/33557068): Desugar protos if needed instead of assuming they don't need desugaring |
| result.addProvider(desugaredJars.build()); |
| return Functions.identity(); |
| } |
| |
| JavaInfo javaInfo = JavaInfo.getJavaInfo(base); |
| if (javaInfo != null) { |
| // These are all transitive hjars of dependencies and hjar of the jar itself |
| NestedSet<Artifact> compileTimeClasspath = JavaInfo.transitiveCompileTimeJars(base); |
| ImmutableSet.Builder<Artifact> jars = ImmutableSet.builder(); |
| jars.addAll(javaInfo.getDirectRuntimeJars()); |
| |
| Artifact rJar = getAndroidLibraryRJar(base); |
| if (rJar != null) { |
| // TODO(b/124540821): Disable R.jar desugaring (with a flag). |
| jars.add(rJar); |
| } |
| |
| Artifact buildStampJar = getAndroidBuildStampJar(base); |
| if (buildStampJar != null) { |
| jars.add(buildStampJar); |
| } |
| |
| // For android_* targets we need to honor their bootclasspath (nicer in general to do so) |
| NestedSet<Artifact> bootclasspath = getBootclasspath(base, ruleContext); |
| |
| jars.addAll(extraToolchainJars); |
| ImmutableSet<Artifact> jarsToProcess = jars.build(); |
| boolean basenameClash = checkBasenameClash(jarsToProcess); |
| for (Artifact jar : jarsToProcess) { |
| Artifact desugared = |
| createDesugarAction( |
| ruleContext, |
| basenameClash, |
| jar, |
| bootclasspath, |
| compileTimeClasspath, |
| minSdkVersion); |
| newlyDesugared.put(jar, desugared); |
| desugaredJars.addDesugaredJar(jar, desugared); |
| } |
| } |
| result.addProvider(desugaredJars.build()); |
| |
| return key -> { |
| if (newlyDesugared.containsKey(key)) { |
| return newlyDesugared.get(key); |
| } |
| // Fall back to the original un-desugared artifact. |
| return key; |
| }; |
| } |
| |
| @Nullable |
| private static ImmutableCollection<Artifact> getProducedRuntimeJars( |
| ConfiguredTarget base, RuleContext ruleContext, Iterable<Artifact> extraToolchainJars) |
| throws RuleErrorException { |
| if (isProtoLibrary(ruleContext)) { |
| if (!ruleContext.getPrerequisites("srcs").isEmpty()) { |
| JavaInfo javaInfo = JavaInfo.getJavaInfo(base); |
| if (javaInfo != null) { |
| return javaInfo.getJavaOutputs().stream() |
| .map(JavaOutput::getClassJar) |
| .collect(toImmutableList()); |
| } |
| } |
| } else { |
| ImmutableSet.Builder<Artifact> jars = ImmutableSet.builder(); |
| JavaInfo javaInfo = JavaInfo.getJavaInfo(base); |
| if (javaInfo != null) { |
| jars.addAll(javaInfo.getDirectRuntimeJars()); |
| } |
| |
| Artifact rJar = getAndroidLibraryRJar(base); |
| if (rJar != null) { |
| jars.add(rJar); |
| } |
| |
| Artifact buildStampJar = getAndroidBuildStampJar(base); |
| if (buildStampJar != null) { |
| jars.add(buildStampJar); |
| } |
| |
| jars.addAll(extraToolchainJars); |
| return jars.build(); |
| } |
| return null; |
| } |
| |
| private static boolean isProtoLibrary(RuleContext ruleContext) { |
| return "proto_library".equals(ruleContext.getRule().getRuleClass()); |
| } |
| |
| @Nullable |
| private static Artifact getAndroidLibraryRJar(ConfiguredTarget base) { |
| AndroidIdeInfoProvider provider = |
| (AndroidIdeInfoProvider) base.get(AndroidIdeInfoProvider.PROVIDER.getKey()); |
| if (provider != null && provider.getResourceJarJavaOutput() != null) { |
| return provider.getResourceJarJavaOutput().getClassJar(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static Artifact getAndroidBuildStampJar(ConfiguredTarget base) { |
| AndroidApplicationResourceInfo provider = |
| (AndroidApplicationResourceInfo) base.get(AndroidApplicationResourceInfo.PROVIDER.getKey()); |
| if (provider != null && provider.getBuildStampJar() != null) { |
| return provider.getBuildStampJar(); |
| } |
| return null; |
| } |
| |
| private static boolean checkBasenameClash(Iterable<Artifact> artifacts) { |
| HashSet<String> seen = new HashSet<>(); |
| for (Artifact artifact : artifacts) { |
| if (!seen.add(artifact.getFilename())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static <T extends TransitiveInfoProvider> void collectPrerequisites( |
| RuleContext ruleContext, Class<T> classType, Consumer<List<T>> sink) { |
| for (String attr : TRANSITIVE_ATTRIBUTES) { |
| if (ruleContext.attributes().getAttributeType(attr) != null) { |
| sink.accept(ruleContext.getPrerequisites(attr, classType)); |
| } |
| } |
| } |
| |
| private NestedSet<Artifact> getBootclasspath(ConfiguredTarget base, RuleContext ruleContext) |
| throws RuleErrorException { |
| NestedSet<Artifact> bootClasspath = JavaInfo.bootClasspath(base); |
| if (!bootClasspath.isEmpty()) { |
| return bootClasspath; |
| } |
| Artifact androidJar = getAndroidJar(ruleContext); |
| if (androidJar != null) { |
| return NestedSetBuilder.<Artifact>naiveLinkOrder().add(androidJar).build(); |
| } |
| // This shouldn't ever be reached, but if it is, we should be clear about the error. |
| throw new IllegalStateException("no compilationInfo or androidJar"); |
| } |
| |
| @Nullable |
| private Artifact getAndroidJar(RuleContext ruleContext) throws RuleErrorException { |
| Label toolchainType = Label.parseCanonicalUnchecked(toolsRepository + sdkToolchainLabel); |
| AndroidSdkProvider androidSdk = AndroidSdkProvider.fromRuleContext(ruleContext, toolchainType); |
| if (androidSdk == null) { |
| // If the Android SDK is null, we don't have a valid toolchain. Expect a rule error reported |
| // from AndroidSdkProvider. |
| return null; |
| } |
| return androidSdk.getAndroidJar(); |
| } |
| |
| private Artifact createDesugarAction( |
| RuleContext ruleContext, |
| boolean disambiguateBasenames, |
| Artifact jar, |
| NestedSet<Artifact> bootclasspath, |
| NestedSet<Artifact> compileTimeClasspath, |
| int minSdkVersion) { |
| |
| String minSdkFilenamePart = minSdkVersion > 0 ? "_minsdk=" + minSdkVersion : ""; |
| return createDesugarAction( |
| ruleContext, |
| ASPECT_DESUGAR_PREREQ, |
| jar, |
| bootclasspath, |
| compileTimeClasspath, |
| minSdkVersion, |
| AndroidBinary.getDxArtifact( |
| ruleContext, |
| (disambiguateBasenames ? jar.getRootRelativePathString() : jar.getFilename()) |
| + minSdkFilenamePart |
| + "_desugared.jar")); |
| } |
| |
| private static Artifact createDesugarAction( |
| RuleContext ruleContext, |
| String desugarPrereqName, |
| Artifact jar, |
| NestedSet<Artifact> bootclasspath, |
| NestedSet<Artifact> classpath, |
| int minSdkVersion, |
| Artifact result) { |
| SpawnAction.Builder action = |
| new SpawnAction.Builder() |
| .useDefaultShellEnvironment() |
| .setExecutable(ruleContext.getExecutablePrerequisite(desugarPrereqName)) |
| .addInput(jar) |
| .addTransitiveInputs(bootclasspath) |
| .addTransitiveInputs(classpath) |
| .addOutput(result) |
| .setMnemonic("Desugar") |
| .setProgressMessage("Desugaring %s for Android", jar.prettyPrint()) |
| .setExecutionInfo( |
| createDexingDesugaringExecRequirements(ruleContext) |
| .putAll(ExecutionRequirements.WORKER_MODE_ENABLED) |
| .buildKeepingLast()); |
| |
| CustomCommandLine.Builder args = |
| new CustomCommandLine.Builder() |
| .addExecPath("--input", jar) |
| .addExecPath("--output", result) |
| .addExecPaths(VectorArg.addBefore("--classpath_entry").each(classpath)) |
| .addExecPaths(VectorArg.addBefore("--bootclasspath_entry").each(bootclasspath)); |
| if (getAndroidConfig(ruleContext).checkDesugarDeps()) { |
| args.add("--emit_dependency_metadata_as_needed"); |
| } |
| if (getAndroidConfig(ruleContext).desugarJava8Libs()) { |
| args.add("--desugar_supported_core_libs"); |
| } |
| if (minSdkVersion > 0) { |
| args.add("--min_sdk_version", Integer.toString(minSdkVersion)); |
| } |
| |
| action.addCommandLine( |
| // Always use params file, so we don't need to compute command line length first |
| args.build(), ParamFileInfo.builder(UNQUOTED).setUseAlways(true).build()); |
| |
| ruleContext.registerAction(action.build(ruleContext)); |
| return result; |
| } |
| |
| /** |
| * Desugars the given Jar using an executable prerequisite {@code "$desugar"}. 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, |
| NestedSet<Artifact> bootclasspath, |
| NestedSet<Artifact> classpath, |
| int minSdkVersion, |
| Artifact result) { |
| return createDesugarAction( |
| ruleContext, "$desugar", jar, bootclasspath, classpath, minSdkVersion, 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. |
| * |
| * @return the artifact given as {@code result}, which can simplify calling code |
| */ |
| // Package-private method for use in AndroidBinary |
| @CanIgnoreReturnValue |
| static Artifact createDexArchiveAction( |
| RuleContext ruleContext, |
| String dexbuilderPrereq, |
| Artifact jar, |
| Set<String> incrementalDexopts, |
| int minSdkVersion, |
| Artifact dexArchive) { |
| SpawnAction.Builder dexbuilder = |
| new SpawnAction.Builder() |
| .useDefaultShellEnvironment() |
| .setExecutable(ruleContext.getExecutablePrerequisite(dexbuilderPrereq)) |
| .setExecutionInfo( |
| createDexingDesugaringExecRequirements(ruleContext) |
| .putAll( |
| TargetUtils.getExecutionInfo( |
| ruleContext.getRule(), ruleContext.isAllowTagsPropagation())) |
| .buildKeepingLast()) |
| // WorkerSpawnStrategy expects the last argument to be @paramfile |
| .addInput(jar) |
| .addOutput(dexArchive) |
| .setMnemonic("DexBuilder") |
| .setProgressMessage( |
| "Dexing %s with applicable dexopts %s", jar.prettyPrint(), incrementalDexopts); |
| |
| CustomCommandLine.Builder args = |
| new CustomCommandLine.Builder() |
| .addExecPath("--input_jar", jar) |
| .addExecPath("--output_zip", dexArchive) |
| .addAll(ImmutableList.copyOf(incrementalDexopts)); |
| if (minSdkVersion > 0) { |
| args.add("--min_sdk_version", Integer.toString(minSdkVersion)); |
| } |
| |
| dexbuilder.addCommandLine( |
| args.build(), ParamFileInfo.builder(UNQUOTED).setUseAlways(true).build()); |
| ruleContext.registerAction(dexbuilder.build(ruleContext)); |
| return dexArchive; |
| } |
| |
| @CanIgnoreReturnValue |
| static Artifact createShardedOptimizedDexArchiveAction( |
| RuleContext ruleContext, |
| Artifact jar, |
| Set<String> incrementalDexopts, |
| int minSdkVersion, |
| Artifact dexArchive) { |
| SpawnAction.Builder dexbuilder = |
| new SpawnAction.Builder() |
| .useDefaultShellEnvironment() |
| .setExecutable(ruleContext.getExecutablePrerequisite(":optimizing_dexer")) |
| .setExecutionInfo( |
| createDexingDesugaringExecRequirements(ruleContext) |
| .putAll( |
| TargetUtils.getExecutionInfo( |
| ruleContext.getRule(), ruleContext.isAllowTagsPropagation())) |
| .buildKeepingLast()) |
| .addInput(jar) |
| .addOutput(dexArchive) |
| .setMnemonic("ShardedOptimizingDex") |
| .setProgressMessage( |
| "Optimized dexing %s with applicable dexopts %s", |
| jar.prettyPrint(), incrementalDexopts); |
| |
| CustomCommandLine.Builder args = |
| new CustomCommandLine.Builder() |
| .add("--intermediate") |
| .add("--release") |
| .add("--no-desugaring") |
| .addExecPath("--output", dexArchive) |
| .addAll(ImmutableList.copyOf(incrementalDexopts)) |
| .addExecPath(jar); |
| if (minSdkVersion > 0) { |
| args.add("--min_sdk_version", Integer.toString(minSdkVersion)); |
| } |
| |
| dexbuilder.addCommandLine(args.build()); |
| ruleContext.registerAction(dexbuilder.build(ruleContext)); |
| return dexArchive; |
| } |
| |
| private static Set<Set<String>> aspectDexopts(RuleContext ruleContext) { |
| return Sets.powerSet( |
| normalizeDexopts(getAndroidConfig(ruleContext).getDexoptsSupportedInIncrementalDexing())); |
| } |
| |
| /** Creates the execution requires for the DexBuilder and Desugar actions */ |
| private static ImmutableMap.Builder<String, String> createDexingDesugaringExecRequirements( |
| RuleContext ruleContext) { |
| final ImmutableMap.Builder<String, String> executionInfo = ImmutableMap.builder(); |
| AndroidConfiguration androidConfiguration = getAndroidConfig(ruleContext); |
| if (androidConfiguration.persistentDexDesugar()) { |
| executionInfo.putAll(ExecutionRequirements.WORKER_MODE_ENABLED); |
| if (androidConfiguration.persistentMultiplexDexDesugar()) { |
| executionInfo.putAll(ExecutionRequirements.WORKER_MULTIPLEX_MODE_ENABLED); |
| } |
| } |
| |
| return executionInfo; |
| } |
| |
| /** |
| * Derives options to use in incremental dexing actions from the given context and dx flags, where |
| * the latter typically come from a {@code dexopts} attribute on a top-level target. This method |
| * only works reliably if the given dexopts were tokenized, e.g., using {@link |
| * RuleContext#getTokenizedStringListAttr}. |
| */ |
| static ImmutableSet<String> incrementalDexopts( |
| RuleContext ruleContext, Iterable<String> tokenizedDexopts) { |
| return normalizeDexopts( |
| Iterables.filter( |
| tokenizedDexopts, |
| // dexopts have to match exactly since aspect only creates archives for listed ones |
| Predicates.in(getAndroidConfig(ruleContext).getDexoptsSupportedInIncrementalDexing()))); |
| } |
| |
| /** |
| * Returns the subset of the given dexopts that are forbidden from using incremental dexing by |
| * default. |
| */ |
| static Iterable<String> forbiddenDexopts(RuleContext ruleContext, List<String> dexopts) { |
| return Iterables.filter( |
| dexopts, |
| new FlagMatcher( |
| getAndroidConfig(ruleContext).getTargetDexoptsThatPreventIncrementalDexing())); |
| } |
| |
| /** |
| * Derives options to use in DexBuilder actions from the given context and dx flags, where the |
| * latter typically come from a {@code dexopts} attribute on a top-level target. This should be a |
| * superset of {@link #incrementalDexopts}. |
| */ |
| static ImmutableSet<String> topLevelDexbuilderDexopts(Iterable<String> tokenizedDexopts) { |
| // We don't need an ordered set but might as well. |
| return normalizeDexopts(Iterables.filter(tokenizedDexopts, DEXOPTS_SUPPORTED_IN_DEXBUILDER)); |
| } |
| |
| /** |
| * Derives options to use in DexFileMerger actions from the given context and dx flags, where the |
| * latter typically come from a {@code dexopts} attribute on a top-level target. |
| */ |
| static ImmutableSet<String> mergerDexopts( |
| RuleContext ruleContext, Iterable<String> tokenizedDexopts) { |
| // We don't need an ordered set but might as well. Note we don't need to worry about coverage |
| // builds since the merger doesn't use --no-locals. |
| return normalizeDexopts( |
| Iterables.filter( |
| tokenizedDexopts, |
| new FlagMatcher(getAndroidConfig(ruleContext).getDexoptsSupportedInDexMerger()))); |
| } |
| |
| /** |
| * Derives options to use in DexFileSharder actions from the given context and dx flags, where the |
| * latter typically come from a {@code dexopts} attribute on a top-level target. |
| */ |
| static ImmutableSet<String> sharderDexopts( |
| RuleContext ruleContext, Iterable<String> tokenizedDexopts) { |
| // We don't need an ordered set but might as well. Note we don't need to worry about coverage |
| // builds since the merger doesn't use --no-locals. |
| return normalizeDexopts( |
| Iterables.filter( |
| tokenizedDexopts, |
| new FlagMatcher(getAndroidConfig(ruleContext).getDexoptsSupportedInDexSharder()))); |
| } |
| |
| private static ImmutableSet<String> normalizeDexopts(Iterable<String> tokenizedDexopts) { |
| // Sort and use ImmutableSet to drop duplicates and get fixed (sorted) order. Fixed order is |
| // important so we generate one dex archive per set of flag in create() method, regardless of |
| // how those flags are listed in all the top-level targets being built. |
| return Streams.stream(tokenizedDexopts) |
| .map(FlagConverter.DX_TO_DEXBUILDER) |
| .sorted() |
| .collect(ImmutableSet.toImmutableSet()); // collector with dedupe |
| } |
| |
| private static class FlagMatcher implements Predicate<String> { |
| private final ImmutableList<String> matching; |
| |
| FlagMatcher(ImmutableList<String> matching) { |
| this.matching = matching; |
| } |
| |
| @Override |
| public boolean apply(String input) { |
| for (String match : matching) { |
| if (input.contains(match)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| private enum FlagConverter implements Function<String, String> { |
| DX_TO_DEXBUILDER; |
| |
| @Override |
| public String apply(String input) { |
| return input.replace("--no-", "--no"); |
| } |
| } |
| } |