blob: 3998de15e724128b5fbaec10b14e2e931321f15e [file] [log] [blame]
// 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.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.devtools.build.lib.collect.nestedset.Order.STABLE_ORDER;
import static com.google.devtools.build.lib.packages.BuildType.LABEL;
import static com.google.devtools.build.lib.packages.Type.STRING;
import com.google.auto.value.AutoValue;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
import com.google.devtools.build.lib.actions.CommandLine;
import com.google.devtools.build.lib.actions.FailAction;
import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
import com.google.devtools.build.lib.actions.ParamFileInfo;
import com.google.devtools.build.lib.actions.ParameterFile;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
import com.google.devtools.build.lib.analysis.Allowlist;
import com.google.devtools.build.lib.analysis.AnalysisUtils;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.OutputGroupInfo;
import com.google.devtools.build.lib.analysis.RequiredConfigFragmentsProvider;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory;
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.actions.ActionConstructionContext;
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.ParameterFileWriteAction;
import com.google.devtools.build.lib.analysis.actions.SpawnAction;
import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate;
import com.google.devtools.build.lib.analysis.actions.SpawnActionTemplate.OutputPathMapper;
import com.google.devtools.build.lib.analysis.actions.SymlinkAction;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.cmdline.Label;
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.AttributeMap;
import com.google.devtools.build.lib.packages.BuildType;
import com.google.devtools.build.lib.packages.RuleClass;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.packages.TriState;
import com.google.devtools.build.lib.packages.Type;
import com.google.devtools.build.lib.rules.android.AndroidBinaryMobileInstall.MobileInstallResourceApks;
import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode;
import com.google.devtools.build.lib.rules.android.ProguardHelper.ProguardOutput;
import com.google.devtools.build.lib.rules.android.ZipFilterBuilder.CheckHashMismatchMode;
import com.google.devtools.build.lib.rules.android.databinding.DataBinding;
import com.google.devtools.build.lib.rules.cpp.CppSemantics;
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.JavaConfiguration;
import com.google.devtools.build.lib.rules.java.JavaConfiguration.OneVersionEnforcementLevel;
import com.google.devtools.build.lib.rules.java.JavaSemantics;
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.OneVersionCheckActionBuilder;
import com.google.devtools.build.lib.rules.java.ProguardSpecProvider;
import com.google.devtools.build.lib.server.FailureDetails.FailAction.Code;
import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndData;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nullable;
import net.starlark.java.eval.StarlarkInt;
/** An implementation for the "android_binary" rule. */
public abstract class AndroidBinary implements RuleConfiguredTargetFactory {
private static final String DX_MINIMAL_MAIN_DEX_OPTION = "--minimal-main-dex";
protected abstract JavaSemantics createJavaSemantics();
protected abstract AndroidSemantics createAndroidSemantics();
protected abstract CppSemantics createCppSemantics();
@Override
@Nullable
public final ConfiguredTarget create(RuleContext ruleContext)
throws InterruptedException, RuleErrorException, ActionConflictException {
CppSemantics cppSemantics = createCppSemantics();
JavaSemantics javaSemantics = createJavaSemantics();
AndroidSemantics androidSemantics = createAndroidSemantics();
androidSemantics.checkForMigrationTag(ruleContext);
androidSemantics.validateAndroidBinaryRuleContext(ruleContext);
AndroidSdkProvider.verifyPresence(ruleContext);
NestedSetBuilder<Artifact> filesBuilder = NestedSetBuilder.stableOrder();
RuleConfiguredTargetBuilder builder =
init(ruleContext, filesBuilder, cppSemantics, javaSemantics, androidSemantics);
return builder.build();
}
@Override
public final void addRuleImplSpecificRequiredConfigFragments(
RequiredConfigFragmentsProvider.Builder requiredFragments,
AttributeMap attributes,
BuildConfigurationValue configuration) {
requiredFragments.addStarlarkOptions(AndroidFeatureFlagSetProvider.getFeatureFlags(attributes));
}
/** Checks expected rule invariants, throws rule errors if anything is set wrong. */
private static void validateRuleContext(RuleContext ruleContext, AndroidDataContext dataContext)
throws 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")) {
if (dataContext.throwOnProguardApplyMapping()) {
ruleContext.throwWithAttributeError(
"proguard_apply_mapping", "This attribute is not supported");
}
if (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("proguard_apply_dictionary")) {
if (dataContext.throwOnProguardApplyDictionary()) {
ruleContext.throwWithAttributeError(
"proguard_apply_dictionary", "This attribute is not supported");
}
if (ruleContext
.attributes()
.get(ProguardHelper.PROGUARD_SPECS, BuildType.LABEL_LIST)
.isEmpty()) {
ruleContext.throwWithAttributeError(
"proguard_apply_dictionary",
"'proguard_apply_dictionary' can only be used when 'proguard_specs' is also set");
}
}
if (ruleContext.attributes().isAttributeValueExplicitlySpecified("shrink_resources")
&& dataContext.throwOnShrinkResources()) {
ruleContext.throwWithAttributeError("shrink_resources", "This attribute is not supported");
}
if (ruleContext.attributes().isAttributeValueExplicitlySpecified("min_sdk_version")
&& Allowlist.hasAllowlist(ruleContext, "allow_min_sdk_version")
&& !Allowlist.isAvailable(ruleContext, "allow_min_sdk_version")) {
ruleContext.attributeError(
"min_sdk_version", "Target is not permitted to set min_sdk_version");
}
if (ruleContext.attributes().isAttributeValueExplicitlySpecified("startup_profiles")
&& Allowlist.hasAllowlist(ruleContext, "allow_baseline_profiles_optimizer_integration")
&& !Allowlist.isAvailable(ruleContext, "allow_baseline_profiles_optimizer_integration")) {
ruleContext.attributeError(
"startup_profiles", "Target is not permitted to use startup_profiles.");
}
if (ruleContext.getFragment(JavaConfiguration.class).enforceProguardFileExtension()
&& ruleContext.attributes().has(ProguardHelper.PROGUARD_SPECS)) {
List<PathFragment> pathsWithUnexpectedExtension =
ruleContext.getPrerequisiteArtifacts(ProguardHelper.PROGUARD_SPECS).list().stream()
.filter(Artifact::isSourceArtifact)
.map(Artifact::getRootRelativePath)
.filter(
// This checks the filename directly instead of using FileType because we want to
// exclude third_party/, but FileType is generally only given the basename.
//
// See e.g. RuleContext#validateDirectPrerequisiteType and
// PrerequisiteArtifacts#filter.
path ->
!path.getFileExtension().equals("pgcfg")
&& !path.startsWith(RuleClass.THIRD_PARTY_PREFIX)
&& !path.startsWith(RuleClass.EXPERIMENTAL_PREFIX))
.collect(toImmutableList());
if (!pathsWithUnexpectedExtension.isEmpty()) {
ruleContext.throwWithAttributeError(
ProguardHelper.PROGUARD_SPECS,
"Proguard spec files must use the .pgcfg extension. These files do not end in .pgcfg: "
+ pathsWithUnexpectedExtension);
}
}
}
private static RuleConfiguredTargetBuilder init(
RuleContext ruleContext,
NestedSetBuilder<Artifact> filesBuilder,
CppSemantics cppSemantics,
JavaSemantics javaSemantics,
AndroidSemantics androidSemantics)
throws InterruptedException, RuleErrorException {
ResourceDependencies resourceDeps =
ResourceDependencies.fromRuleDeps(ruleContext, /* neverlink= */ false);
AndroidDataContext dataContext = androidSemantics.makeContextForNative(ruleContext);
validateRuleContext(ruleContext, dataContext);
// Retrieve and compile the resources defined on the android_binary rule.
AndroidResources.validateRuleContext(ruleContext);
Map<String, String> manifestValues = StampedAndroidManifest.getManifestValues(ruleContext);
StampedAndroidManifest manifest;
if (isInstrumentation(ruleContext)
&& dataContext.getAndroidConfig().disableInstrumentationManifestMerging()) {
manifest =
AndroidManifest.fromAttributes(ruleContext, dataContext, androidSemantics)
.stamp(dataContext);
} else {
manifest =
AndroidManifest.fromAttributes(ruleContext, dataContext, androidSemantics)
.mergeWithDeps(
dataContext,
androidSemantics,
ruleContext,
resourceDeps,
manifestValues,
ruleContext.getRule().isAttrDefined("manifest_merger", STRING)
? ruleContext.attributes().get("manifest_merger", STRING)
: null);
}
boolean shrinkResourceCycles =
dataContext.shouldShrinkResourceCycles(
ruleContext, dataContext.isResourceShrinkingEnabled());
ProcessedAndroidData processedAndroidData =
ProcessedAndroidData.processBinaryDataFrom(
dataContext,
ruleContext,
manifest,
/* conditionalKeepRules= */ shrinkResourceCycles,
manifestValues,
AndroidResources.from(ruleContext, "resource_files"),
AndroidAssets.from(ruleContext),
resourceDeps,
AssetDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false),
ResourceFilterFactory.fromRuleContextAndAttrs(ruleContext),
ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions"),
ruleContext.attributes().get("crunch_png", Type.BOOLEAN),
DataBinding.contextFrom(ruleContext, dataContext.getAndroidConfig()));
AndroidApplicationResourceInfo androidApplicationResourceInfo =
ruleContext.getPrerequisite(
"application_resources", AndroidApplicationResourceInfo.PROVIDER);
final ResourceApk resourceApk;
boolean shouldCompileJavaSrcs = true;
if (androidApplicationResourceInfo == null) {
resourceApk =
new RClassGeneratorActionBuilder()
.withDependencies(resourceDeps)
.finalFields(!shrinkResourceCycles)
.setClassJarOut(
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR))
.build(dataContext, processedAndroidData);
} else {
resourceApk =
ResourceApk.fromAndroidApplicationResourceInfo(
ruleContext, dataContext.getAndroidConfig(), androidApplicationResourceInfo);
shouldCompileJavaSrcs = androidApplicationResourceInfo.shouldCompileJavaSrcs();
}
if (dataContext.useResourcePathShortening()) {
filesBuilder.add(
ruleContext.getImplicitOutputArtifact(
AndroidRuleClasses.ANDROID_RESOURCE_PATH_SHORTENING_MAP));
}
ruleContext.assertNoErrors();
JavaCommon javaCommon =
AndroidCommon.createJavaCommonWithAndroidDataBinding(
ruleContext,
javaSemantics,
resourceApk.asDataBindingContext(),
/* isLibrary */ false,
shouldCompileJavaSrcs);
javaSemantics.checkRule(ruleContext, javaCommon);
javaSemantics.checkForProtoLibraryAndJavaProtoLibraryOnSameProto(ruleContext, javaCommon);
AndroidCommon androidCommon = new AndroidCommon(javaCommon, /* asNeverLink= */ true);
// Remove the library resource JARs from the binary's runtime classpath.
// Resource classes from android_library dependencies are replaced by the binary's resource
// class. We remove them only at the top level so that resources included by a library that is
// a dependency of a java_library are still included, since these resources are propagated via
// android-specific providers and won't show up when we collect the library resource JARs.
// TODO(b/69552500): Instead, handle this properly so R JARs aren't put on the classpath for
// both binaries and libraries.
NestedSet<Artifact> excludedRuntimeArtifacts = getLibraryResourceJars(ruleContext);
JavaTargetAttributes resourceClasses =
androidCommon.init(
javaSemantics,
androidSemantics,
resourceApk,
ruleContext.getConfiguration().isCodeCoverageEnabled(),
/* collectJavaCompilationArgs= */ true,
/* isBinary= */ true,
shouldCompileJavaSrcs,
excludedRuntimeArtifacts,
/* generateExtensionRegistry= */ true);
ruleContext.assertNoErrors();
Function<Artifact, Artifact> derivedJarFunction =
collectDesugaredJars(ruleContext, androidCommon, androidSemantics, resourceClasses);
Artifact deployJar =
createDeployJar(
ruleContext,
javaSemantics,
androidCommon,
resourceClasses,
AndroidCommon.getAndroidConfig(ruleContext).checkDesugarDeps(),
derivedJarFunction);
boolean isBinaryJarFiltered = isInstrumentation(ruleContext);
if (isBinaryJarFiltered) {
deployJar = getFilteredDeployJar(ruleContext, deployJar);
}
OneVersionEnforcementLevel oneVersionEnforcementLevel =
ruleContext.getFragment(JavaConfiguration.class).oneVersionEnforcementLevel();
Artifact oneVersionOutputArtifact = null;
if (oneVersionEnforcementLevel != OneVersionEnforcementLevel.OFF) {
NestedSet<Artifact> transitiveDependencies =
NestedSetBuilder.<Artifact>stableOrder()
.addAll(
Iterables.transform(
resourceClasses.getRuntimeClassPath().toList(), derivedJarFunction))
.addAll(
Iterables.transform(
androidCommon.getJarsProducedForRuntime().toList(), derivedJarFunction))
.build();
oneVersionOutputArtifact =
OneVersionCheckActionBuilder.newBuilder()
.withEnforcementLevel(oneVersionEnforcementLevel)
.useToolchain(JavaToolchainProvider.from(ruleContext))
.checkJars(transitiveDependencies)
.build(ruleContext);
}
Artifact proguardMapping = ruleContext.getPrerequisiteArtifact("proguard_apply_mapping");
MobileInstallResourceApks mobileInstallResourceApks =
AndroidBinaryMobileInstall.createMobileInstallResourceApks(
ruleContext, dataContext, manifest);
Artifact manifestValidation = null;
boolean shouldValidateMultidex =
(Allowlist.hasAllowlist(ruleContext, "android_multidex_native_min_sdk_allowlist")
&& !Allowlist.isAvailable(ruleContext, "android_multidex_native_min_sdk_allowlist")
&& getMultidexMode(ruleContext) == MultidexMode.NATIVE);
boolean shouldValidateMinSdk = getMinSdkVersion(ruleContext) > 0;
if (ruleContext.isAttrDefined("$validate_manifest", LABEL)
&& (shouldValidateMultidex || shouldValidateMinSdk)) {
manifestValidation =
ruleContext.getPackageRelativeArtifact(
ruleContext.getLabel().getName() + "_manifest_validation_output",
ruleContext.getBinOrGenfilesDirectory());
ruleContext.registerAction(
createSpawnActionBuilder(ruleContext)
.setExecutable(ruleContext.getExecutablePrerequisite("$validate_manifest"))
.setProgressMessage("Validating %{input}")
.setMnemonic("ValidateManifest")
.addInput(manifest.getManifest())
.addOutput(manifestValidation)
.addCommandLine(
CustomCommandLine.builder()
.addExecPath("--manifest", manifest.getManifest())
.addExecPath("--output", manifestValidation)
.addFormatted(
"--validate_multidex=%s", Boolean.toString(shouldValidateMultidex))
.add(
"--expected_min_sdk_version",
Integer.toString(getMinSdkVersion(ruleContext)))
.build())
.build(ruleContext));
}
AndroidBinaryNativeLibsInfo nativeLibsInfo =
ruleContext.getPrerequisite("application_resources", AndroidBinaryNativeLibsInfo.PROVIDER);
NativeLibs nativeLibs;
if (nativeLibsInfo != null && nativeLibsInfo.getNativeLibs() != null) {
nativeLibs = nativeLibsInfo.getNativeLibs();
} else {
nativeLibs =
NativeLibs.fromLinkedNativeDeps(
ruleContext,
ImmutableList.of("deps"),
androidSemantics.getNativeDepsFileName(),
cppSemantics);
}
final NestedSet<Artifact> nativeLibsAar;
if (nativeLibsInfo != null && nativeLibsInfo.getTransitiveNativeLibs() != null) {
nativeLibsAar = nativeLibsInfo.getTransitiveNativeLibs();
} else {
nativeLibsAar = getTransitiveNativeLibs(ruleContext);
}
return createAndroidBinary(
ruleContext,
dataContext,
filesBuilder,
deployJar,
derivedJarFunction,
isBinaryJarFiltered,
androidCommon,
javaSemantics,
androidSemantics,
nativeLibs,
resourceApk,
mobileInstallResourceApks,
resourceClasses,
ImmutableList.of(),
ImmutableList.of(),
proguardMapping,
oneVersionOutputArtifact,
manifestValidation,
nativeLibsAar);
}
public static RuleConfiguredTargetBuilder createAndroidBinary(
RuleContext ruleContext,
AndroidDataContext dataContext,
NestedSetBuilder<Artifact> filesBuilder,
Artifact binaryJar,
Function<Artifact, Artifact> derivedJarFunction,
boolean isBinaryJarFiltered,
AndroidCommon androidCommon,
JavaSemantics javaSemantics,
AndroidSemantics androidSemantics,
NativeLibs nativeLibs,
ResourceApk resourceApk,
@Nullable MobileInstallResourceApks mobileInstallResourceApks,
JavaTargetAttributes resourceClasses,
ImmutableList<Artifact> apksUnderTest,
ImmutableList<Artifact> additionalMergedManifests,
Artifact proguardMapping,
@Nullable Artifact oneVersionEnforcementArtifact,
@Nullable Artifact manifestValidation,
NestedSet<Artifact> nativeLibsAar)
throws InterruptedException, RuleErrorException {
AndroidOptimizationInfo optimizationInfo =
ruleContext.getPrerequisite("application_resources", AndroidOptimizationInfo.PROVIDER);
AndroidDexInfo androidDexInfo =
ruleContext.getPrerequisite("application_resources", AndroidDexInfo.PROVIDER);
String baselineProfileDir = ruleContext.getLabel().getName() + "-baseline-profile/";
ProguardOutput proguardOutput = null;
Artifact postProcessingOutputMap = null;
Artifact finalProguardOutputMap = null;
Artifact startupProfile = null;
Artifact baselineProfile = null;
List<ProguardSpecProvider> proguardDeps = new ArrayList<>();
Iterables.addAll(
proguardDeps, ruleContext.getPrerequisites("deps", ProguardSpecProvider.PROVIDER));
Iterables.addAll(
proguardDeps,
ruleContext.getPrerequisites("application_resources", ProguardSpecProvider.PROVIDER));
if (ruleContext.getConfiguration().isCodeCoverageEnabled()
&& ruleContext.attributes().has("$jacoco_runtime", BuildType.LABEL)) {
proguardDeps.add(
ruleContext.getPrerequisite("$jacoco_runtime", ProguardSpecProvider.PROVIDER));
}
ImmutableList<Artifact> proguardSpecs =
getProguardSpecs(
dataContext,
androidSemantics,
resourceApk.getResourceProguardConfig(),
resourceApk.getManifest(),
ruleContext.attributes().has(ProguardHelper.PROGUARD_SPECS, BuildType.LABEL_LIST)
? ruleContext.getPrerequisiteArtifacts(ProguardHelper.PROGUARD_SPECS).list()
: ImmutableList.of(),
proguardDeps);
boolean hasProguardSpecs = !proguardSpecs.isEmpty();
// TODO(bazel-team): Verify that proguard spec files don't contain -printmapping directions
// which this -printmapping command line flag will override.
Artifact proguardOutputMap = null;
boolean generateProguardMap =
ProguardHelper.genProguardMapping(ruleContext.attributes())
|| dataContext.isResourceShrinkingEnabled();
boolean postprocessingRewritesMap = androidSemantics.postprocessClassesRewritesMap(ruleContext);
boolean desugarJava8LibsGeneratesMap =
AndroidCommon.getAndroidConfig(ruleContext).desugarJava8Libs();
boolean optimizingDexing = ruleContext.getExecutablePrerequisite(":optimizing_dexer") != null;
BaselineProfileProvider baselineprofileProvider =
ruleContext.getPrerequisite("application_resources", BaselineProfileProvider.PROVIDER);
if (baselineprofileProvider == null
&& Allowlist.hasAllowlist(ruleContext, "allow_baseline_profiles_optimizer_integration")
&& Allowlist.isAvailable(ruleContext, "allow_baseline_profiles_optimizer_integration")) {
baselineProfile =
androidSemantics.mergeBaselineProfiles(
ruleContext,
baselineProfileDir,
// Include startup profiles if the optimizer is disabled since profiles won't be
// merged in the optimizer.
proguardSpecs.isEmpty());
if (!proguardSpecs.isEmpty()) {
// This is only needed for optimized builds since otherwise the dexer doesn't process this.
startupProfile = androidSemantics.mergeStartupProfiles(ruleContext, baselineProfileDir);
// Wildcards only need to be expanded for optimized builds since if these aren't consumed by
// the optimizer, they can just be expanded during profile compilation instead.
// Start-up profiles are not expanded because it shouldn't be necessary as these should
// contain profiles generated by devices on start-up.
baselineProfile =
androidSemantics.expandBaselineProfileWildcards(
ruleContext, binaryJar, baselineProfile, baselineProfileDir);
}
}
if (optimizationInfo == null) {
if (generateProguardMap) {
// Determine the output of the Proguard map from shrinking the app. This depends on the
// additional steps which can process the map before the final Proguard map artifact is
// generated.
if (!hasProguardSpecs && !postprocessingRewritesMap) {
// When no shrinking happens a generating rule for the output map artifact is still
// needed.
proguardOutputMap = androidSemantics.getProguardOutputMap(ruleContext);
} else if (optimizingDexing) {
proguardOutputMap = ProguardHelper.getProguardTempArtifact(ruleContext, "pre_dexing.map");
} else if (postprocessingRewritesMap) {
// Proguard map from shrinking goes to postprocessing.
proguardOutputMap =
ProguardHelper.getProguardTempArtifact(ruleContext, "proguard_output_for_rex.map");
} else if (desugarJava8LibsGeneratesMap) {
// Proguard map from shrinking will be merged with desugared library proguard map.
proguardOutputMap =
getDxArtifact(ruleContext, "_proguard_output_for_desugared_library.map");
} else {
// Proguard map from shrinking is the final output.
proguardOutputMap = androidSemantics.getProguardOutputMap(ruleContext);
}
}
proguardOutput =
applyProguard(
ruleContext,
androidCommon,
javaSemantics,
binaryJar,
proguardSpecs,
proguardMapping,
proguardOutputMap,
startupProfile,
baselineProfile,
baselineProfileDir);
// Determine the outputs for the proguard map transition through post-processing and adding of
// desugared library map.
if (proguardOutput.hasMapping()) {
// Determine the Proguard map artifacts for the additional steps (if any) if shrinking of
// the app is enabled.
if ((optimizingDexing || postprocessingRewritesMap) && desugarJava8LibsGeneratesMap) {
// Proguard map from preprocessing will be merged with Proguard map for desugared
// library.
postProcessingOutputMap =
getDxArtifact(ruleContext, "_proguard_output_for_desugared_library.map");
finalProguardOutputMap = androidSemantics.getProguardOutputMap(ruleContext);
} else if (optimizingDexing || postprocessingRewritesMap) {
// No desugared library, Proguard map from preprocessing is the final Proguard map.
postProcessingOutputMap = androidSemantics.getProguardOutputMap(ruleContext);
finalProguardOutputMap = postProcessingOutputMap;
} else if (desugarJava8LibsGeneratesMap) {
// No postprocessing, Proguard map from merging with the desugared library map is the
// final Proguard map.
postProcessingOutputMap = proguardOutputMap;
finalProguardOutputMap = androidSemantics.getProguardOutputMap(ruleContext);
} else {
// No postprocessing, no desugared library, the final Proguard map is the Proguard map
// from shrinking
postProcessingOutputMap = proguardOutputMap;
finalProguardOutputMap = proguardOutputMap;
}
}
if (dataContext.useResourceShrinking(hasProguardSpecs)) {
resourceApk =
shrinkResources(
ruleContext,
androidSemantics.makeContextForNative(ruleContext),
resourceApk,
proguardOutput,
filesBuilder);
}
resourceApk = maybeOptimizeResources(dataContext, resourceApk, hasProguardSpecs);
} else {
proguardOutput =
new ProguardOutput(
optimizationInfo.getOptimizedJar(),
optimizationInfo.getMapping(),
optimizationInfo.getProtoMapping(),
optimizationInfo.getSeeds(),
optimizationInfo.getUsage(),
/* constantStringObfuscatedMapping= */ null,
optimizationInfo.getLibraryJar(),
optimizationInfo.getConfig(),
optimizationInfo.getRewrittenStartupProfile(),
optimizationInfo.getRewrittenMergedBaselineProfile());
if (optimizationInfo.getOptimizedResourceApk() != null) {
resourceApk = resourceApk.withApk(optimizationInfo.getOptimizedResourceApk());
} else if (optimizationInfo.getShrunkResourceApk() != null) {
resourceApk = resourceApk.withApk(optimizationInfo.getShrunkResourceApk());
}
symlinkOptimizationOutputs(
ruleContext,
androidSemantics,
javaSemantics,
dataContext,
proguardOutput,
androidDexInfo.getFinalProguardOutputMap(),
optimizationInfo.getOptimizedResourceApk(),
optimizationInfo.getShrunkResourceApk(),
optimizationInfo.getShrunkResourceZip(),
optimizationInfo.getResourceShrinkerLog(),
optimizationInfo.getResourceOptimizationConfig(),
optimizationInfo.getResourcePathShorteningMap());
}
Artifact jarToDex = proguardOutput.getOutputJar();
DexingOutput dexingOutput = null;
DexPostprocessingOutput dexPostprocessingOutput = null;
ImmutableList<Artifact> finalShardDexZips = null;
Java8LegacyDexOutput java8LegacyDexOutput = null;
// Compute the final DEX files by appending Java 8 legacy .dex if used.
final Artifact finalClassesDex;
if (androidDexInfo != null) {
finalClassesDex = androidDexInfo.getFinalClassesDexZip();
finalShardDexZips = ImmutableList.of();
if (androidDexInfo.getShuffledJavaResourceJar() != null) {
// Symlink to the java resource jar created by this android_binary's android_binary_internal
// target to satisfy its implicit output of android_binary.
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
androidDexInfo.getShuffledJavaResourceJar(), // target
ruleContext.getImplicitOutputArtifact(
AndroidRuleClasses.JAVA_RESOURCES_JAR), // symlink
"Symlinking Android shuffled java resources jar"));
}
} else {
dexingOutput =
dex(
ruleContext,
androidSemantics,
binaryJar,
jarToDex,
isBinaryJarFiltered,
androidCommon,
resourceApk.getMainDexProguardConfig(),
resourceClasses,
derivedJarFunction,
proguardOutputMap,
postProcessingOutputMap,
proguardOutput.getLibraryJar(),
proguardOutput.getStartupProfileRewritten(),
!proguardSpecs.isEmpty());
dexPostprocessingOutput =
androidSemantics.postprocessClassesDexZip(
ruleContext,
filesBuilder,
dexingOutput.classesDexZip,
proguardOutput,
postProcessingOutputMap,
dexingOutput.mainDexList);
finalShardDexZips = dexingOutput.shardDexZips;
if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8Libs()
&& dexPostprocessingOutput.classesDexZip().getFilename().endsWith(".zip")) {
if (binaryJar.equals(jarToDex)) {
// No shrinking: use canned Java 8 legacy .dex file
java8LegacyDexOutput = Java8LegacyDexOutput.getCanned(ruleContext);
} else {
// Shrinking is used: build custom Java 8 legacy .dex file
java8LegacyDexOutput = buildJava8LegacyDex(ruleContext, jarToDex);
// Merge the mapping files from shrinking the program and Java 8 legacy .dex file.
if (finalProguardOutputMap != null) {
ruleContext.registerAction(
createSpawnActionBuilder(ruleContext)
.useDefaultShellEnvironment()
.setExecutable(ruleContext.getExecutablePrerequisite("$merge_proguard_maps"))
.addInput(dexPostprocessingOutput.proguardMap())
.addInput(java8LegacyDexOutput.getMap())
.addOutput(finalProguardOutputMap)
.addCommandLine(
CustomCommandLine.builder()
.addExecPath("--pg-map", dexPostprocessingOutput.proguardMap())
.addExecPath("--pg-map", java8LegacyDexOutput.getMap())
.addExecPath("--pg-map-output", finalProguardOutputMap)
.build())
.setMnemonic("MergeProguardMaps")
.setProgressMessage(
"Merging app and desugared library Proguard maps for %{label}")
.build(ruleContext));
}
}
// Append legacy .dex library to app's .dex files
finalClassesDex = getDxArtifact(ruleContext, "_final_classes.dex.zip");
ruleContext.registerAction(
createSpawnActionBuilder(ruleContext)
.useDefaultShellEnvironment()
.setMnemonic("AppendJava8LegacyDex")
.setProgressMessage("Adding Java 8 legacy library for %{label}")
.setExecutable(ruleContext.getExecutablePrerequisite("$merge_dexzips"))
.addInput(dexPostprocessingOutput.classesDexZip())
.addInput(java8LegacyDexOutput.getDex())
.addOutput(finalClassesDex)
// Order matters here: we want java8LegacyDex to be the highest-numbered
// classesN.dex
.addCommandLine(
CustomCommandLine.builder()
.addExecPath("--input_zip", dexPostprocessingOutput.classesDexZip())
.addExecPath("--input_zip", java8LegacyDexOutput.getDex())
.addExecPath("--output_zip", finalClassesDex)
.build())
.build(ruleContext));
finalShardDexZips =
ImmutableList.<Artifact>builder()
.addAll(finalShardDexZips)
.add(java8LegacyDexOutput.getDex())
.build();
} else {
finalClassesDex = dexPostprocessingOutput.classesDexZip();
}
}
if (hasProguardSpecs) {
proguardOutput.addAllToSet(filesBuilder, finalProguardOutputMap);
}
Artifact artProfileZip = null;
if (baselineprofileProvider != null) {
// This happens when baseline profiles are provided via starlark.
artProfileZip = baselineprofileProvider.getArtProfileZip();
} else if (baselineProfile == null && startupProfile == null) {
// This happens when optimizer profile rewriting isn't enabled.
artProfileZip =
androidSemantics.getArtProfileForApk(
ruleContext, finalClassesDex, finalProguardOutputMap, baselineProfileDir);
} else {
// This happens when optimizer profile rewriting is enabled.
artProfileZip =
androidSemantics.compileBaselineProfile(
ruleContext,
finalClassesDex,
// Minified symbols are emitted when rewriting, so only use map for symbols which
// weren't passed to bytecode optimizer (if it exists).
java8LegacyDexOutput == null ? null : java8LegacyDexOutput.getMap(),
// At this point, either baseline profile here also contains startup-profiles, if any.
proguardOutput.getMergedBaselineProfileRewritten() == null
? baselineProfile
: proguardOutput.getMergedBaselineProfileRewritten(),
baselineProfileDir);
}
Artifact unsignedApk =
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_UNSIGNED_APK);
Artifact zipAlignedApk =
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_APK);
Artifact v4Signature =
(dataContext.getAndroidConfig().apkSigningMethodV4() != null
&& dataContext.getAndroidConfig().apkSigningMethodV4())
? ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_V4_SIGNATURE)
: null;
ImmutableList<Artifact> signingKeys = AndroidCommon.getApkDebugSigningKeys(ruleContext);
Artifact signingLineage = ruleContext.getPrerequisiteArtifact("debug_signing_lineage_file");
String keyRotationMinSdk = ruleContext.attributes().get("key_rotation_min_sdk", Type.STRING);
FilesToRunProvider resourceExtractor =
ruleContext.getExecutablePrerequisite("$resource_extractor");
ApkActionsBuilder actionsBuilder =
ApkActionsBuilder.create("apk")
.setClassesDex(finalClassesDex)
.addInputZip(resourceApk.getArtifact())
.setJavaResourceZip(
androidDexInfo == null
? dexingOutput.javaResourceJar
: androidDexInfo.getJavaResourceJar(),
resourceExtractor)
.addInputZips(nativeLibsAar.toList())
.setNativeLibs(nativeLibs)
.setUnsignedApk(unsignedApk)
.setSignedApk(zipAlignedApk)
.setSigningKeys(signingKeys)
.setSigningLineageFile(signingLineage)
.setSigningKeyRotationMinSdk(keyRotationMinSdk)
.setV4Signature(v4Signature)
.setZipalignApk(true)
.setDeterministicSigning(androidSemantics.deterministicSigning());
if (artProfileZip != null) {
actionsBuilder.addInputZip(artProfileZip);
}
actionsBuilder.registerActions(ruleContext);
filesBuilder.add(binaryJar);
filesBuilder.add(unsignedApk);
filesBuilder.add(zipAlignedApk);
if (v4Signature != null) {
filesBuilder.add(v4Signature);
}
NestedSet<Artifact> filesToBuild = filesBuilder.build();
Artifact deployInfo = ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.DEPLOY_INFO);
ImmutableList.Builder<Artifact> listBuilder =
ImmutableList.<Artifact>builder().add(zipAlignedApk).addAll(apksUnderTest);
if (v4Signature != null) {
listBuilder.add(v4Signature);
}
AndroidDeployInfoAction.createDeployInfoAction(
ruleContext,
deployInfo,
resourceApk.getManifest(),
additionalMergedManifests,
listBuilder.build());
RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(ruleContext);
// If this is an instrumentation APK, create the provider for android_instrumentation_test.
if (isInstrumentation(ruleContext)) {
ApkInfo targetApkProvider = ruleContext.getPrerequisite("instruments", ApkInfo.PROVIDER);
AndroidInstrumentationInfo instrumentationProvider =
new AndroidInstrumentationInfo(targetApkProvider);
builder.addNativeDeclaredProvider(instrumentationProvider);
// At this point, the Android manifests of both target and instrumentation APKs are finalized.
FilesToRunProvider checker =
ruleContext.getExecutablePrerequisite("$instrumentation_test_check");
Artifact targetManifest = targetApkProvider.getMergedManifest();
Artifact instrumentationManifest = resourceApk.getManifest();
Artifact checkOutput =
ruleContext.getImplicitOutputArtifact(
AndroidRuleClasses.INSTRUMENTATION_TEST_CHECK_RESULTS);
SpawnAction.Builder checkAction =
createSpawnActionBuilder(ruleContext)
.setExecutable(checker)
.addInput(targetManifest)
.addInput(instrumentationManifest)
.addOutput(checkOutput)
.setProgressMessage(
"Validating the merged manifests of the target and instrumentation APKs")
.setMnemonic("AndroidManifestInstrumentationCheck");
CustomCommandLine commandLine =
CustomCommandLine.builder()
.addExecPath("--instrumentation_manifest", instrumentationManifest)
.addExecPath("--target_manifest", targetManifest)
.addExecPath("--output", checkOutput)
.build();
builder.addOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL, checkOutput);
checkAction.addCommandLine(commandLine);
ruleContext.registerAction(checkAction.build(ruleContext));
}
OutputGroupInfo androidApplicationOutputGroupInfo =
ruleContext.getPrerequisite("application_resources", OutputGroupInfo.STARLARK_CONSTRUCTOR);
if (androidApplicationOutputGroupInfo != null) {
for (String key : androidApplicationOutputGroupInfo.getFieldNames()) {
builder.addOutputGroup(key, androidApplicationOutputGroupInfo.getOutputGroup(key));
}
}
androidCommon.addTransitiveInfoProviders(
builder,
/* aar= */ null,
resourceApk,
zipAlignedApk,
apksUnderTest,
nativeLibs,
androidCommon.isNeverLink(),
/* isLibrary = */ false);
ProguardMappingProvider proguardMappingProvider =
ruleContext.getPrerequisite("application_resources", ProguardMappingProvider.PROVIDER);
if (proguardMappingProvider != null) {
builder.addNativeDeclaredProvider(proguardMappingProvider);
} else if (dexPostprocessingOutput != null && dexPostprocessingOutput.proguardMap() != null) {
builder.addNativeDeclaredProvider(
new ProguardMappingProvider(dexPostprocessingOutput.proguardMap()));
}
if (oneVersionEnforcementArtifact != null) {
builder.addOutputGroup(OutputGroupInfo.HIDDEN_TOP_LEVEL, oneVersionEnforcementArtifact);
}
if (mobileInstallResourceApks != null) {
AndroidBinaryMobileInstall.addMobileInstall(
ruleContext,
builder,
androidDexInfo == null
? dexingOutput.javaResourceJar
: androidDexInfo.getJavaResourceJar(),
finalShardDexZips,
javaSemantics,
nativeLibs,
resourceApk,
mobileInstallResourceApks,
resourceExtractor,
nativeLibsAar,
signingKeys,
signingLineage,
additionalMergedManifests);
}
if (manifestValidation != null) {
builder.addOutputGroup(
OutputGroupInfo.VALIDATION, NestedSetBuilder.create(STABLE_ORDER, manifestValidation));
}
// First propagate validations from most rule attributes as usual; then handle "deps" separately
// to propagate validations from each config split but avoid known-redundant Android Lint
// validations (b/168038145, b/180746622).
// TODO(b/180746622): remove custom filtering once semantically identical actions with
// different configurations are deduped (while still propagating actions from all splits)
RuleConfiguredTargetBuilder.collectTransitiveValidationOutputGroups(
ruleContext,
attr -> !"deps".equals(attr),
validations -> builder.addOutputGroup(OutputGroupInfo.VALIDATION_TRANSITIVE, validations));
boolean filterSplitValidations = false; // propagate validations from first split unfiltered
for (List<ConfiguredTargetAndData> deps : ruleContext.getSplitPrerequisites("deps").values()) {
for (OutputGroupInfo provider :
AnalysisUtils.getProviders(
getConfiguredTargets(deps), OutputGroupInfo.STARLARK_CONSTRUCTOR)) {
NestedSet<Artifact> validations = provider.getOutputGroup(OutputGroupInfo.VALIDATION);
if (filterSplitValidations) {
// Filter out Android Lint validations by name: we know these validations are expensive
// and duplicative between splits, so arbitrarily only propagate them from the first split
// (b/180746622). While it's cheesy to rely on naming patterns, more semantic filtering
// requires a lot of work (e.g., using an aspect that observes actions by mnemonic).
NestedSetBuilder<Artifact> filtered = NestedSetBuilder.stableOrder();
validations.toList().stream()
.filter(Artifact::hasKnownGeneratingAction)
.filter(a -> !a.getFilename().endsWith("android_lint_output.xml"))
.forEach(filtered::add);
validations = filtered.build();
}
if (!validations.isEmpty()) {
builder.addOutputGroup(OutputGroupInfo.VALIDATION_TRANSITIVE, validations);
}
}
// Filter out Android Lint Validations from any subsequent split,
// because they're redundant with those in the first split.
filterSplitValidations = true;
}
AndroidPreDexJarProvider androidPreDexJarProvider =
ruleContext.getPrerequisite("application_resources", AndroidPreDexJarProvider.PROVIDER);
if (androidPreDexJarProvider != null) {
builder.addNativeDeclaredProvider(androidPreDexJarProvider);
} else {
builder.addNativeDeclaredProvider(new AndroidPreDexJarProvider(jarToDex));
}
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()))
.addNativeDeclaredProvider(
new ApkInfo(
zipAlignedApk,
unsignedApk,
binaryJar,
getCoverageInstrumentationJarForApk(ruleContext),
resourceApk.getManifest(),
signingKeys,
signingLineage,
keyRotationMinSdk))
.addNativeDeclaredProvider(
AndroidFeatureFlagSetProvider.create(
AndroidFeatureFlagSetProvider.getAndValidateFlagMapFromRuleContext(ruleContext)))
.addOutputGroup("android_deploy_info", deployInfo)
.addOutputGroup("android_deploy_info", resourceApk.getManifest());
}
public static NestedSet<Artifact> getTransitiveNativeLibs(RuleContext ruleContext) {
// Collect all native shared libraries across split transitions. Some AARs contain shared
// libraries across multiple architectures, e.g. x86 and armeabi-v7a, and need to be packed
// into the APK.
NestedSetBuilder<Artifact> transitiveNativeLibs = NestedSetBuilder.naiveLinkOrder();
for (List<ConfiguredTargetAndData> deps : ruleContext.getSplitPrerequisites("deps").values()) {
for (AndroidNativeLibsInfo provider :
AnalysisUtils.getProviders(getConfiguredTargets(deps), AndroidNativeLibsInfo.PROVIDER)) {
transitiveNativeLibs.addTransitive(provider.getNativeLibs());
}
}
return transitiveNativeLibs.build();
}
private static ImmutableList<ConfiguredTarget> getConfiguredTargets(
List<ConfiguredTargetAndData> prerequisitesList) {
return prerequisitesList.stream()
.map(ConfiguredTargetAndData::getConfiguredTarget)
.collect(toImmutableList());
}
static class Java8LegacyDexOutput {
private final Artifact dex;
private final Artifact map;
private Java8LegacyDexOutput(Artifact dex, Artifact map) {
this.dex = dex;
this.map = map;
}
static Java8LegacyDexOutput getCanned(RuleContext ruleContext) {
return new Java8LegacyDexOutput(
ruleContext.getPrerequisiteArtifact("$java8_legacy_dex"), null);
}
boolean isEmpty() {
return dex == null;
}
public Artifact getDex() {
return dex;
}
public Artifact getMap() {
return map;
}
}
private static void symlinkOptimizationOutputs(
RuleContext ruleContext,
AndroidSemantics androidSemantics,
JavaSemantics javaSemantics,
AndroidDataContext dataContext,
ProguardOutput proguardOutput,
@Nullable Artifact finalProguardOutputMap,
@Nullable Artifact optimizedResourceApk,
@Nullable Artifact shrunkResourceApk,
@Nullable Artifact shrunkResourceZip,
@Nullable Artifact resourceShrinkerLog,
@Nullable Artifact resourceOptimizationConfig,
@Nullable Artifact resourcePathShorteningMap)
throws InterruptedException {
if (proguardOutput.getOutputJar() != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
proguardOutput.getOutputJar(),
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_PROGUARD_JAR),
"Symlinking proguard output jar"));
}
if (proguardOutput.getSeeds() != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
proguardOutput.getSeeds(),
ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_SEEDS),
"Symlinking proguard seeds"));
}
if (proguardOutput.getConfig() != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
proguardOutput.getConfig(),
ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_CONFIG),
"Symlinking proguard config"));
}
if (proguardOutput.getUsage() != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
proguardOutput.getUsage(),
ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_USAGE),
"Symlinking proguard usage"));
}
if (proguardOutput.getProtoMapping() != null
&& javaSemantics.getProtoMapping(ruleContext) != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
proguardOutput.getProtoMapping(),
javaSemantics.getProtoMapping(ruleContext),
"Symlinking proguard proto mapping"));
}
// Conditionally select which output map to symlink. In the case where a select() resolves to
// an empty list we should pull the fail action artifact from the proguard processor. Otherwise
// we should prefer the final output map from dexing.
Artifact outputMap = null;
if (finalProguardOutputMap != null) {
outputMap = finalProguardOutputMap;
} else if (proguardOutput.getMapping() != null) {
outputMap = proguardOutput.getMapping();
}
if (outputMap != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
outputMap,
androidSemantics.getProguardOutputMap(ruleContext),
"Symlinking final proguard output map"));
}
if (optimizedResourceApk != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
optimizedResourceApk,
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_OPTIMIZED_APK),
"Symlinking optimized resource apk"));
}
if (shrunkResourceApk != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
shrunkResourceApk,
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_APK),
"Symlinking shrunk resource apk"));
}
if (shrunkResourceZip != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
shrunkResourceZip,
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_ZIP),
"Symlinking shrunk resource zip"));
}
if (resourceShrinkerLog != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
resourceShrinkerLog,
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCE_SHRINKER_LOG),
"Symlinking resource shrinker log"));
}
if (resourceOptimizationConfig != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
resourceOptimizationConfig,
dataContext.createOutputArtifact(
AndroidRuleClasses.ANDROID_RESOURCE_OPTIMIZATION_CONFIG),
"Symlinking resource optimization config"));
}
if (resourcePathShorteningMap != null) {
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
resourcePathShorteningMap,
dataContext.createOutputArtifact(
AndroidRuleClasses.ANDROID_RESOURCE_PATH_SHORTENING_MAP),
"Symlinking resource path shortening map"));
}
}
static Java8LegacyDexOutput buildJava8LegacyDex(RuleContext ruleContext, Artifact jarToDex)
throws RuleErrorException {
Artifact java8LegacyDexRules = getDxArtifact(ruleContext, "_java8_legacy.dex.pgcfg");
Artifact java8LegacyDex = getDxArtifact(ruleContext, "_java8_legacy.dex.zip");
Artifact java8LegacyDexMap = getDxArtifact(ruleContext, "_java8_legacy.dex.map");
Artifact androidJar = AndroidSdkProvider.fromRuleContext(ruleContext).getAndroidJar();
ruleContext.registerAction(
createSpawnActionBuilder(ruleContext)
.setExecutable(ruleContext.getExecutablePrerequisite("$build_java8_legacy_dex"))
.addInput(jarToDex)
.addInput(androidJar)
.addOutput(java8LegacyDexRules)
.addOutput(java8LegacyDex)
.addOutput(java8LegacyDexMap)
.addCommandLine(
CustomCommandLine.builder()
.addExecPath("--rules", java8LegacyDexRules)
.addExecPath("--binary", jarToDex)
.addExecPath("--android_jar", androidJar)
.addExecPath("--output", java8LegacyDex)
.addExecPath("--output_map", java8LegacyDexMap)
.build())
.setMnemonic("BuildLegacyDex")
.setProgressMessage("Building Java 8 legacy library for %{label}")
.build(ruleContext));
return new Java8LegacyDexOutput(java8LegacyDex, java8LegacyDexMap);
}
/**
* For coverage builds, this returns a Jar containing <b>un</b>instrumented bytecode for the
* coverage reporter's consumption. This method simply returns the deploy Jar. Note the deploy Jar
* is built anyway for Android binaries.
*
* @return A Jar containing uninstrumented bytecode or {@code null} for non-coverage builds
*/
@Nullable
private static Artifact getCoverageInstrumentationJarForApk(RuleContext ruleContext)
throws InterruptedException {
return ruleContext.getConfiguration().isCodeCoverageEnabled()
? ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_DEPLOY_JAR)
: null;
}
public static NestedSet<Artifact> getLibraryResourceJars(RuleContext ruleContext) {
Iterable<AndroidLibraryResourceClassJarProvider> libraryResourceJarProviders =
AndroidCommon.getTransitivePrerequisites(
ruleContext, AndroidLibraryResourceClassJarProvider.PROVIDER);
NestedSetBuilder<Artifact> libraryResourceJarsBuilder = NestedSetBuilder.naiveLinkOrder();
for (AndroidLibraryResourceClassJarProvider provider : libraryResourceJarProviders) {
libraryResourceJarsBuilder.addTransitive(provider.getResourceClassJars());
}
return libraryResourceJarsBuilder.build();
}
/**
* Generates an uncompressed _deploy.jar of all the runtime jars, or creates a link to the deploy
* jar created by this android_binary's android_binary_internal target if it is provided.
*/
public static Artifact createDeployJar(
RuleContext ruleContext,
JavaSemantics javaSemantics,
AndroidCommon common,
JavaTargetAttributes attributes,
boolean checkDesugarDeps,
Function<Artifact, Artifact> derivedJarFunction)
throws InterruptedException, RuleErrorException {
Artifact deployJar =
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_BINARY_DEPLOY_JAR);
AndroidDexInfo androidDexInfo =
ruleContext.getPrerequisite("application_resources", AndroidDexInfo.PROVIDER);
if (androidDexInfo != null && androidDexInfo.getDeployJar() != null) {
// Symlink to the deploy jar created by this android_binary's android_binary_internal target
// to satisfy the deploy jar implicit output of android_binary.
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
androidDexInfo.getDeployJar(), // target
deployJar, // symlink
"Symlinking Android deploy jar"));
} else {
new DeployArchiveBuilder(javaSemantics, ruleContext)
.setOutputJar(deployJar)
.setAttributes(attributes)
.addRuntimeJars(common.getRuntimeJars())
.setDerivedJarFunction(derivedJarFunction)
.setCheckDesugarDeps(checkDesugarDeps)
.build();
}
return deployJar;
}
/**
* 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,
@Nullable Artifact startupProfile,
@Nullable Artifact baselineProfile,
String baselineProfileDir)
throws InterruptedException, RuleErrorException {
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);
NestedSetBuilder<Artifact> libraryJars =
NestedSetBuilder.<Artifact>naiveLinkOrder().add(sdk.getAndroidJar());
if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8Libs()) {
// Proguard sees the desugared app, so it needs legacy APIs to resolve symbols
libraryJars.addTransitive(
ruleContext
.getPrerequisite("$desugared_java8_legacy_apis")
.getProvider(FileProvider.class)
.getFilesToBuild());
}
libraryJars.addTransitive(common.getTransitiveNeverLinkLibraries());
Artifact proguardSeeds =
ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_SEEDS);
Artifact proguardUsage =
ruleContext.getImplicitOutputArtifact(JavaSemantics.JAVA_BINARY_PROGUARD_USAGE);
Artifact proguardDictionary = ruleContext.getPrerequisiteArtifact("proguard_apply_dictionary");
return ProguardHelper.createOptimizationActions(
ruleContext,
sdk.getProguard(),
deployJarArtifact,
proguardSpecs,
proguardSeeds,
proguardUsage,
proguardMapping,
proguardDictionary,
libraryJars.build(),
proguardOutputJar,
javaSemantics,
getProguardOptimizationPasses(ruleContext),
proguardOutputMap,
startupProfile,
baselineProfile,
baselineProfileDir);
}
@Nullable
private static Integer getProguardOptimizationPasses(RuleContext ruleContext) {
if (ruleContext.attributes().has("proguard_optimization_passes", Type.INTEGER)) {
StarlarkInt i = ruleContext.attributes().get("proguard_optimization_passes", Type.INTEGER);
if (i != null) {
return i.toIntUnchecked();
}
}
return null;
}
private static ProguardOutput createEmptyProguardAction(
RuleContext ruleContext,
JavaSemantics semantics,
Artifact proguardOutputJar,
Artifact deployJarArtifact,
Artifact proguardOutputMap)
throws InterruptedException {
NestedSetBuilder<Artifact> failures = NestedSetBuilder.stableOrder();
ProguardOutput outputs =
ProguardHelper.getProguardOutputs(
proguardOutputJar,
/* proguardSeeds= */ null,
/* proguardUsage= */ null,
ruleContext,
semantics,
proguardOutputMap,
/* libraryJar= */ null,
/* startupProfileRewritten= */ null,
/* mergedBaselineProfileRewritten= */ null);
outputs.addAllToSet(failures);
ruleContext.registerAction(
new FailAction(
ruleContext.getActionOwner(),
failures.build().toSet(),
"Can't run Proguard without proguard_specs",
Code.PROGUARD_SPECS_MISSING));
return ProguardOutput.createEmpty(deployJarArtifact);
}
static ImmutableList<Artifact> getProguardSpecs(
AndroidDataContext dataContext,
AndroidSemantics androidSemantics,
Artifact resourceProguardConfig,
Artifact mergedManifest,
ImmutableList<Artifact> localProguardSpecs,
Iterable<ProguardSpecProvider> proguardDeps) {
ImmutableList<Artifact> proguardSpecs =
ProguardHelper.collectTransitiveProguardSpecs(
dataContext.getLabel(),
dataContext.getActionConstructionContext(),
ImmutableList.of(resourceProguardConfig),
localProguardSpecs,
proguardDeps);
boolean assumeMinSdkVersion = dataContext.getAndroidConfig().assumeMinSdkVersion();
if (!proguardSpecs.isEmpty() && assumeMinSdkVersion) {
// NB: Order here is important. We're including generated Proguard specs before the user's
// specs so that they can override values.
proguardSpecs =
ImmutableList.<Artifact>builder()
.addAll(androidSemantics.getProguardSpecsForManifest(dataContext, mergedManifest))
.addAll(proguardSpecs)
.build();
}
return proguardSpecs;
}
private static ResourceApk shrinkResources(
RuleContext ruleContext,
AndroidDataContext dataContext,
ResourceApk resourceApk,
ProguardOutput proguardOutput,
NestedSetBuilder<Artifact> filesBuilder)
throws RuleErrorException, InterruptedException {
Artifact shrunkApk =
shrinkResources(
dataContext,
resourceApk.getRTxt(),
resourceApk.getResourcesZip(),
proguardOutput.getOutputJar(),
proguardOutput.getMapping(),
ResourceFilterFactory.fromRuleContextAndAttrs(ruleContext),
ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions"));
filesBuilder.add(
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCE_SHRINKER_LOG));
return resourceApk.withApk(shrunkApk);
}
static Artifact shrinkResources(
AndroidDataContext dataContext,
ValidatedAndroidResources validatedResources,
Artifact proguardOutputJar,
Artifact proguardMapping,
ResourceFilterFactory resourceFilterFactory,
List<String> noCompressExtensions)
throws InterruptedException {
return shrinkResources(
dataContext,
Preconditions.checkNotNull(validatedResources).getRTxt(),
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP),
proguardOutputJar,
proguardMapping,
resourceFilterFactory,
noCompressExtensions);
}
private static Artifact shrinkResources(
AndroidDataContext dataContext,
Artifact rTxt,
Artifact resourcesZip,
Artifact proguardOutputJar,
Artifact proguardMapping,
ResourceFilterFactory resourceFilterFactory,
List<String> noCompressExtensions)
throws InterruptedException {
ResourceShrinkerActionBuilder resourceShrinkerActionBuilder =
new ResourceShrinkerActionBuilder()
.setResourceApkOut(
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_APK))
.setShrunkResourcesOut(
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_ZIP))
.setLogOut(
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCE_SHRINKER_LOG))
.withResourceFiles(resourcesZip)
.withShrunkJar(proguardOutputJar)
.withProguardMapping(proguardMapping)
.withRTxt(rTxt)
.setResourceFilterFactory(resourceFilterFactory)
.setUncompressedExtensions(noCompressExtensions)
.setResourceOptimizationConfigOut(
dataContext.createOutputArtifact(
AndroidRuleClasses.ANDROID_RESOURCE_OPTIMIZATION_CONFIG));
return resourceShrinkerActionBuilder.build(dataContext);
}
private static ResourceApk maybeOptimizeResources(
AndroidDataContext dataContext, ResourceApk resourceApk, boolean hasProguardSpecs)
throws InterruptedException {
boolean useResourcePathShortening = dataContext.useResourcePathShortening();
boolean useResourceNameObfuscation = dataContext.useResourceNameObfuscation(hasProguardSpecs);
if (!useResourcePathShortening && !useResourceNameObfuscation) {
return resourceApk;
}
Artifact optimizedApk =
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_OPTIMIZED_APK);
Aapt2OptimizeActionBuilder.Builder builder =
Aapt2OptimizeActionBuilder.builder()
.setResourceApk(resourceApk.getArtifact())
.setOptimizedApkOut(optimizedApk);
if (useResourcePathShortening) {
builder.setResourcePathShorteningMapOut(
dataContext.createOutputArtifact(
AndroidRuleClasses.ANDROID_RESOURCE_PATH_SHORTENING_MAP));
}
if (useResourceNameObfuscation) {
builder.setResourceOptimizationConfig(
dataContext.createOutputArtifact(
AndroidRuleClasses.ANDROID_RESOURCE_OPTIMIZATION_CONFIG));
}
builder.build().registerAction(dataContext);
return resourceApk.withApk(optimizedApk);
}
@Immutable
static final class DexingOutput {
private final Artifact classesDexZip;
final Artifact javaResourceJar;
final ImmutableList<Artifact> shardDexZips;
// This is not technically and output of dexing, but the processed main dex list that was used
// in dexing.
final Artifact mainDexList;
private DexingOutput(
Artifact classesDexZip,
Artifact javaResourceJar,
ImmutableList<Artifact> shardDexZips,
Artifact mainDexList) {
this.classesDexZip = classesDexZip;
this.javaResourceJar = javaResourceJar;
this.shardDexZips = Preconditions.checkNotNull(shardDexZips);
this.mainDexList = mainDexList;
}
}
/** All artifacts modified by any dex post-processing steps. */
@AutoValue
public abstract static class DexPostprocessingOutput {
public static DexPostprocessingOutput create(Artifact classesDexZip, Artifact proguardMap) {
return new AutoValue_AndroidBinary_DexPostprocessingOutput(classesDexZip, proguardMap);
}
/** A .zip of .dex files to include in the APK. */
abstract Artifact classesDexZip();
/**
* The proguard mapping corresponding to the post-processed dex files. This may be null if
* proguard was not run.
*/
@Nullable
abstract Artifact proguardMap();
}
/** 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,
@Nullable Artifact postProcessingOutputMap,
@Nullable Artifact libraryJar,
@Nullable Artifact startupProfile,
boolean isOptimizedBuild)
throws InterruptedException, RuleErrorException {
FilesToRunProvider optimizingDexer = ruleContext.getExecutablePrerequisite(":optimizing_dexer");
List<String> dexopts = ruleContext.getExpander().withDataLocations().tokenized("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 minSdkVersion = getMinSdkVersion(ruleContext);
int dexShards = ruleContext.attributes().get("dex_shards", Type.INTEGER).toIntUnchecked();
if (dexShards > 1) {
if (multidexMode == MultidexMode.MANUAL_MAIN_DEX) {
ruleContext.throwWithRuleError(".dex sharding is not available in manual multidex mode");
}
}
Artifact mainDexList = ruleContext.getPrerequisiteArtifact("main_dex_list");
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.");
}
boolean usesDexArchives =
getEffectiveIncrementalDexing(
ruleContext, dexopts, !Objects.equals(binaryJar, proguardedJar));
Artifact inclusionFilterJar =
isBinaryJarFiltered && Objects.equals(binaryJar, proguardedJar) ? binaryJar : null;
Artifact singleJarToDex = !Objects.equals(binaryJar, proguardedJar) ? proguardedJar : null;
Artifact javaResourceSourceJar =
AndroidCommon.getAndroidConfig(ruleContext).getJavaResourcesFromOptimizedJar()
? proguardedJar
: binaryJar;
// 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);
} else if (multidexMode == MultidexMode.MANUAL_MAIN_DEX) {
mainDexList =
transformDexListThroughProguardMapAction(ruleContext, proguardOutputMap, mainDexList);
}
Artifact classesDex = getDxArtifact(ruleContext, "classes.dex.zip");
if (optimizingDexer != null && isOptimizedBuild) {
SpawnAction.Builder dexAction =
new SpawnAction.Builder()
.useDefaultShellEnvironment()
.setExecutable(optimizingDexer)
.setProgressMessage("Optimized dexing for %{label}")
.setMnemonic("OptimizingDex")
.addInput(proguardedJar)
.addOutput(classesDex);
boolean nativeMultidex = multidexMode == MultidexMode.NATIVE;
CustomCommandLine.Builder dexCommand =
CustomCommandLine.builder()
.addExecPath(proguardedJar)
.add("--release")
.add("--no-desugaring")
.addExecPath("--output", classesDex)
.addAll(dexopts);
if (proguardOutputMap != null) {
dexAction.addInput(proguardOutputMap).addOutput(postProcessingOutputMap);
dexCommand
.addExecPath("--pg-map", proguardOutputMap)
.addExecPath("--pg-map-output", postProcessingOutputMap);
}
if (startupProfile != null && nativeMultidex) {
dexAction.addInput(startupProfile);
dexCommand.addExecPath("--startup-profile", startupProfile);
}
// TODO(b/261110876): Pass min SDK through here based on the value in the merged manifest. The
// current value is statically defined for the entire depot.
// We currently set the minimum SDK version to 21 if you are doing native multidex as that is
// required for native multidex to work in the first place and as a result is required for
// correct behavior from the dexer.
int sdk = nativeMultidex ? Math.max(21, minSdkVersion) : minSdkVersion;
if (sdk != 0) {
dexCommand.add("--min-api", Integer.toString(sdk));
}
if (mainDexList != null) {
dexCommand.addExecPath("--main-dex-list", mainDexList);
dexAction.addInput(mainDexList);
}
if (libraryJar != null) {
dexCommand.addExecPath("--lib", libraryJar);
dexAction.addInput(libraryJar);
}
dexAction.addCommandLine(dexCommand.build());
ruleContext.registerAction(dexAction.build(ruleContext));
return new DexingOutput(
classesDex, javaResourceSourceJar, ImmutableList.of(classesDex), mainDexList);
} else if (dexShards > 1) {
ImmutableList<Artifact> shards =
makeShardArtifacts(ruleContext, dexShards, usesDexArchives ? ".jar.dex.zip" : ".jar");
Artifact javaResourceJar =
createShuffleJarActions(
ruleContext,
usesDexArchives,
singleJarToDex,
shards,
common,
inclusionFilterJar,
dexopts,
minSdkVersion,
androidSemantics,
attributes,
derivedJarFunction,
mainDexList);
ImmutableList.Builder<Artifact> shardDexesBuilder = ImmutableList.builder();
for (int i = 1; i <= dexShards; i++) {
Artifact shard = shards.get(i - 1);
Artifact shardDex = getDxArtifact(ruleContext, "shard" + i + ".dex.zip");
shardDexesBuilder.add(shardDex);
if (usesDexArchives) {
// 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.
createDexMergerAction(
ruleContext,
mainDexList != null && i == 1 ? "minimal" : "best_effort",
ImmutableList.of(shard),
shardDex,
/*mainDexList=*/ null,
dexopts);
} else {
AndroidCommon.createDexAction(
ruleContext, shard, shardDex, dexopts, minSdkVersion, /*mainDexList=*/ null);
}
}
ImmutableList<Artifact> shardDexes = shardDexesBuilder.build();
CommandLine mergeCommandLine =
CustomCommandLine.builder()
.addExecPaths(VectorArg.addBefore("--input_zip").each(shardDexes))
.addExecPath("--output_zip", classesDex)
.build();
ruleContext.registerAction(
createSpawnActionBuilder(ruleContext)
.useDefaultShellEnvironment()
.setMnemonic("MergeDexZips")
.setProgressMessage("Merging dex shards for %s", ruleContext.getLabel())
.setExecutable(ruleContext.getExecutablePrerequisite("$merge_dexzips"))
.addInputs(shardDexes)
.addOutput(classesDex)
.addCommandLine(mergeCommandLine)
.build(ruleContext));
if (usesDexArchives) {
// 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 = javaResourceSourceJar;
}
return new DexingOutput(classesDex, javaResourceJar, shardDexes, mainDexList);
} else {
if (usesDexArchives) {
createIncrementalDexingActions(
ruleContext,
singleJarToDex,
common,
inclusionFilterJar,
dexopts,
minSdkVersion,
androidSemantics,
attributes,
derivedJarFunction,
mainDexList,
classesDex);
} 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,
minSdkVersion,
mainDexList);
createCleanDexZipAction(ruleContext, classesDexIntermediate, classesDex);
}
return new DexingOutput(
classesDex, javaResourceSourceJar, ImmutableList.of(classesDex), mainDexList);
}
}
/** Helper that sets up dexbuilder/dexmerger actions when dex_shards attribute is not set. */
private static void createIncrementalDexingActions(
RuleContext ruleContext,
@Nullable Artifact proguardedJar,
AndroidCommon common,
@Nullable Artifact inclusionFilterJar,
List<String> dexopts,
int minSdkVersion,
AndroidSemantics androidSemantics,
JavaTargetAttributes attributes,
Function<Artifact, Artifact> derivedJarFunction,
@Nullable Artifact mainDexList,
Artifact classesDex)
throws InterruptedException, RuleErrorException {
ImmutableList<Artifact> dexArchives;
if (proguardedJar == null
&& AndroidCommon.getAndroidConfig(ruleContext).incrementalDexingUseDexSharder()) {
dexArchives =
toDexedClasspath(
ruleContext,
collectRuntimeJars(common, attributes),
collectDexArchives(
ruleContext,
common,
dexopts,
minSdkVersion,
androidSemantics,
derivedJarFunction));
} else {
if (proguardedJar != null
&& AndroidCommon.getAndroidConfig(ruleContext).incrementalDexingShardsAfterProguard()
> 1) {
// TODO(b/69816569): Also use this logic if #shards > #Jars on runtime classpath
dexArchives =
makeShardArtifacts(
ruleContext,
AndroidCommon.getAndroidConfig(ruleContext).incrementalDexingShardsAfterProguard(),
".jar.dex.zip");
} else {
dexArchives = ImmutableList.of(AndroidBinary.getDxArtifact(ruleContext, "classes.jar"));
}
if (proguardedJar != null && dexArchives.size() == 1) {
// No need to shuffle, just run proguarded Jar through dexbuilder
DexArchiveAspect.createDexArchiveAction(
ruleContext,
"$dexbuilder_after_proguard",
proguardedJar,
DexArchiveAspect.topLevelDexbuilderDexopts(dexopts),
minSdkVersion,
dexArchives.get(0));
} else {
createShuffleJarActions(
ruleContext,
/*makeDexArchives=*/ true,
proguardedJar,
dexArchives,
common,
inclusionFilterJar,
dexopts,
minSdkVersion,
androidSemantics,
attributes,
derivedJarFunction,
null);
inclusionFilterJar = null;
}
}
if (dexArchives.size() == 1) {
checkState(inclusionFilterJar == null);
createDexMergerAction(ruleContext, "minimal", dexArchives, classesDex, mainDexList, dexopts);
} else {
SpecialArtifact shardsToMerge =
createSharderAction(ruleContext, dexArchives, mainDexList, dexopts, inclusionFilterJar);
SpecialArtifact multidexShards =
ruleContext.getTreeArtifact(
ruleContext.getUniqueDirectory("dexfiles"), ruleContext.getBinOrGenfilesDirectory());
FilesToRunProvider dexMerger = ruleContext.getExecutablePrerequisite("$dexmerger");
createTemplatedMergerActions(ruleContext, multidexShards, shardsToMerge, dexopts, dexMerger);
// TODO(b/69431301): avoid this action and give the files to apk build action directly
createZipMergeAction(ruleContext, multidexShards, classesDex);
}
}
private static ImmutableList<Artifact> makeShardArtifacts(
RuleContext ruleContext, int shardCount, String suffix) {
ImmutableList.Builder<Artifact> shardsBuilder = ImmutableList.builder();
for (int i = 1; i <= shardCount; i++) {
shardsBuilder.add(getDxArtifact(ruleContext, "shard" + i + suffix));
}
return shardsBuilder.build();
}
/**
* Returns whether incremental dexing should actually be used based on the --incremental_dexing
* flag, the incremental_dexing attribute and the target's dexopts.
*/
private static boolean getEffectiveIncrementalDexing(
RuleContext ruleContext, List<String> dexopts, boolean isBinaryProguarded) {
TriState override = ruleContext.attributes().get("incremental_dexing", BuildType.TRISTATE);
AndroidConfiguration config = AndroidCommon.getAndroidConfig(ruleContext);
// Ignore --incremental_dexing if the incremental_dexing attribute is set, but require the
// attribute to be YES for proguarded binaries and binaries with forbidden dexopts.
if (isBinaryProguarded
&& override == TriState.YES
&& config.incrementalDexingShardsAfterProguard() <= 0) {
ruleContext.attributeError(
"incremental_dexing", "target cannot be incrementally dexed because it uses Proguard");
return false;
}
if (override == TriState.NO) {
return false;
}
if (override == TriState.YES || config.useIncrementalDexing()) {
if (isBinaryProguarded) {
return override == TriState.YES || config.incrementalDexingAfterProguardByDefault();
}
Iterable<String> forbiddenDexopts = DexArchiveAspect.forbiddenDexopts(ruleContext, dexopts);
if (Iterables.isEmpty(forbiddenDexopts)) {
// target's dexopts are all compatible with incremental dexing.
return true;
} else if (override == TriState.YES) {
// target's dexopts include forbidden flags 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.
Iterable<String> ignored =
Iterables.filter(
forbiddenDexopts,
Predicates.not(Predicates.in(config.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",
forbiddenDexopts,
Iterables.isEmpty(ignored) ? "" : " Ignored dexopts: " + ignored));
return true;
} else {
// If there are incompatible dexopts and the attribute is not set, we silently don't run
// incremental dexing.
return false;
}
} else {
// attribute is auto and flag is false
return false;
}
}
/**
* Sets up a {@code $dexsharder} action for the given {@code dexArchives} and returns the output
* tree artifact.
*
* @return Tree artifact containing dex archives to merge into exactly one .dex file each
*/
private static SpecialArtifact createSharderAction(
RuleContext ruleContext,
ImmutableList<Artifact> dexArchives,
@Nullable Artifact mainDexList,
Collection<String> dexopts,
@Nullable Artifact inclusionFilterJar) {
SpecialArtifact outputTree =
ruleContext.getTreeArtifact(
ruleContext.getUniqueDirectory("dexsplits"), ruleContext.getBinOrGenfilesDirectory());
SpawnAction.Builder shardAction =
createSpawnActionBuilder(ruleContext)
.useDefaultShellEnvironment()
.setMnemonic("ShardForMultidex")
.setProgressMessage(
"Assembling dex files for %s", ruleContext.getLabel().getCanonicalForm())
.setExecutable(ruleContext.getExecutablePrerequisite("$dexsharder"))
.addInputs(dexArchives)
.addOutput(outputTree);
CustomCommandLine.Builder shardCommandLine =
CustomCommandLine.builder()
.addExecPaths(VectorArg.addBefore("--input").each(dexArchives))
.addExecPath("--output", outputTree);
if (mainDexList != null) {
shardAction.addInput(mainDexList);
shardCommandLine.addExecPath("--main-dex-list", mainDexList);
}
shardCommandLine.addAll(DexArchiveAspect.sharderDexopts(ruleContext, dexopts));
if (inclusionFilterJar != null) {
shardCommandLine.addExecPath("--inclusion_filter_jar", inclusionFilterJar);
shardAction.addInput(inclusionFilterJar);
}
ruleContext.registerAction(
shardAction
.addCommandLine(
shardCommandLine.build(),
// Classpaths can be long--overflow into @params file if necessary
ParamFileInfo.builder(ParameterFile.ParameterFileType.SHELL_QUOTED).build())
.build(ruleContext));
return outputTree;
}
/**
* Sets up a monodex {@code $dexmerger} actions for each dex archive in the given tree artifact
* and puts the outputs in a tree artifact.
*/
public static void createTemplatedMergerActions(
RuleContext ruleContext,
SpecialArtifact outputTree,
SpecialArtifact inputTree,
List<String> dexopts,
FilesToRunProvider executable) {
SpawnActionTemplate.Builder dexmerger =
new SpawnActionTemplate.Builder(inputTree, outputTree)
.setExecutable(executable)
.setMnemonics("DexShardsToMerge", "DexMerger")
.setOutputPathMapper(
(OutputPathMapper & Serializable) TreeFileArtifact::getParentRelativePath);
CustomCommandLine.Builder commandLine =
CustomCommandLine.builder()
.addPlaceholderTreeArtifactExecPath("--input", inputTree)
.addPlaceholderTreeArtifactExecPath("--output", outputTree)
.add("--multidex=given_shard")
.addAll(
DexArchiveAspect.mergerDexopts(
ruleContext,
Iterables.filter(
dexopts, Predicates.not(Predicates.equalTo(DX_MINIMAL_MAIN_DEX_OPTION)))));
dexmerger.setCommandLineTemplate(commandLine.build());
ruleContext.registerAction(dexmerger.build(ruleContext.getActionOwner()));
}
private static void createZipMergeAction(
RuleContext ruleContext, Artifact inputTree, Artifact outputZip) throws RuleErrorException {
CustomCommandLine args =
CustomCommandLine.builder()
.add("--normalize")
.add("--exclude_build_data")
.add("--dont_change_compression")
.add("--sources")
.addExpandedTreeArtifactExecPaths(inputTree)
.addExecPath("--output", outputZip)
.add("--no_duplicates") // safety: expect distinct entry names in all inputs
.build();
// Must use params file as otherwise expanding the input tree artifact doesn't work
Artifact paramFile =
ruleContext.getDerivedArtifact(
ParameterFile.derivePath(
outputZip.getOutputDirRelativePath(
ruleContext.getConfiguration().isSiblingRepositoryLayout())),
outputZip.getRoot());
ruleContext.registerAction(
new ParameterFileWriteAction(
ruleContext.getActionOwner(),
NestedSetBuilder.create(Order.STABLE_ORDER, inputTree),
paramFile,
args,
ParameterFile.ParameterFileType.SHELL_QUOTED));
ruleContext.registerAction(
singleJarSpawnActionBuilder(ruleContext)
.setMnemonic("MergeDexZips")
.setProgressMessage("Merging dex shards for %s", ruleContext.getLabel())
.addInput(inputTree)
.addInput(paramFile)
.addOutput(outputZip)
.addCommandLine(CustomCommandLine.builder().addPrefixedExecPath("@", paramFile).build())
.build(ruleContext));
}
private static void createDexMergerAction(
RuleContext ruleContext,
String multidexStrategy,
ImmutableList<Artifact> dexArchives,
Artifact classesDex,
@Nullable Artifact mainDexList,
Collection<String> dexopts) {
SpawnAction.Builder dexmerger =
createSpawnActionBuilder(ruleContext)
.useDefaultShellEnvironment()
.setExecutable(ruleContext.getExecutablePrerequisite("$dexmerger"))
.setMnemonic("DexMerger")
.setProgressMessage("Assembling dex files into %s", classesDex.getRootRelativePath())
.addInputs(dexArchives)
.addOutput(classesDex);
CustomCommandLine.Builder commandLine =
CustomCommandLine.builder()
.addExecPaths(VectorArg.addBefore("--input").each(dexArchives))
.addExecPath("--output", classesDex)
.addAll(DexArchiveAspect.mergerDexopts(ruleContext, dexopts))
.addPrefixed("--multidex=", multidexStrategy);
if (mainDexList != null) {
dexmerger.addInput(mainDexList);
commandLine.addExecPath("--main-dex-list", mainDexList);
}
dexmerger.addCommandLine(
commandLine.build(),
// Classpaths can be long--overflow into @params file if necessary
ParamFileInfo.builder(ParameterFile.ParameterFileType.SHELL_QUOTED).build());
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)
throws RuleErrorException {
if (!AndroidCommon.getAndroidConfig(ruleContext).desugarJava8()) {
return Functions.identity();
}
AndroidRuntimeJarProvider.Builder result =
collectDesugaredJarsFromAttributes(
ruleContext, semantics.getAttributesWithJavaRuntimeDeps(ruleContext));
for (Artifact jar : common.getJarsProducedForRuntime().toList()) {
// 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.getOutputDirRelativePath(ruleContext.getConfiguration().isSiblingRepositoryLayout());
Artifact desugared =
DexArchiveAspect.desugar(
ruleContext,
jar,
attributes.getBootClassPath().bootclasspath(),
attributes.getCompileTimeClassPath(),
getMinSdkVersion(ruleContext),
ruleContext.getDerivedArtifact(
jarPath.replaceName(jarPath.getBaseName() + "_desugared.jar"), jar.getRoot()));
result.addDesugaredJar(jar, desugared);
}
return result.build().collapseToFunction();
}
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, 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,
int minSdkVersion,
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, DexArchiveProvider.class));
}
ImmutableSet<String> incrementalDexopts =
DexArchiveAspect.incrementalDexopts(ruleContext, dexopts);
for (Artifact jar : common.getJarsProducedForRuntime().toList()) {
// 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.getOutputDirRelativePath(ruleContext.getConfiguration().isSiblingRepositoryLayout());
Artifact dexArchive =
DexArchiveAspect.createDexArchiveAction(
ruleContext,
"$dexbuilder",
derivedJarFunction.apply(jar),
incrementalDexopts,
minSdkVersion,
ruleContext.getDerivedArtifact(
jarPath.replaceName(jarPath.getBaseName() + ".dex.zip"), jar.getRoot()));
result.addDexArchive(incrementalDexopts, dexArchive, jar);
}
return result.build().archivesForDexopts(incrementalDexopts);
}
private static Artifact createShuffleJarActions(
RuleContext ruleContext,
boolean makeDexArchives,
@Nullable Artifact proguardedJar,
ImmutableList<Artifact> shards,
AndroidCommon common,
@Nullable Artifact inclusionFilterJar,
List<String> dexopts,
int minSdkVersion,
AndroidSemantics semantics,
JavaTargetAttributes attributes,
Function<Artifact, Artifact> derivedJarFunction,
@Nullable Artifact mainDexList)
throws InterruptedException, RuleErrorException {
checkArgument(!shards.isEmpty());
checkArgument(mainDexList == null || shards.size() > 1);
checkArgument(proguardedJar == null || inclusionFilterJar == null);
Artifact javaResourceJar =
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.JAVA_RESOURCES_JAR);
ImmutableList<Artifact> shuffleOutputs;
if (makeDexArchives && proguardedJar != null) {
checkArgument(shards.size() > 1);
// Split proguardedJar into N shards and run dexbuilder over each one below
shuffleOutputs = makeShardArtifacts(ruleContext, shards.size(), ".jar");
} else {
shuffleOutputs = shards;
}
SpawnAction.Builder shardAction =
createSpawnActionBuilder(ruleContext)
.useDefaultShellEnvironment()
.setMnemonic("ShardClassesToDex")
.setProgressMessage("Sharding classes for dexing for %s", ruleContext.getLabel())
.setExecutable(ruleContext.getExecutablePrerequisite("$shuffle_jars"))
.addOutputs(shuffleOutputs)
.addOutput(javaResourceJar);
CustomCommandLine.Builder shardCommandLine =
CustomCommandLine.builder()
.addExecPaths(VectorArg.addBefore("--output_jar").each(shuffleOutputs))
.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, which has to
// be converted to dex. Otherwise we can use the transitive classpath directly and can leverage
// incremental dexing outputs for classpath Jars if applicable.
if (proguardedJar != null) {
shardCommandLine.addExecPath("--input_jar", proguardedJar);
shardAction.addInput(proguardedJar);
} else {
ImmutableList<Artifact> classpath = collectRuntimeJars(common, attributes);
// 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 (makeDexArchives) {
// 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, minSdkVersion, semantics, derivedJarFunction);
classpath = toDexedClasspath(ruleContext, classpath, dexArchives);
shardCommandLine.add("--split_dexed_classes");
} else {
classpath = classpath.stream().map(derivedJarFunction).collect(toImmutableList());
}
shardCommandLine.addExecPaths(VectorArg.addBefore("--input_jar").each(classpath));
shardAction.addInputs(classpath);
if (inclusionFilterJar != null) {
shardCommandLine.addExecPath("--inclusion_filter_jar", inclusionFilterJar);
shardAction.addInput(inclusionFilterJar);
}
}
shardAction.addCommandLine(
shardCommandLine.build(), ParamFileInfo.builder(ParameterFileType.SHELL_QUOTED).build());
ruleContext.registerAction(shardAction.build(ruleContext));
if (makeDexArchives && proguardedJar != null) {
for (int i = 0; i < shards.size(); ++i) {
checkState(!shuffleOutputs.get(i).equals(shards.get(i)));
DexArchiveAspect.createDexArchiveAction(
ruleContext,
"$dexbuilder_after_proguard",
shuffleOutputs.get(i),
DexArchiveAspect.topLevelDexbuilderDexopts(dexopts),
minSdkVersion,
shards.get(i));
}
}
return javaResourceJar;
}
private static ImmutableList<Artifact> collectRuntimeJars(
AndroidCommon common, JavaTargetAttributes attributes) {
return ImmutableList.<Artifact>builder()
.addAll(common.getRuntimeJars())
.addAll(attributes.getRuntimeClassPathForArchive().toList())
.build();
}
private static ImmutableList<Artifact> toDexedClasspath(
RuleContext ruleContext,
ImmutableList<Artifact> classpath,
Map<Artifact, Artifact> dexArchives)
throws RuleErrorException {
// This is a simple Iterables.transform but with useful error message in case of missed Jars.
ImmutableList.Builder<Artifact> dexedClasspath = ImmutableList.builder();
for (Artifact jar : classpath) {
Artifact dexArchive = dexArchives.get(jar);
if (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);
}
return dexedClasspath.build();
}
/** Adds execution info by propagating tags from the target */
private static SpawnAction.Builder createSpawnActionBuilder(RuleContext ruleContext) {
return new SpawnAction.Builder()
.setExecutionInfo(
TargetUtils.getExecutionInfo(
ruleContext.getRule(), ruleContext.isAllowTagsPropagation()));
}
private static SpawnAction.Builder singleJarSpawnActionBuilder(RuleContext ruleContext)
throws RuleErrorException {
FilesToRunProvider singleJar = JavaToolchainProvider.from(ruleContext).getSingleJar();
SpawnAction.Builder builder =
createSpawnActionBuilder(ruleContext).useDefaultShellEnvironment();
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.
*/
private static void createCleanDexZipAction(
RuleContext ruleContext, Artifact inputZip, Artifact outputZip) throws RuleErrorException {
ruleContext.registerAction(
singleJarSpawnActionBuilder(ruleContext)
.setProgressMessage("Trimming %s", inputZip.getExecPath().getBaseName())
.setMnemonic("TrimDexZip")
.addInput(inputZip)
.addOutput(outputZip)
.addCommandLine(
CustomCommandLine.builder()
.add("--exclude_build_data")
.add("--dont_change_compression")
.addExecPath("--sources", inputZip)
.addExecPath("--output", outputZip)
.add("--include_prefixes")
.add("classes")
.build())
.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.
*/
private static Artifact createMainDexListAction(
RuleContext ruleContext,
AndroidSemantics androidSemantics,
Artifact jar,
@Nullable Artifact mainDexProguardSpec,
@Nullable Artifact proguardOutputMap)
throws InterruptedException, RuleErrorException {
AndroidSdkProvider sdk = AndroidSdkProvider.fromRuleContext(ruleContext);
// Create the main dex classes list.
Artifact mainDexList = AndroidBinary.getDxArtifact(ruleContext, "main_dex_list.txt");
List<Artifact> proguardSpecs =
new ArrayList<>(ruleContext.getPrerequisiteArtifacts("main_dex_proguard_specs").list());
if (proguardSpecs.isEmpty()) {
proguardSpecs.add(sdk.getMainDexClasses());
}
if (mainDexProguardSpec != null) {
proguardSpecs.add(mainDexProguardSpec);
}
// Use the legacy_main_dex_list_generator provided by the --legacy_main_dex_list_generator flag
// if present. If not, fall back to the one provided by the SDK.
FilesToRunProvider legacyMainDexListGenerator =
ruleContext.getExecutablePrerequisite(":legacy_main_dex_list_generator");
if (legacyMainDexListGenerator == null) {
legacyMainDexListGenerator = sdk.getLegacyMainDexListGenerator();
}
// If legacy_main_dex_list_generator is not set by either the SDK or the flag, use ProGuard and
// the main dext list creator specified by the android_sdk rule. If
// legacy_main_dex_list_generator is provided, use that tool instead.
// TODO(b/147692286): Remove the old main-dex list generation that relied on ProGuard.
if (legacyMainDexListGenerator == null) {
if (sdk.getShrinkedAndroidJar() == null) {
ruleContext.throwWithRuleError(
"In \"legacy\" multidex mode, either legacy_main_dex_list_generator or "
+ "shrinked_android_jar must be set in the android_sdk.");
}
// Process the input jar through Proguard into an intermediate, streamlined jar.
Artifact strippedJar = AndroidBinary.getDxArtifact(ruleContext, "main_dex_intermediate.jar");
SpawnAction.Builder streamlinedBuilder =
createSpawnActionBuilder(ruleContext)
.useDefaultShellEnvironment()
.addOutput(strippedJar)
.setExecutable(sdk.getProguard())
.setProgressMessage("Generating streamlined input jar for main dex classes list")
.setMnemonic("MainDexClassesIntermediate")
.addInput(jar)
.addInput(sdk.getShrinkedAndroidJar());
CustomCommandLine.Builder streamlinedCommandLine =
CustomCommandLine.builder()
.add("-forceprocessing")
.addExecPath("-injars", jar)
.addExecPath("-libraryjars", sdk.getShrinkedAndroidJar())
.addExecPath("-outjars", strippedJar)
.add("-dontwarn")
.add("-dontnote")
.add("-dontoptimize")
.add("-dontobfuscate");
for (Artifact spec : proguardSpecs) {
streamlinedBuilder.addInput(spec);
streamlinedCommandLine.addExecPath("-include", spec);
}
androidSemantics.addMainDexListActionArguments(
ruleContext, streamlinedBuilder, streamlinedCommandLine, proguardOutputMap);
streamlinedBuilder.addCommandLine(streamlinedCommandLine.build());
ruleContext.registerAction(streamlinedBuilder.build(ruleContext));
SpawnAction.Builder builder =
createSpawnActionBuilder(ruleContext)
.setMnemonic("MainDexClasses")
.setProgressMessage("Generating main dex classes list");
ruleContext.registerAction(
builder
.setExecutable(sdk.getMainDexListCreator())
.addOutput(mainDexList)
.addInput(strippedJar)
.addInput(jar)
.addCommandLine(
CustomCommandLine.builder()
.addExecPath(mainDexList)
.addExecPath(strippedJar)
.addExecPath(jar)
.addAll(
ruleContext
.getExpander()
.withDataLocations()
.tokenized("main_dex_list_opts"))
.build())
.build(ruleContext));
} else {
// Use the newer legacy multidex main-dex list generation.
SpawnAction.Builder actionBuilder =
createSpawnActionBuilder(ruleContext)
.setMnemonic("MainDexClasses")
.setProgressMessage("Generating main dex classes list");
CustomCommandLine.Builder commandLineBuilder =
CustomCommandLine.builder()
.addExecPath("--main-dex-list-output", mainDexList)
.addExecPath("--lib", sdk.getAndroidJar());
if (AndroidCommon.getAndroidConfig(ruleContext).desugarJava8Libs()) {
NestedSet<Artifact> legacyApis =
ruleContext
.getPrerequisite("$desugared_java8_legacy_apis")
.getProvider(FileProvider.class)
.getFilesToBuild();
for (Artifact lib : legacyApis.toList()) {
actionBuilder.addInput(lib);
commandLineBuilder.addExecPath("--lib", lib);
}
}
for (Artifact spec : proguardSpecs) {
actionBuilder.addInput(spec);
commandLineBuilder.addExecPath("--main-dex-rules", spec);
}
commandLineBuilder.addExecPath(jar);
ruleContext.registerAction(
actionBuilder
.setExecutable(legacyMainDexListGenerator)
.addOutput(mainDexList)
.addInput(jar)
.addInput(sdk.getAndroidJar())
.addCommandLine(commandLineBuilder.build())
.build(ruleContext));
}
return mainDexList;
}
/** Transforms manual main_dex_list through proguard obfuscation map. */
private static Artifact transformDexListThroughProguardMapAction(
RuleContext ruleContext, @Nullable Artifact proguardOutputMap, Artifact mainDexList) {
if (proguardOutputMap == null
|| !ruleContext.attributes().get("proguard_generate_mapping", Type.BOOLEAN)) {
return mainDexList;
}
Artifact obfuscatedMainDexList =
AndroidBinary.getDxArtifact(ruleContext, "main_dex_list_obfuscated.txt");
SpawnAction.Builder actionBuilder =
createSpawnActionBuilder(ruleContext)
.setMnemonic("MainDexProguardClasses")
.setProgressMessage("Obfuscating main dex classes list")
.setExecutable(ruleContext.getExecutablePrerequisite("$dex_list_obfuscator"))
.addInput(mainDexList)
.addInput(proguardOutputMap)
.addOutput(obfuscatedMainDexList)
.addCommandLine(
CustomCommandLine.builder()
.addExecPath("--input", mainDexList)
.addExecPath("--output", obfuscatedMainDexList)
.addExecPath("--obfuscation_map", proguardOutputMap)
.build());
ruleContext.registerAction(actionBuilder.build(ruleContext));
return obfuscatedMainDexList;
}
public static Artifact createMainDexProguardSpec(Label label, ActionConstructionContext context) {
return ProguardHelper.getProguardConfigArtifact(label, context, "main_dex");
}
/** Returns the multidex mode to apply to this target. */
public static MultidexMode getMultidexMode(RuleContext ruleContext) {
return Preconditions.checkNotNull(
MultidexMode.fromValue(ruleContext.attributes().get("multidex", Type.STRING)));
}
private static int getMinSdkVersion(RuleContext ruleContext) {
if (ruleContext.getRule().isAttrDefined("min_sdk_version", Type.INTEGER)) {
return ruleContext.attributes().get("min_sdk_version", Type.INTEGER).toIntUnchecked();
}
return 0;
}
/**
* 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 their 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.
*/
private static boolean supportsMultidexMode(RuleContext ruleContext, MultidexMode mode)
throws RuleErrorException {
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 forbiddenRuntime : RUNTIMES_THAT_DONT_SUPPORT_NATIVE_MULTIDEXING) {
if (runtime.contains(forbiddenRuntime)) {
return false;
}
}
}
return true;
}
/** Returns an intermediate artifact used to support dex generation. */
static Artifact getDxArtifact(RuleContext ruleContext, String baseName) {
return ruleContext.getUniqueDirectoryArtifact(
"_dx", baseName, ruleContext.getBinOrGenfilesDirectory());
}
/** Returns true if this android_binary target is an instrumentation binary */
private static boolean isInstrumentation(RuleContext ruleContext) {
return ruleContext.attributes().isAttributeValueExplicitlySpecified("instruments");
}
/**
* Perform class filtering using the target APK's predexed JAR. Filter duplicate .class and
* R.class files based on name. Prevents runtime crashes on ART. See b/19713845 for details.
*/
private static Artifact getFilteredDeployJar(RuleContext ruleContext, Artifact deployJar)
throws InterruptedException {
Artifact filterJar =
ruleContext
.getPrerequisite("instruments")
.get(AndroidPreDexJarProvider.PROVIDER)
.getPreDexJar();
Artifact filteredDeployJar =
ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_TEST_FILTERED_JAR);
AndroidDexInfo androidDexInfo =
ruleContext.getPrerequisite("application_resources", AndroidDexInfo.PROVIDER);
if (androidDexInfo != null && androidDexInfo.getFilteredDeployJar() != null) {
// Symlink to the filtered deploy jar created by this android_binary's android_binary_internal
// target to satisfy the filtered deploy jar implicit output of android_binary.
ruleContext.registerAction(
SymlinkAction.toArtifact(
ruleContext.getActionOwner(),
androidDexInfo.getFilteredDeployJar(), // target
filteredDeployJar, // symlink
"Symlinking Android filtered deploy jar"));
} else {
AndroidCommon.createZipFilterAction(
ruleContext,
deployJar,
filterJar,
filteredDeployJar,
CheckHashMismatchMode.NONE,
ruleContext
.getFragment(AndroidConfiguration.class)
.removeRClassesFromInstrumentationTestJar());
}
return filteredDeployJar;
}
}