// Copyright 2015 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.base.Preconditions.checkArgument; import static com.google.common.base.Predicates.in; import static com.google.common.base.Predicates.not; import static com.google.common.collect.Iterables.filter; import static com.google.devtools.build.lib.analysis.OutputGroupProvider.INTERNAL_SUFFIX; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Optional; import com.google.common.base.Predicates; 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.Multimap; import com.google.common.collect.MultimapBuilder; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.actions.FailAction; import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.actions.CommandLine; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.actions.SpawnAction.Builder; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.collect.IterablesChain; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.packages.BuildType; import com.google.devtools.build.lib.packages.TriState; import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidBinaryType; import com.google.devtools.build.lib.rules.android.AndroidConfiguration.ApkSigningMethod; import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode; import com.google.devtools.build.lib.rules.cpp.CcToolchainProvider; import com.google.devtools.build.lib.rules.cpp.CppHelper; import com.google.devtools.build.lib.rules.java.DeployArchiveBuilder; 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.JavaConfiguration; import com.google.devtools.build.lib.rules.java.JavaConfiguration.JavaOptimizationMode; import com.google.devtools.build.lib.rules.java.JavaHelper; import com.google.devtools.build.lib.rules.java.JavaSemantics; import com.google.devtools.build.lib.rules.java.JavaSourceInfoProvider; import com.google.devtools.build.lib.rules.java.JavaTargetAttributes; import com.google.devtools.build.lib.rules.java.JavaToolchainProvider; import com.google.devtools.build.lib.rules.java.Jvm; import com.google.devtools.build.lib.rules.java.ProguardHelper; import com.google.devtools.build.lib.rules.java.ProguardHelper.ProguardOutput; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import javax.annotation.Nullable; /** * An implementation for the "android_binary" rule. */ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { protected abstract JavaSemantics createJavaSemantics(); protected abstract AndroidSemantics createAndroidSemantics(); @Override public ConfiguredTarget create(RuleContext ruleContext) throws InterruptedException, RuleErrorException { JavaSemantics javaSemantics = createJavaSemantics(); AndroidSemantics androidSemantics = createAndroidSemantics(); if (!AndroidSdkProvider.verifyPresence(ruleContext)) { return null; } NestedSetBuilder<Artifact> filesBuilder = NestedSetBuilder.stableOrder(); JavaCommon javaCommon = AndroidCommon.createJavaCommonWithAndroidDataBinding(ruleContext, javaSemantics, false); javaSemantics.checkRule(ruleContext, javaCommon); javaSemantics.checkForProtoLibraryAndJavaProtoLibraryOnSameProto(ruleContext, javaCommon); AndroidCommon androidCommon = new AndroidCommon( javaCommon, true /* asNeverLink */, true /* exportDeps */); ResourceDependencies resourceDeps = LocalResourceContainer.definesAndroidResources( ruleContext.attributes()) ? ResourceDependencies.fromRuleDeps(ruleContext, false /* neverlink */) : ResourceDependencies.fromRuleResourceAndDeps(ruleContext, false /* neverlink */); RuleConfiguredTargetBuilder builder = init( ruleContext, filesBuilder, resourceDeps, javaCommon, androidCommon, javaSemantics, androidSemantics); return builder.build(); } /** * Checks expected rule invariants, throws rule errors if anything is set wrong. */ private static void validateRuleContext(RuleContext ruleContext) throws InterruptedException, RuleErrorException { if (getMultidexMode(ruleContext) != MultidexMode.LEGACY && ruleContext.attributes().isAttributeValueExplicitlySpecified( "main_dex_proguard_specs")) { ruleContext.throwWithAttributeError("main_dex_proguard_specs", "The " + "'main_dex_proguard_specs' attribute is only allowed if 'multidex' is set to 'legacy'"); } if (ruleContext.attributes().isAttributeValueExplicitlySpecified("proguard_apply_mapping") && ruleContext.attributes() .get(ProguardHelper.PROGUARD_SPECS, BuildType.LABEL_LIST) .isEmpty()) { ruleContext.throwWithAttributeError("proguard_apply_mapping", "'proguard_apply_mapping' can only be used when 'proguard_specs' is also set"); } if (ruleContext.attributes().isAttributeValueExplicitlySpecified("rex_package_map") && !ruleContext.attributes().get("rewrite_dexes_with_rex", Type.BOOLEAN)) { ruleContext.throwWithAttributeError( "rex_package_map", "'rex_package_map' can only be used when 'rewrite_dexes_with_rex' is also set"); } if (ruleContext.attributes().isAttributeValueExplicitlySpecified("rex_package_map") && ruleContext.attributes() .get(ProguardHelper.PROGUARD_SPECS, BuildType.LABEL_LIST) .isEmpty()) { ruleContext.throwWithAttributeError("rex_package_map", "'rex_package_map' can only be used when 'proguard_specs' is also set"); } if (ruleContext.attributes().isAttributeValueExplicitlySpecified("resources") && DataBinding.isEnabled(ruleContext)) { ruleContext.throwWithRuleError("Data binding doesn't work with the \"resources\" attribute. " + "Use \"resource_files\" instead."); } } private static RuleConfiguredTargetBuilder init( RuleContext ruleContext, NestedSetBuilder<Artifact> filesBuilder, ResourceDependencies resourceDeps, JavaCommon javaCommon, AndroidCommon androidCommon, JavaSemantics javaSemantics, AndroidSemantics androidSemantics) throws InterruptedException, RuleErrorException { validateRuleContext(ruleContext); // TODO(bazel-team): Find a way to simplify this code. // treeKeys() means that the resulting map sorts the entries by key, which is necessary to // ensure determinism. Multimap<String, TransitiveInfoCollection> depsByArchitecture = MultimapBuilder.treeKeys().arrayListValues().build(); AndroidConfiguration androidConfig = ruleContext.getFragment(AndroidConfiguration.class); for (Map.Entry<Optional<String>, ? extends List<? extends TransitiveInfoCollection>> entry : ruleContext.getSplitPrerequisites("deps").entrySet()) { String cpu = entry.getKey().or(androidConfig.getCpu()); depsByArchitecture.putAll(cpu, entry.getValue()); } Map<String, BuildConfiguration> configurationMap = new LinkedHashMap<>(); Map<String, CcToolchainProvider> toolchainMap = new LinkedHashMap<>(); for (Map.Entry<Optional<String>, ? extends List<? extends TransitiveInfoCollection>> entry : ruleContext.getSplitPrerequisites(":cc_toolchain_split").entrySet()) { String cpu = entry.getKey().or(androidConfig.getCpu()); TransitiveInfoCollection dep = Iterables.getOnlyElement(entry.getValue()); CcToolchainProvider toolchain = CppHelper.getToolchain(ruleContext, dep); configurationMap.put(cpu, dep.getConfiguration()); toolchainMap.put(cpu, toolchain); } NativeLibs nativeLibs = NativeLibs.fromLinkedNativeDeps( ruleContext, androidSemantics.getNativeDepsFileName(), depsByArchitecture, toolchainMap, configurationMap); // TODO(bazel-team): Resolve all the different cases of resource handling so this conditional // can go away: recompile from android_resources, and recompile from android_binary attributes. ApplicationManifest applicationManifest; ResourceApk resourceApk; ResourceApk incrementalResourceApk; ResourceApk instantRunResourceApk; ResourceApk splitResourceApk; if (LocalResourceContainer.definesAndroidResources(ruleContext.attributes())) { // Retrieve and compile the resources defined on the android_binary rule. LocalResourceContainer.validateRuleContext(ruleContext); ApplicationManifest ruleManifest = androidSemantics.getManifestForRule(ruleContext); applicationManifest = ruleManifest.mergeWith(ruleContext, resourceDeps); Artifact featureOfArtifact = ruleContext.attributes().isAttributeValueExplicitlySpecified("feature_of") ? Iterables.getOnlyElement( ruleContext.getPrerequisite("feature_of", Mode.TARGET, ApkProvider.class) .getTransitiveApks()) : null; Artifact featureAfterArtifact = ruleContext.attributes().isAttributeValueExplicitlySpecified("feature_after") ? Iterables.getOnlyElement( ruleContext.getPrerequisite("feature_after", Mode.TARGET, ApkProvider.class) .getTransitiveApks()) : null; resourceApk = applicationManifest.packWithDataAndResources( ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK), ruleContext, false, /* isLibrary */ resourceDeps, ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), null, /* Artifact symbolsTxt */ ResourceFilter.fromRuleContext(ruleContext), ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), false, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, ""), createMainDexProguardSpec(ruleContext), ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST), ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP), DataBinding.isEnabled(ruleContext) ? DataBinding.getLayoutInfoFile(ruleContext) : null, featureOfArtifact, featureAfterArtifact); ruleContext.assertNoErrors(); incrementalResourceApk = applicationManifest .addMobileInstallStubApplication(ruleContext) .packWithDataAndResources( ruleContext .getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_INCREMENTAL_RESOURCES_APK), ruleContext, false, /* isLibrary */ resourceDeps, null, /* Artifact rTxt */ null, /* Artifact symbolsTxt */ ResourceFilter.fromRuleContext(ruleContext), ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), true, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental"), null, /* mainDexProguardCfg */ null, /* manifestOut */ null, /* mergedResourcesOut */ null, /* dataBindingInfoZip */ null, /* featureOf */ null /* featureAfter */); ruleContext.assertNoErrors(); instantRunResourceApk = applicationManifest .addInstantRunStubApplication(ruleContext) .packWithDataAndResources( getDxArtifact(ruleContext, "android_instant_run.ap_"), ruleContext, false, /* isLibrary */ resourceDeps, null, /* Artifact rTxt */ null, /* Artifact symbolsTxt */ ResourceFilter.fromRuleContext(ruleContext), ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), true, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, "instant_run"), null, /* mainDexProguardCfg */ null, /* manifestOut */ null, /* mergedResourcesOut */ null, /* dataBindingInfoZip */ null, /* featureOf */ null /* featureAfter */); ruleContext.assertNoErrors(); splitResourceApk = applicationManifest .createSplitManifest(ruleContext, "android_resources", false) .packWithDataAndResources( getDxArtifact(ruleContext, "android_resources.ap_"), ruleContext, false, /* isLibrary */ resourceDeps, null, /* Artifact rTxt */ null, /* Artifact symbolsTxt */ ResourceFilter.fromRuleContext(ruleContext), ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), true, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental_split"), null, /* mainDexProguardCfg */ null, /* manifestOut */ null, /* mergedResourcesOut */ null, /* dataBindingInfoZip */ null, /* featureOf */ null /* featureAfter */); ruleContext.assertNoErrors(); } else { if (!ruleContext.attributes().get("crunch_png", Type.BOOLEAN)) { ruleContext.throwWithRuleError("Setting crunch_png = 0 is not supported for android_binary" + " rules which depend on android_resources rules."); } // Retrieve the resources from the resources attribute on the android_binary rule // and recompile them if necessary. ApplicationManifest resourcesManifest = ApplicationManifest.fromResourcesRule(ruleContext); if (resourcesManifest == null) { throw new RuleErrorException(); } applicationManifest = resourcesManifest.mergeWith(ruleContext, resourceDeps); // Always recompiling resources causes AndroidTest to fail in certain circumstances. if (shouldRegenerate(ruleContext, resourceDeps)) { resourceApk = applicationManifest.packWithResources( ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK), ruleContext, resourceDeps, true, /* createSource */ ProguardHelper.getProguardConfigArtifact(ruleContext, ""), createMainDexProguardSpec(ruleContext)); ruleContext.assertNoErrors(); } else { resourceApk = applicationManifest.useCurrentResources( ruleContext, ProguardHelper.getProguardConfigArtifact(ruleContext, ""), createMainDexProguardSpec(ruleContext)); ruleContext.assertNoErrors(); } incrementalResourceApk = applicationManifest .addMobileInstallStubApplication(ruleContext) .packWithResources( ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.ANDROID_INCREMENTAL_RESOURCES_APK), ruleContext, resourceDeps, false, /* createSource */ ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental"), null /* mainDexProguardConfig */); ruleContext.assertNoErrors(); instantRunResourceApk = applicationManifest .addInstantRunStubApplication(ruleContext) .packWithResources( getDxArtifact(ruleContext, "android_instant_run.ap_"), ruleContext, resourceDeps, false, /* createSource */ ProguardHelper.getProguardConfigArtifact(ruleContext, "instant_run"), null /* mainDexProguardConfig */); ruleContext.assertNoErrors(); splitResourceApk = applicationManifest .createSplitManifest(ruleContext, "android_resources", false) .packWithResources(getDxArtifact(ruleContext, "android_resources.ap_"), ruleContext, resourceDeps, false, /* createSource */ ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental_split"), null /* mainDexProguardConfig */); ruleContext.assertNoErrors(); } boolean shrinkResources = shouldShrinkResources(ruleContext); JavaTargetAttributes resourceClasses = androidCommon.init( javaSemantics, androidSemantics, resourceApk, ruleContext.getConfiguration().isCodeCoverageEnabled(), true /* collectJavaCompilationArgs */, true, /* isBinary */ androidConfig.includeLibraryResourceJars()); ruleContext.assertNoErrors(); Function<Artifact, Artifact> derivedJarFunction = collectDesugaredJars(ruleContext, androidCommon, androidSemantics, resourceClasses); Artifact deployJar = createDeployJar(ruleContext, javaSemantics, androidCommon, resourceClasses, derivedJarFunction); Artifact proguardMapping = ruleContext.getPrerequisiteArtifact( "proguard_apply_mapping", Mode.TARGET); return createAndroidBinary( ruleContext, filesBuilder, deployJar, derivedJarFunction, /* isBinaryJarFiltered */ false, javaCommon, androidCommon, javaSemantics, androidSemantics, nativeLibs, applicationManifest, resourceApk, incrementalResourceApk, instantRunResourceApk, splitResourceApk, shrinkResources, resourceClasses, ImmutableList.<Artifact>of(), ImmutableList.<Artifact>of(), proguardMapping); } public static RuleConfiguredTargetBuilder createAndroidBinary( RuleContext ruleContext, NestedSetBuilder<Artifact> filesBuilder, Artifact binaryJar, Function<Artifact, Artifact> derivedJarFunction, boolean isBinaryJarFiltered, JavaCommon javaCommon, AndroidCommon androidCommon, JavaSemantics javaSemantics, AndroidSemantics androidSemantics, NativeLibs nativeLibs, ApplicationManifest applicationManifest, ResourceApk resourceApk, ResourceApk incrementalResourceApk, ResourceApk instantRunResourceApk, ResourceApk splitResourceApk, boolean shrinkResources, JavaTargetAttributes resourceClasses, ImmutableList<Artifact> apksUnderTest, ImmutableList<Artifact> additionalMergedManifests, Artifact proguardMapping) throws InterruptedException, RuleErrorException { ImmutableList<Artifact> proguardSpecs = ProguardHelper.collectTransitiveProguardSpecs( ruleContext, ImmutableList.of(resourceApk.getResourceProguardConfig())); boolean rexEnabled = ruleContext.getFragment(AndroidConfiguration.class).useRexToCompressDexFiles() || (ruleContext.attributes().get("rewrite_dexes_with_rex", Type.BOOLEAN)); // TODO(bazel-team): Verify that proguard spec files don't contain -printmapping directions // which this -printmapping command line flag will override. Artifact proguardOutputMap = null; if (ProguardHelper.genProguardMapping(ruleContext.attributes()) || ProguardHelper.getJavaOptimizationMode(ruleContext).alwaysGenerateOutputMapping() || shrinkResources) { if (rexEnabled) { proguardOutputMap = ProguardHelper.getProguardTempArtifact(ruleContext, ProguardHelper.getJavaOptimizationMode(ruleContext).name().toLowerCase(), "proguard_output_for_rex.map"); } else { proguardOutputMap = ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_MAP); } } ProguardOutput proguardOutput = applyProguard( ruleContext, androidCommon, javaSemantics, binaryJar, proguardSpecs, proguardMapping, proguardOutputMap); if (shrinkResources) { resourceApk = shrinkResources( ruleContext, resourceApk, proguardSpecs, proguardOutput, filesBuilder); } Artifact jarToDex = proguardOutput.getOutputJar(); DexingOutput dexingOutput = dex( ruleContext, androidSemantics, binaryJar, jarToDex, isBinaryJarFiltered, androidCommon, resourceApk.getMainDexProguardConfig(), resourceClasses, derivedJarFunction, proguardOutputMap); NestedSet<Artifact> nativeLibsZips = AndroidCommon.collectTransitiveNativeLibsZips(ruleContext).build(); Artifact finalDexes; Artifact finalProguardMap; if (rexEnabled) { finalDexes = getDxArtifact(ruleContext, "rexed_dexes.zip"); Builder rexActionBuilder = new SpawnAction.Builder(); rexActionBuilder .setExecutable(ruleContext.getExecutablePrerequisite("$rex_wrapper", Mode.HOST)) .setMnemonic("Rex") .setProgressMessage("Rexing dex files") .addArgument("--dex_input") .addInputArgument(dexingOutput.classesDexZip) .addArgument("--dex_output") .addOutputArgument(finalDexes); if (proguardOutput.getMapping() != null) { finalProguardMap = ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_MAP); Artifact finalRexPackageMap = getDxArtifact(ruleContext, "rex_output_package.map"); rexActionBuilder .addArgument("--proguard_input_map") .addInputArgument(proguardOutput.getMapping()) .addArgument("--proguard_output_map") .addOutputArgument(finalProguardMap) .addArgument("--rex_output_package_map") .addOutputArgument(finalRexPackageMap); if (ruleContext.attributes().isAttributeValueExplicitlySpecified("rex_package_map")) { Artifact rexPackageMap = ruleContext.getPrerequisiteArtifact("rex_package_map", Mode.TARGET); rexActionBuilder.addArgument("--rex_input_package_map").addInputArgument(rexPackageMap); } } else { finalProguardMap = proguardOutput.getMapping(); } // the Rex flag --keep-main-dex is used to support builds with API level below 21 that do not // support native multi-dex. This flag indicates to Rex to use the main_dex_list file which // can be provided by the user via the main_dex_list attribute or created automatically // when multidex mode is set to legacy. if (ruleContext.attributes().isAttributeValueExplicitlySpecified("main_dex_list") || getMultidexMode(ruleContext) == MultidexMode.LEGACY) { rexActionBuilder.addArgument("--keep-main-dex"); } ruleContext.registerAction(rexActionBuilder.build(ruleContext)); } else { finalDexes = dexingOutput.classesDexZip; finalProguardMap = proguardOutput.getMapping(); } if (!proguardSpecs.isEmpty()) { proguardOutput.addAllToSet(filesBuilder, finalProguardMap); } ApkSigningMethod signingMethod = ruleContext.getFragment(AndroidConfiguration.class).getApkSigningMethod(); Artifact unsignedApk = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_UNSIGNED_APK); Artifact zipAlignedApk = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_APK); ApkActionsBuilder.create("apk", signingMethod) .setClassesDex(finalDexes) .setResourceApk(resourceApk.getArtifact()) .setJavaResourceZip(dexingOutput.javaResourceJar) .setNativeLibsZips(nativeLibsZips) .setNativeLibs(nativeLibs) .setUnsignedApk(unsignedApk) .setSignedApk(zipAlignedApk) .setZipalignApk(true) .registerActions(ruleContext, androidSemantics); // Don't add blacklistedApk, so it's only built if explicitly requested. filesBuilder.add(binaryJar); filesBuilder.add(unsignedApk); filesBuilder.add(zipAlignedApk); NestedSet<Artifact> filesToBuild = filesBuilder.build(); Iterable<Artifact> dataDeps = ImmutableList.of(); if (ruleContext.attributes().has("data", BuildType.LABEL_LIST) && ruleContext.getAttributeMode("data") == Mode.DATA) { dataDeps = ruleContext.getPrerequisiteArtifacts("data", Mode.DATA).list(); } Artifact deployInfo = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.DEPLOY_INFO); AndroidDeployInfoAction.createDeployInfoAction(ruleContext, deployInfo, resourceApk.getManifest(), additionalMergedManifests, Iterables.concat(ImmutableList.of(zipAlignedApk), apksUnderTest), dataDeps); NestedSet<Artifact> coverageMetadata = (androidCommon.getInstrumentedJar() != null) ? NestedSetBuilder.create(Order.STABLE_ORDER, androidCommon.getInstrumentedJar()) : NestedSetBuilder.<Artifact>emptySet(Order.STABLE_ORDER); RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(ruleContext); Artifact incrementalApk = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_INCREMENTAL_APK); Artifact fullDeployMarker = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.FULL_DEPLOY_MARKER); Artifact incrementalDeployMarker = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.INCREMENTAL_DEPLOY_MARKER); Artifact splitDeployMarker = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.SPLIT_DEPLOY_MARKER); Artifact incrementalDexManifest = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.DEX_MANIFEST); ruleContext.registerAction(new SpawnAction.Builder() .setMnemonic("AndroidDexManifest") .setProgressMessage("Generating incremental installation manifest for " + ruleContext.getLabel()) .setExecutable( ruleContext.getExecutablePrerequisite("$build_incremental_dexmanifest", Mode.HOST)) .addOutputArgument(incrementalDexManifest) .addInputArguments(dexingOutput.shardDexZips) .useParameterFile(ParameterFileType.UNQUOTED).build(ruleContext)); Artifact stubData = ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.MOBILE_INSTALL_STUB_APPLICATION_DATA); Artifact stubDex = getStubDex(ruleContext, javaSemantics, false); ruleContext.assertNoErrors(); ApkActionsBuilder incrementalActionsBuilder = ApkActionsBuilder .create("incremental apk", signingMethod) .setClassesDex(stubDex) .setResourceApk(incrementalResourceApk.getArtifact()) .setJavaResourceZip(dexingOutput.javaResourceJar) .setNativeLibsZips(nativeLibsZips) .setJavaResourceFile(stubData) .setSignedApk(incrementalApk); if (!ruleContext.getFragment(AndroidConfiguration.class).useIncrementalNativeLibs()) { incrementalActionsBuilder.setNativeLibs(nativeLibs); } incrementalActionsBuilder.registerActions(ruleContext, androidSemantics); Artifact argsArtifact = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.MOBILE_INSTALL_ARGS); ruleContext.registerAction( new WriteAdbArgsAction(ruleContext.getActionOwner(), argsArtifact)); createInstallAction(ruleContext, false, fullDeployMarker, argsArtifact, incrementalDexManifest, incrementalResourceApk.getArtifact(), incrementalApk, nativeLibs, stubData); createInstallAction(ruleContext, true, incrementalDeployMarker, argsArtifact, incrementalDexManifest, incrementalResourceApk.getArtifact(), incrementalApk, nativeLibs, stubData); Artifact incrementalDeployInfo = ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.DEPLOY_INFO_INCREMENTAL); AndroidDeployInfoAction.createDeployInfoAction(ruleContext, incrementalDeployInfo, resourceApk.getManifest(), additionalMergedManifests, ImmutableList.<Artifact>of(), dataDeps); NestedSet<Artifact> fullInstallOutputGroup = NestedSetBuilder.<Artifact>stableOrder() .add(fullDeployMarker) .add(incrementalDeployInfo) .build(); NestedSet<Artifact> incrementalInstallOutputGroup = NestedSetBuilder.<Artifact>stableOrder() .add(incrementalDeployMarker) .add(incrementalDeployInfo) .build(); NestedSetBuilder<Artifact> splitApkSetBuilder = NestedSetBuilder.compileOrder(); // Put the Android resource APK first so that this split gets installed first. // // This avoids some logcat spam during installation, because otherwise the Android package // manager would complain about references to missing resources in the manifest during the // installation of each split (said references would eventually get installed, but it cannot // know that in advance) Artifact resourceSplitApk = getDxArtifact(ruleContext, "android_resources.apk"); ApkActionsBuilder.create("split Android resource apk", signingMethod) .setResourceApk(splitResourceApk.getArtifact()) .setSignedApk(resourceSplitApk) .registerActions(ruleContext, androidSemantics); splitApkSetBuilder.add(resourceSplitApk); for (int i = 0; i < dexingOutput.shardDexZips.size(); i++) { String splitName = "dex" + (i + 1); Artifact splitApkResources = createSplitApkResources( ruleContext, applicationManifest, splitName, true); Artifact splitApk = getDxArtifact(ruleContext, splitName + ".apk"); ApkActionsBuilder.create("split dex apk " + (i + 1), signingMethod) .setClassesDex(dexingOutput.shardDexZips.get(i)) .setResourceApk(splitApkResources) .setSignedApk(splitApk) .registerActions(ruleContext, androidSemantics); splitApkSetBuilder.add(splitApk); } Artifact nativeSplitApkResources = createSplitApkResources( ruleContext, applicationManifest, "native", false); Artifact nativeSplitApk = getDxArtifact(ruleContext, "native.apk"); ApkActionsBuilder.create("split native apk", signingMethod) .setResourceApk(nativeSplitApkResources) .setNativeLibs(nativeLibs) .setSignedApk(nativeSplitApk) .registerActions(ruleContext, androidSemantics); splitApkSetBuilder.add(nativeSplitApk); Artifact javaSplitApkResources = createSplitApkResources( ruleContext, applicationManifest, "java_resources", false); Artifact javaSplitApk = getDxArtifact(ruleContext, "java_resources.apk"); ApkActionsBuilder.create("split Java resource apk", signingMethod) .setResourceApk(javaSplitApkResources) .setJavaResourceZip(dexingOutput.javaResourceJar) .setSignedApk(javaSplitApk) .registerActions(ruleContext, androidSemantics); splitApkSetBuilder.add(javaSplitApk); Artifact splitMainApkResources = getDxArtifact(ruleContext, "split_main.ap_"); ruleContext.registerAction(new SpawnAction.Builder() .setMnemonic("AndroidStripResources") .setProgressMessage("Stripping resources from split main apk") .setExecutable(ruleContext.getExecutablePrerequisite("$strip_resources", Mode.HOST)) .addArgument("--input_resource_apk") .addInputArgument(resourceApk.getArtifact()) .addArgument("--output_resource_apk") .addOutputArgument(splitMainApkResources) .build(ruleContext)); NestedSet<Artifact> splitApks = splitApkSetBuilder.build(); Artifact splitMainApk = getDxArtifact(ruleContext, "split_main.apk"); Artifact splitStubDex = getStubDex(ruleContext, javaSemantics, true); ruleContext.assertNoErrors(); ApkActionsBuilder.create("split main apk", signingMethod) .setClassesDex(splitStubDex) .setResourceApk(splitMainApkResources) .setNativeLibsZips(nativeLibsZips) .setSignedApk(splitMainApk) .registerActions(ruleContext, androidSemantics); splitApkSetBuilder.add(splitMainApk); NestedSet<Artifact> allSplitApks = splitApkSetBuilder.build(); createSplitInstallAction(ruleContext, splitDeployMarker, argsArtifact, splitMainApk, splitApks, stubData); Artifact splitDeployInfo = ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.DEPLOY_INFO_SPLIT); AndroidDeployInfoAction.createDeployInfoAction( ruleContext, splitDeployInfo, resourceApk.getManifest(), additionalMergedManifests, ImmutableList.<Artifact>of(), dataDeps); NestedSet<Artifact> splitInstallOutputGroup = NestedSetBuilder.<Artifact>stableOrder() .addTransitive(allSplitApks) .add(splitDeployMarker) .add(splitDeployInfo) .build(); Artifact debugKeystore = androidSemantics.getApkDebugSigningKey(ruleContext); Artifact apkManifest = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.APK_MANIFEST); createApkManifestAction( ruleContext, apkManifest, false, // text proto androidCommon, resourceClasses, instantRunResourceApk, nativeLibs, debugKeystore); Artifact apkManifestText = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.APK_MANIFEST_TEXT); createApkManifestAction( ruleContext, apkManifestText, true, // text proto androidCommon, resourceClasses, instantRunResourceApk, nativeLibs, debugKeystore); androidCommon.addTransitiveInfoProviders( builder, androidSemantics, null /* aar */, resourceApk, zipAlignedApk, apksUnderTest, nativeLibs); androidSemantics.addTransitiveInfoProviders(builder, ruleContext, javaCommon, androidCommon); if (proguardOutput.getMapping() != null) { builder.add( ProguardMappingProvider.class, ProguardMappingProvider.create(finalProguardMap)); } return builder .setFilesToBuild(filesToBuild) .addProvider( RunfilesProvider.class, RunfilesProvider.simple( new Runfiles.Builder( ruleContext.getWorkspaceName(), ruleContext.getConfiguration().legacyExternalRunfiles()) .addRunfiles(ruleContext, RunfilesProvider.DEFAULT_RUNFILES) .addTransitiveArtifacts(filesToBuild) .build())) .addProvider( JavaSourceInfoProvider.class, JavaSourceInfoProvider.fromJavaTargetAttributes(resourceClasses, javaSemantics)) .addProvider( ApkProvider.class, ApkProvider.create( NestedSetBuilder.create(Order.STABLE_ORDER, zipAlignedApk), coverageMetadata, NestedSetBuilder.create(Order.STABLE_ORDER, applicationManifest.getManifest()))) .addProvider(AndroidPreDexJarProvider.class, AndroidPreDexJarProvider.create(jarToDex)) .addProvider( AndroidFeatureFlagSetProvider.class, AndroidFeatureFlagSetProvider.create( AndroidFeatureFlagSetProvider.getAndValidateFlagMapFromRuleContext(ruleContext))) .addOutputGroup("mobile_install_full" + INTERNAL_SUFFIX, fullInstallOutputGroup) .addOutputGroup( "mobile_install_incremental" + INTERNAL_SUFFIX, incrementalInstallOutputGroup) .addOutputGroup("mobile_install_split" + INTERNAL_SUFFIX, splitInstallOutputGroup) .addOutputGroup("apk_manifest", apkManifest) .addOutputGroup("apk_manifest_text", apkManifestText) .addOutputGroup("android_deploy_info", deployInfo); } private static void createSplitInstallAction(RuleContext ruleContext, Artifact marker, Artifact argsArtifact, Artifact splitMainApk, NestedSet<Artifact> splitApks, Artifact stubDataFile) { FilesToRunProvider adb = AndroidSdkProvider.fromRuleContext(ruleContext).getAdb(); SpawnAction.Builder builder = new SpawnAction.Builder() .setExecutable(ruleContext.getExecutablePrerequisite("$incremental_install", Mode.HOST)) .addTool(adb) .executeUnconditionally() .setMnemonic("AndroidInstall") .setProgressMessage("Installing " + ruleContext.getLabel() + " using split apks") .setExecutionInfo(ImmutableMap.of("local", "")) .addArgument("--output_marker") .addOutputArgument(marker) .addArgument("--stub_datafile") .addInputArgument(stubDataFile) .addArgument("--adb") .addArgument(adb.getExecutable().getExecPathString()) .addTool(adb) .addArgument("--flagfile") .addInputArgument(argsArtifact) .addArgument("--split_main_apk") .addInputArgument(splitMainApk); for (Artifact splitApk : splitApks) { builder .addArgument("--split_apk") .addInputArgument(splitApk); } ruleContext.registerAction(builder.build(ruleContext)); } private static void createInstallAction(RuleContext ruleContext, boolean incremental, Artifact marker, Artifact argsArtifact, Artifact dexmanifest, Artifact resourceApk, Artifact apk, NativeLibs nativeLibs, Artifact stubDataFile) { FilesToRunProvider adb = AndroidSdkProvider.fromRuleContext(ruleContext).getAdb(); SpawnAction.Builder builder = new SpawnAction.Builder() .setExecutable(ruleContext.getExecutablePrerequisite("$incremental_install", Mode.HOST)) // We cannot know if the user connected a new device, uninstalled the app from the device // or did anything strange to it, so we always run this action. .executeUnconditionally() .setMnemonic("AndroidInstall") .setProgressMessage( "Installing " + ruleContext.getLabel() + (incremental ? " incrementally" : "")) .setExecutionInfo(ImmutableMap.of("local", "")) .addArgument("--output_marker") .addOutputArgument(marker) .addArgument("--dexmanifest") .addInputArgument(dexmanifest) .addArgument("--resource_apk") .addInputArgument(resourceApk) .addArgument("--stub_datafile") .addInputArgument(stubDataFile) .addArgument("--adb") .addArgument(adb.getExecutable().getExecPathString()) .addTool(adb) .addArgument("--flagfile") .addInputArgument(argsArtifact); if (!incremental) { builder .addArgument("--apk") .addInputArgument(apk); } if (ruleContext.getFragment(AndroidConfiguration.class).useIncrementalNativeLibs()) { for (Map.Entry<String, NestedSet<Artifact>> arch : nativeLibs.getMap().entrySet()) { for (Artifact lib : arch.getValue()) { builder .addArgument("--native_lib") .addArgument(arch.getKey() + ":" + lib.getExecPathString()) .addInput(lib); } } } ruleContext.registerAction(builder.build(ruleContext)); } private static Artifact getStubDex( RuleContext ruleContext, JavaSemantics javaSemantics, boolean split) throws InterruptedException { String attribute = split ? "$incremental_split_stub_application" : "$incremental_stub_application"; TransitiveInfoCollection dep = ruleContext.getPrerequisite(attribute, Mode.TARGET); if (dep == null) { ruleContext.attributeError(attribute, "Stub application cannot be found"); return null; } JavaCompilationArgsProvider provider = dep.getProvider(JavaCompilationArgsProvider.class); if (provider == null) { ruleContext.attributeError(attribute, "'" + dep.getLabel() + "' should be a Java target"); return null; } JavaTargetAttributes attributes = new JavaTargetAttributes.Builder(javaSemantics) .addRuntimeClassPathEntries(provider.getJavaCompilationArgs().getRuntimeJars()) .build(); Function<Artifact, Artifact> desugaredJars = Functions.identity(); if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8()) { desugaredJars = collectDesugaredJarsFromAttributes(ruleContext, ImmutableList.of(attribute)) .build() .collapseToFunction(); } Artifact stubDeployJar = getDxArtifact(ruleContext, split ? "split_stub_deploy.jar" : "stub_deploy.jar"); new DeployArchiveBuilder(javaSemantics, ruleContext) .setOutputJar(stubDeployJar) .setAttributes(attributes) .setDerivedJarFunction(desugaredJars) .build(); Artifact stubDex = getDxArtifact( ruleContext, split ? "split_stub_application/classes.dex" : "stub_application/classes.dex"); AndroidCommon.createDexAction( ruleContext, stubDeployJar, stubDex, ImmutableList.<String>of(), false, null); return stubDex; } private static void createApkManifestAction( RuleContext ruleContext, Artifact apkManfiest, boolean textProto, final AndroidCommon androidCommon, JavaTargetAttributes resourceClasses, ResourceApk resourceApk, NativeLibs nativeLibs, Artifact debugKeystore) { // TODO(bazel-team): Sufficient to use resourceClasses.getRuntimeClasspathForArchive? // Deleting getArchiveInputs could simplify the implementation of DeployArchiveBuidler.build() Iterable<Artifact> jars = IterablesChain.concat( DeployArchiveBuilder.getArchiveInputs(resourceClasses), androidCommon.getRuntimeJars()); // The resources jars from android_library rules contain stub ids, so filter those out of the // transitive jars. Iterable<AndroidLibraryResourceClassJarProvider> libraryResourceJarProviders = AndroidCommon.getTransitivePrerequisites( ruleContext, Mode.TARGET, AndroidLibraryResourceClassJarProvider.class); NestedSetBuilder<Artifact> libraryResourceJarsBuilder = NestedSetBuilder.naiveLinkOrder(); for (AndroidLibraryResourceClassJarProvider provider : libraryResourceJarProviders) { libraryResourceJarsBuilder.addTransitive(provider.getResourceClassJars()); } NestedSet<Artifact> libraryResourceJars = libraryResourceJarsBuilder.build(); Iterable<Artifact> filteredJars = ImmutableList.copyOf( filter(jars, not(in(libraryResourceJars.toSet())))); AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); ApkManifestAction manifestAction = new ApkManifestAction( ruleContext.getActionOwner(), apkManfiest, textProto, sdk, filteredJars, resourceApk, nativeLibs, debugKeystore); ruleContext.registerAction(manifestAction); } /** Generates an uncompressed _deploy.jar of all the runtime jars. */ public static Artifact createDeployJar( RuleContext ruleContext, JavaSemantics javaSemantics, AndroidCommon common, JavaTargetAttributes attributes, Function<Artifact, Artifact> derivedJarFunction) throws InterruptedException { Artifact deployJar = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_DEPLOY_JAR); new DeployArchiveBuilder(javaSemantics, ruleContext) .setOutputJar(deployJar) .setAttributes(attributes) .addRuntimeJars(common.getRuntimeJars()) .setDerivedJarFunction(derivedJarFunction) .build(); return deployJar; } private static JavaOptimizationMode getJavaOptimizationMode(RuleContext ruleContext) { return ruleContext.getConfiguration().getFragment(JavaConfiguration.class) .getJavaOptimizationMode(); } /** * Applies the proguard specifications, and creates a ProguardedJar. Proguard's output artifacts * are added to the given {@code filesBuilder}. */ private static ProguardOutput applyProguard( RuleContext ruleContext, AndroidCommon common, JavaSemantics javaSemantics, Artifact deployJarArtifact, ImmutableList<Artifact> proguardSpecs, Artifact proguardMapping, @Nullable Artifact proguardOutputMap) throws InterruptedException { Artifact proguardOutputJar = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_PROGUARD_JAR); // Proguard will be only used for binaries which specify a proguard_spec if (proguardSpecs.isEmpty()) { // Although normally the Proguard jar artifact is not needed for binaries which do not specify // proguard_specs, targets which use a select to provide an empty list to proguard_specs will // still have a Proguard jar implicit output, as it is impossible to tell what a select will // produce at the time of implicit output determination. As a result, this artifact must // always be created. return createEmptyProguardAction(ruleContext, javaSemantics, proguardOutputJar, deployJarArtifact, proguardOutputMap); } AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); NestedSet<Artifact> libraryJars = NestedSetBuilder.<Artifact>naiveLinkOrder() .add(sdk.getAndroidJar()) .addTransitive(common.getTransitiveNeverLinkLibraries()) .build(); Artifact proguardSeeds = ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_SEEDS); Artifact proguardUsage = ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_USAGE); ProguardOutput result = ProguardHelper.createOptimizationActions( ruleContext, sdk.getProguard(), deployJarArtifact, proguardSpecs, proguardSeeds, proguardUsage, proguardMapping, libraryJars, proguardOutputJar, javaSemantics, getProguardOptimizationPasses(ruleContext), proguardOutputMap); return result; } @Nullable private static Integer getProguardOptimizationPasses(RuleContext ruleContext) { if (ruleContext.attributes().has("proguard_optimization_passes", Type.INTEGER)) { return ruleContext.attributes().get("proguard_optimization_passes", Type.INTEGER); } else { return null; } } private static ProguardOutput createEmptyProguardAction(RuleContext ruleContext, JavaSemantics semantics, Artifact proguardOutputJar, Artifact deployJarArtifact, Artifact proguardOutputMap) throws InterruptedException { NestedSetBuilder<Artifact> failures = NestedSetBuilder.<Artifact>stableOrder(); ProguardOutput outputs = ProguardHelper.getProguardOutputs( proguardOutputJar, /* proguardSeeds */ (Artifact) null, /* proguardUsage */ (Artifact) null, ruleContext, semantics, proguardOutputMap); outputs.addAllToSet(failures); JavaOptimizationMode optMode = getJavaOptimizationMode(ruleContext); ruleContext.registerAction( new FailAction( ruleContext.getActionOwner(), failures.build(), String.format("Can't run Proguard %s", optMode == JavaOptimizationMode.LEGACY ? "without proguard_specs" : "in optimization mode " + optMode))); return new ProguardOutput(deployJarArtifact, null, null, null, null, null, null); } /** Returns {@code true} if resource shrinking should be performed. */ private static boolean shouldShrinkResources(RuleContext ruleContext) { TriState state = ruleContext.attributes().get("shrink_resources", BuildType.TRISTATE); if (state == TriState.AUTO) { boolean globalShrinkResources = ruleContext.getFragment(AndroidConfiguration.class).useAndroidResourceShrinking(); state = (globalShrinkResources) ? TriState.YES : TriState.NO; } return (state == TriState.YES); } private static ResourceApk shrinkResources( RuleContext ruleContext, ResourceApk resourceApk, ImmutableList<Artifact> proguardSpecs, ProguardOutput proguardOutput, NestedSetBuilder<Artifact> filesBuilder) throws InterruptedException { if (LocalResourceContainer.definesAndroidResources(ruleContext.attributes()) && !proguardSpecs.isEmpty()) { Artifact apk = new ResourceShrinkerActionBuilder(ruleContext) .setResourceApkOut(ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_APK)) .setShrunkResourcesOut(ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_ZIP)) .setLogOut(ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.ANDROID_RESOURCE_SHRINKER_LOG)) .withResourceFiles(ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) .withShrunkJar(proguardOutput.getOutputJar()) .withProguardMapping(proguardOutput.getMapping()) .withPrimary(resourceApk.getPrimaryResource()) .withDependencies(resourceApk.getResourceDependencies()) .setResourceFilter(ResourceFilter.fromRuleContext(ruleContext)) .setUncompressedExtensions( ruleContext.getTokenizedStringListAttr("nocompress_extensions")) .build(); filesBuilder.add(ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.ANDROID_RESOURCE_SHRINKER_LOG)); return new ResourceApk(apk, resourceApk.getResourceJavaSrcJar(), resourceApk.getResourceJavaClassJar(), resourceApk.getResourceDependencies(), resourceApk.getPrimaryResource(), resourceApk.getManifest(), resourceApk.getResourceProguardConfig(), resourceApk.getMainDexProguardConfig(), resourceApk.isLegacy()); } return resourceApk; } @Immutable private static final class DexingOutput { private final Artifact classesDexZip; private final Artifact javaResourceJar; private final ImmutableList<Artifact> shardDexZips; private DexingOutput( Artifact classesDexZip, Artifact javaResourceJar, Iterable<Artifact> shardDexZips) { this.classesDexZip = classesDexZip; this.javaResourceJar = javaResourceJar; this.shardDexZips = ImmutableList.copyOf(shardDexZips); } } /** Creates one or more classes.dex files that correspond to {@code proguardedJar}. */ private static DexingOutput dex( RuleContext ruleContext, AndroidSemantics androidSemantics, Artifact binaryJar, Artifact proguardedJar, boolean isBinaryJarFiltered, AndroidCommon common, @Nullable Artifact mainDexProguardSpec, JavaTargetAttributes attributes, Function<Artifact, Artifact> derivedJarFunction, @Nullable Artifact proguardOutputMap) throws InterruptedException, RuleErrorException { List<String> dexopts = ruleContext.getTokenizedStringListAttr("dexopts"); MultidexMode multidexMode = getMultidexMode(ruleContext); if (!supportsMultidexMode(ruleContext, multidexMode)) { ruleContext.throwWithRuleError("Multidex mode \"" + multidexMode.getAttributeValue() + "\" not supported by this version of the Android SDK"); } int dexShards = ruleContext.attributes().get("dex_shards", Type.INTEGER); if (dexShards > 1) { if (multidexMode == MultidexMode.OFF) { ruleContext.throwWithRuleError(".dex sharding is only available in multidex mode"); } if (multidexMode == MultidexMode.MANUAL_MAIN_DEX) { ruleContext.throwWithRuleError(".dex sharding is not available in manual multidex mode"); } } Artifact mainDexList = ruleContext.getPrerequisiteArtifact("main_dex_list", Mode.TARGET); if ((mainDexList != null && multidexMode != MultidexMode.MANUAL_MAIN_DEX) || (mainDexList == null && multidexMode == MultidexMode.MANUAL_MAIN_DEX)) { ruleContext.throwWithRuleError( "Both \"main_dex_list\" and \"multidex='manual_main_dex'\" must be specified."); } // Always OFF if finalJarIsDerived ImmutableSet<AndroidBinaryType> incrementalDexing = getEffectiveIncrementalDexing( ruleContext, dexopts, !Objects.equals(binaryJar, proguardedJar)); Artifact inclusionFilterJar = isBinaryJarFiltered && Objects.equals(binaryJar, proguardedJar) ? binaryJar : null; if (multidexMode == MultidexMode.OFF) { // Single dex mode: generate classes.dex directly from the input jar. if (incrementalDexing.contains(AndroidBinaryType.MONODEX)) { Artifact classesDex = getDxArtifact(ruleContext, "classes.dex.zip"); Artifact jarToDex = getDxArtifact(ruleContext, "classes.jar"); createShuffleJarAction(ruleContext, true, (Artifact) null, ImmutableList.of(jarToDex), common, inclusionFilterJar, dexopts, androidSemantics, attributes, derivedJarFunction, (Artifact) null); createDexMergerAction(ruleContext, "off", jarToDex, classesDex, (Artifact) null, dexopts); return new DexingOutput(classesDex, binaryJar, ImmutableList.of(classesDex)); } else { // By *not* writing a zip we get dx to drop resources on the floor. Artifact classesDex = getDxArtifact(ruleContext, "classes.dex"); AndroidCommon.createDexAction( ruleContext, proguardedJar, classesDex, dexopts, /* multidex */ false, (Artifact) null); return new DexingOutput(classesDex, binaryJar, ImmutableList.of(classesDex)); } } else { // Multidex mode: generate classes.dex.zip, where the zip contains [classes.dex, // classes2.dex, ... classesN.dex]. if (multidexMode == MultidexMode.LEGACY) { // For legacy multidex, we need to generate a list for the dexer's --main-dex-list flag. mainDexList = createMainDexListAction( ruleContext, androidSemantics, proguardedJar, mainDexProguardSpec, proguardOutputMap); } Artifact classesDex = getDxArtifact(ruleContext, "classes.dex.zip"); if (dexShards > 1) { List<Artifact> shards = new ArrayList<>(dexShards); for (int i = 1; i <= dexShards; i++) { shards.add(getDxArtifact(ruleContext, "shard" + i + ".jar")); } Artifact javaResourceJar = createShuffleJarAction( ruleContext, incrementalDexing.contains(AndroidBinaryType.MULTIDEX_SHARDED), /*proguardedJar*/ !Objects.equals(binaryJar, proguardedJar) ? proguardedJar : null, shards, common, inclusionFilterJar, dexopts, androidSemantics, attributes, derivedJarFunction, mainDexList); List<Artifact> shardDexes = new ArrayList<>(dexShards); for (int i = 1; i <= dexShards; i++) { Artifact shard = shards.get(i - 1); Artifact shardDex = getDxArtifact(ruleContext, "shard" + i + ".dex.zip"); shardDexes.add(shardDex); if (incrementalDexing.contains(AndroidBinaryType.MULTIDEX_SHARDED)) { // If there's a main dex list then the first shard contains exactly those files. // To work with devices that lack native multi-dex support we need to make sure that // the main dex list becomes one dex file if at all possible. // Note shard here (mostly) contains of .class.dex files from shuffled dex archives, // instead of being a conventional Jar file with .class files. String multidexStrategy = mainDexList != null && i == 1 ? "minimal" : "best_effort"; createDexMergerAction(ruleContext, multidexStrategy, shard, shardDex, (Artifact) null, dexopts); } else { AndroidCommon.createDexAction( ruleContext, shard, shardDex, dexopts, /* multidex */ true, (Artifact) null); } } CommandLine mergeCommandLine = CustomCommandLine.builder() .addBeforeEachExecPath("--input_zip", shardDexes) .addExecPath("--output_zip", classesDex) .build(); ruleContext.registerAction(new SpawnAction.Builder() .setMnemonic("MergeDexZips") .setProgressMessage("Merging dex shards for " + ruleContext.getLabel()) .setExecutable(ruleContext.getExecutablePrerequisite("$merge_dexzips", Mode.HOST)) .addInputs(shardDexes) .addOutput(classesDex) .setCommandLine(mergeCommandLine) .build(ruleContext)); if (incrementalDexing.contains(AndroidBinaryType.MULTIDEX_SHARDED)) { // Using the deploy jar for java resources gives better "bazel mobile-install" performance // with incremental dexing b/c bazel can create the "incremental" and "split resource" // APKs earlier (b/c these APKs don't depend on code being dexed here). This is also done // for other multidex modes. javaResourceJar = binaryJar; } return new DexingOutput(classesDex, javaResourceJar, shardDexes); } else { if (incrementalDexing.contains(AndroidBinaryType.MULTIDEX_UNSHARDED)) { Artifact jarToDex = AndroidBinary.getDxArtifact(ruleContext, "classes.jar"); createShuffleJarAction(ruleContext, true, (Artifact) null, ImmutableList.of(jarToDex), common, inclusionFilterJar, dexopts, androidSemantics, attributes, derivedJarFunction, (Artifact) null); createDexMergerAction(ruleContext, "minimal", jarToDex, classesDex, mainDexList, dexopts); } else { // Because the dexer also places resources into this zip, we also need to create a cleanup // action that removes all non-.dex files before staging for apk building. // Create an artifact for the intermediate zip output that includes non-.dex files. Artifact classesDexIntermediate = AndroidBinary.getDxArtifact( ruleContext, "intermediate_classes.dex.zip"); // Have the dexer generate the intermediate file and the "cleaner" action consume this to // generate the final archive with only .dex files. AndroidCommon.createDexAction(ruleContext, proguardedJar, classesDexIntermediate, dexopts, /* multidex */ true, mainDexList); createCleanDexZipAction(ruleContext, classesDexIntermediate, classesDex); } return new DexingOutput(classesDex, binaryJar, ImmutableList.of(classesDex)); } } } private static ImmutableSet<AndroidBinaryType> getEffectiveIncrementalDexing( RuleContext ruleContext, List<String> dexopts, boolean isBinaryProguarded) { TriState override = ruleContext.attributes().get("incremental_dexing", BuildType.TRISTATE); // Ignore --incremental_dexing_binary_types if the incremental_dexing attribute is set, but // raise an error if proguard is enabled (b/c incompatible with incremental dexing ATM). if (isBinaryProguarded && override == TriState.YES) { ruleContext.attributeError("incremental_dexing", "target cannot be incrementally dexed because it uses Proguard"); return ImmutableSet.of(); } if (isBinaryProguarded || override == TriState.NO) { return ImmutableSet.of(); } ImmutableSet<AndroidBinaryType> result = override == TriState.YES ? ImmutableSet.copyOf(AndroidBinaryType.values()) : AndroidCommon.getAndroidConfig(ruleContext).getIncrementalDexingBinaries(); if (!result.isEmpty()) { Iterable<String> blacklistedDexopts = DexArchiveAspect.blacklistedDexopts(ruleContext, dexopts); if (!Iterables.isEmpty(blacklistedDexopts)) { // target's dexopts include flags blacklisted with --non_incremental_per_target_dexopts. If // incremental_dexing attribute is explicitly set for this target then we'll warn and // incrementally dex anyway. Otherwise, just don't incrementally dex. if (override == TriState.YES) { Iterable<String> ignored = Iterables.filter( blacklistedDexopts, Predicates.not( Predicates.in( AndroidCommon.getAndroidConfig(ruleContext) .getDexoptsSupportedInIncrementalDexing()))); ruleContext.attributeWarning("incremental_dexing", String.format("Using incremental dexing even though dexopts %s indicate this target " + "may be unsuitable for incremental dexing for the moment.%s", blacklistedDexopts, Iterables.isEmpty(ignored) ? "" : " These will be ignored: " + ignored)); } else { result = ImmutableSet.of(); } } } return result; } private static void createDexMergerAction( RuleContext ruleContext, String multidexStrategy, Artifact inputJar, Artifact classesDex, @Nullable Artifact mainDexList, Collection<String> dexopts) { SpawnAction.Builder dexmerger = new SpawnAction.Builder() .setExecutable(ruleContext.getExecutablePrerequisite("$dexmerger", Mode.HOST)) .addArgument("--input") .addInputArgument(inputJar) .addArgument("--output") .addOutputArgument(classesDex) .addArguments(DexArchiveAspect.mergerDexopts(ruleContext, dexopts)) .addArgument("--multidex=" + multidexStrategy) .setMnemonic("DexMerger") .setProgressMessage("Assembling dex files into " + classesDex.prettyPrint()); if (mainDexList != null) { dexmerger.addArgument("--main-dex-list").addInputArgument(mainDexList); } ruleContext.registerAction(dexmerger.build(ruleContext)); } /** * Returns a {@link DexArchiveProvider} of all transitively generated dex archives as well as dex * archives for the Jars produced by the binary target itself. */ public static Function<Artifact, Artifact> collectDesugaredJars( RuleContext ruleContext, AndroidCommon common, AndroidSemantics semantics, JavaTargetAttributes attributes) { if (!AndroidCommon.getAndroidConfig(ruleContext).desugarJava8()) { return Functions.identity(); } AndroidRuntimeJarProvider.Builder result = collectDesugaredJarsFromAttributes( ruleContext, semantics.getAttributesWithJavaRuntimeDeps(ruleContext)); 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) // 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 desugared = DexArchiveAspect.desugar( ruleContext, jar, attributes.getBootClassPath(), attributes.getCompileTimeClassPath(), ruleContext.getDerivedArtifact( jarPath.replaceName(jarPath.getBaseName() + "_desugared.jar"), jar.getRoot())); result.addDesugaredJar(jar, desugared); } return result.build().collapseToFunction(); } private static AndroidRuntimeJarProvider.Builder collectDesugaredJarsFromAttributes( RuleContext ruleContext, ImmutableList<String> attributes) { AndroidRuntimeJarProvider.Builder result = new AndroidRuntimeJarProvider.Builder(); for (String attr : attributes) { // Use all available AndroidRuntimeJarProvider from attributes that carry runtime dependencies result.addTransitiveProviders( ruleContext.getPrerequisites(attr, Mode.TARGET, AndroidRuntimeJarProvider.class)); } return result; } /** * Returns a {@link Map} of all transitively generated dex archives as well as dex archives for * the Jars produced by the binary target itself. */ private static Map<Artifact, Artifact> collectDexArchives( RuleContext ruleContext, AndroidCommon common, List<String> dexopts, AndroidSemantics semantics, Function<Artifact, Artifact> derivedJarFunction) { DexArchiveProvider.Builder result = new DexArchiveProvider.Builder(); for (String attr : semantics.getAttributesWithJavaRuntimeDeps(ruleContext)) { // Use all available DexArchiveProviders from attributes that carry runtime dependencies result.addTransitiveProviders( ruleContext.getPrerequisites(attr, Mode.TARGET, DexArchiveProvider.class)); } ImmutableSet<String> incrementalDexopts = 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) // 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 = DexArchiveAspect.createDexArchiveAction( ruleContext, derivedJarFunction.apply(jar), incrementalDexopts, ruleContext.getDerivedArtifact( jarPath.replaceName(jarPath.getBaseName() + ".dex.zip"), jar.getRoot())); result.addDexArchive(incrementalDexopts, dexArchive, jar); } return result.build().archivesForDexopts(incrementalDexopts); } private static Artifact createShuffleJarAction( RuleContext ruleContext, boolean useDexArchives, @Nullable Artifact proguardedJar, List<Artifact> shards, AndroidCommon common, @Nullable Artifact inclusionFilterJar, List<String> dexopts, AndroidSemantics semantics, JavaTargetAttributes attributes, Function<Artifact, Artifact> derivedJarFunction, @Nullable Artifact mainDexList) throws InterruptedException, RuleErrorException { checkArgument(mainDexList == null || shards.size() > 1); checkArgument(proguardedJar == null || inclusionFilterJar == null); Artifact javaResourceJar = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.JAVA_RESOURCES_JAR); SpawnAction.Builder shardAction = new SpawnAction.Builder() .setMnemonic("ShardClassesToDex") .setProgressMessage("Sharding classes for dexing for " + ruleContext.getLabel()) .setExecutable(ruleContext.getExecutablePrerequisite("$shuffle_jars", Mode.HOST)) .addOutputs(shards) .addOutput(javaResourceJar); CustomCommandLine.Builder shardCommandLine = CustomCommandLine.builder() .addBeforeEachExecPath("--output_jar", shards) .addExecPath("--output_resources", javaResourceJar); if (mainDexList != null) { shardCommandLine.addExecPath("--main_dex_filter", mainDexList); shardAction.addInput(mainDexList); } // If we need to run Proguard, all the class files will be in the Proguarded jar and the // deploy jar will already have been built (since it's the input of Proguard) and it will // contain all the Java resources. Otherwise, we don't want to have deploy jar creation on // the critical path, so we put all the jar files that constitute it on the inputs of the // jar shuffler. if (proguardedJar != null) { // When proguard is used we can't use dex archives, so just shuffle the proguarded jar checkArgument(!useDexArchives, "Dex archives are incompatible with Proguard"); shardCommandLine.addExecPath("--input_jar", proguardedJar); shardAction.addInput(proguardedJar); } else { Iterable<Artifact> classpath = Iterables.concat(common.getRuntimeJars(), attributes.getRuntimeClassPathForArchive()); // Check whether we can use dex archives. Besides the --incremental_dexing flag, also // make sure the "dexopts" attribute on this target doesn't mention any problematic flags. if (useDexArchives) { // Use dex archives instead of their corresponding Jars wherever we can. At this point // 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. Map<Artifact, Artifact> dexArchives = collectDexArchives(ruleContext, common, dexopts, semantics, derivedJarFunction); ImmutableList.Builder<Artifact> dexedClasspath = ImmutableList.builder(); boolean reportMissing = AndroidCommon.getAndroidConfig(ruleContext).incrementalDexingErrorOnMissedJars(); for (Artifact jar : classpath) { Artifact dexArchive = dexArchives.get(jar); if (reportMissing && dexArchive == null) { // Users can create this situation by directly depending on a .jar artifact (checked in // or coming from a genrule or similar, b/11285003). This will also catch new implicit // dependencies that incremental dexing would need to be extended to (b/34949364). // Typically the fix for the latter involves propagating DexArchiveAspect along the // attribute defining the new implicit dependency. ruleContext.throwWithAttributeError("deps", "Dependencies on .jar artifacts are not " + "allowed in Android binaries, please use a java_import to depend on " + jar.prettyPrint() + ". If this is an implicit dependency then the rule that " + "introduces it will need to be fixed to account for it correctly."); } dexedClasspath.add(dexArchive != null ? dexArchive : jar); } classpath = dexedClasspath.build(); shardCommandLine.add("--split_dexed_classes"); } else { classpath = Iterables.transform(classpath, derivedJarFunction); } shardCommandLine.addBeforeEachExecPath("--input_jar", classpath); shardAction.addInputs(classpath); if (inclusionFilterJar != null) { shardCommandLine.addExecPath("--inclusion_filter_jar", inclusionFilterJar); shardAction.addInput(inclusionFilterJar); } } shardAction.setCommandLine(shardCommandLine.build()); ruleContext.registerAction(shardAction.build(ruleContext)); return javaResourceJar; } // Adds the appropriate SpawnAction options depending on if SingleJar is a jar or not. private static SpawnAction.Builder singleJarSpawnActionBuilder(RuleContext ruleContext) { Artifact singleJar = JavaToolchainProvider.fromRuleContext(ruleContext).getSingleJar(); SpawnAction.Builder builder = new SpawnAction.Builder(); if (singleJar.getFilename().endsWith(".jar")) { builder .setJarExecutable( ruleContext.getHostConfiguration().getFragment(Jvm.class).getJavaExecutable(), singleJar, JavaToolchainProvider.fromRuleContext(ruleContext).getJvmOptions()) .addTransitiveInputs(JavaHelper.getHostJavabaseInputs(ruleContext)); } else { builder.setExecutable(singleJar); } return builder; } /** * Creates an action that copies a .zip file to a specified path, filtering all non-.dex files out * of the output. */ static void createCleanDexZipAction( RuleContext ruleContext, Artifact inputZip, Artifact outputZip) { ruleContext.registerAction( singleJarSpawnActionBuilder(ruleContext) .addArgument("--exclude_build_data") .addArgument("--dont_change_compression") .addArgument("--sources") .addInputArgument(inputZip) .addArgument("--output") .addOutputArgument(outputZip) .addArgument("--include_prefixes") .addArgument("classes") .setProgressMessage("Trimming " + inputZip.getExecPath().getBaseName()) .setMnemonic("TrimDexZip") .build(ruleContext)); } /** * Creates an action that generates a list of classes to be passed to the dexer's * --main-dex-list flag (which specifies the classes that need to be directly in classes.dex). * Returns the file containing the list. */ static Artifact createMainDexListAction( RuleContext ruleContext, AndroidSemantics androidSemantics, Artifact jar, @Nullable Artifact mainDexProguardSpec, @Nullable Artifact proguardOutputMap) throws InterruptedException { // Process the input jar through Proguard into an intermediate, streamlined jar. Artifact strippedJar = AndroidBinary.getDxArtifact(ruleContext, "main_dex_intermediate.jar"); AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); SpawnAction.Builder streamlinedBuilder = new SpawnAction.Builder() .addOutput(strippedJar) .setExecutable(sdk.getProguard()) .setProgressMessage("Generating streamlined input jar for main dex classes list") .setMnemonic("MainDexClassesIntermediate") .addArgument("-forceprocessing") .addArgument("-injars") .addInputArgument(jar) .addArgument("-libraryjars") .addInputArgument(sdk.getShrinkedAndroidJar()) .addArgument("-outjars") .addArgument(strippedJar.getExecPathString()) .addArgument("-dontwarn") .addArgument("-dontnote") .addArgument("-dontoptimize") .addArgument("-dontobfuscate") .addArgument("-dontpreverify"); List<Artifact> specs = new ArrayList<>(); specs.addAll( ruleContext.getPrerequisiteArtifacts("main_dex_proguard_specs", Mode.TARGET).list()); if (specs.isEmpty()) { specs.add(sdk.getMainDexClasses()); } if (mainDexProguardSpec != null) { specs.add(mainDexProguardSpec); } for (Artifact spec : specs) { streamlinedBuilder.addArgument("-include"); streamlinedBuilder.addInputArgument(spec); } androidSemantics .addMainDexListActionArguments(ruleContext, streamlinedBuilder, proguardOutputMap); ruleContext.registerAction(streamlinedBuilder.build(ruleContext)); // Create the main dex classes list. Artifact mainDexList = AndroidBinary.getDxArtifact(ruleContext, "main_dex_list.txt"); Builder builder = new Builder() .setMnemonic("MainDexClasses") .setProgressMessage("Generating main dex classes list"); ruleContext.registerAction(builder .setExecutable(sdk.getMainDexListCreator()) .addOutputArgument(mainDexList) .addInputArgument(strippedJar) .addInputArgument(jar) .addArguments(ruleContext.getTokenizedStringListAttr("main_dex_list_opts")) .build(ruleContext)); return mainDexList; } private static Artifact createSplitApkResources(RuleContext ruleContext, ApplicationManifest mainManifest, String splitName, boolean hasCode) { Artifact splitManifest = mainManifest.createSplitManifest(ruleContext, splitName, hasCode) .getManifest(); Artifact splitResources = getDxArtifact(ruleContext, "split_" + splitName + ".ap_"); AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext); ruleContext.registerAction(new SpawnAction.Builder() .setExecutable(sdk.getAapt()) .setMnemonic("AndroidAapt") .setProgressMessage("Generating resource apk for split " + splitName) .addArgument("package") .addArgument("-F") .addOutputArgument(splitResources) .addArgument("-M") .addInputArgument(splitManifest) .addArgument("-I") .addInputArgument(sdk.getAndroidJar()) .build(ruleContext)); return splitResources; } private static Artifact createMainDexProguardSpec(RuleContext ruleContext) { return ProguardHelper.getProguardConfigArtifact(ruleContext, "main_dex"); } /** * Tests if the resources need to be regenerated. * * <p>The resources should be regenerated (using aapt) if any of the following are true: * <ul> * <li>There is more than one resource container * <li>There are resource filters. * <li>There are extensions that should be compressed. * </ul> */ public static boolean shouldRegenerate(RuleContext ruleContext, ResourceDependencies resourceDeps) { return Iterables.size(resourceDeps.getResources()) > 1 || ResourceFilter.hasFilters(ruleContext) || ruleContext.attributes().isAttributeValueExplicitlySpecified("nocompress_extensions"); } /** * Returns the multidex mode to apply to this target. */ public static MultidexMode getMultidexMode(RuleContext ruleContext) { if (ruleContext.getRule().isAttrDefined("multidex", Type.STRING)) { return Preconditions.checkNotNull( MultidexMode.fromValue(ruleContext.attributes().get("multidex", Type.STRING))); } else { return MultidexMode.OFF; } } /** * List of Android SDKs that contain runtimes that do not support the native multidexing * introduced in Android L. If someone tries to build an android_binary that has multidex=native * set with an old SDK, we will exit with an error to alert the developer that his application * might not run on devices that the used SDK still supports. */ private static final ImmutableSet<String> RUNTIMES_THAT_DONT_SUPPORT_NATIVE_MULTIDEXING = ImmutableSet.of( "/android_sdk_linux/platforms/android_10/", "/android_sdk_linux/platforms/android_13/", "/android_sdk_linux/platforms/android_15/", "/android_sdk_linux/platforms/android_16/", "/android_sdk_linux/platforms/android_17/", "/android_sdk_linux/platforms/android_18/", "/android_sdk_linux/platforms/android_19/", "/android_sdk_linux/platforms/android_20/"); /** * Returns true if the runtime contained in the Android SDK used to build this rule supports the * given version of multidex mode specified, false otherwise. */ public static boolean supportsMultidexMode(RuleContext ruleContext, MultidexMode mode) { if (mode == MultidexMode.NATIVE) { // Native mode is not supported by Android devices running Android before v21. String runtime = AndroidSdkProvider.fromRuleContext(ruleContext).getAndroidJar().getExecPathString(); for (String blacklistedRuntime : RUNTIMES_THAT_DONT_SUPPORT_NATIVE_MULTIDEXING) { if (runtime.contains(blacklistedRuntime)) { return false; } } } return true; } /** * Returns an intermediate artifact used to support dex generation. */ public static Artifact getDxArtifact(RuleContext ruleContext, String baseName) { return ruleContext.getUniqueDirectoryArtifact("_dx", baseName, ruleContext.getBinOrGenfilesDirectory()); } }