in src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java [404:827]
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)
throws InterruptedException, RuleErrorException {
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(),
ruleContext.getPrerequisiteArtifacts(":extra_proguard_specs").list(),
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();
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 (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 proguardOutput =
applyProguard(
ruleContext,
androidCommon,
javaSemantics,
binaryJar,
proguardSpecs,
proguardMapping,
proguardOutputMap);
// Determine the outputs for the proguard map transition through post-processing and adding of
// desugared library map.
Artifact postProcessingOutputMap = null;
Artifact finalProguardOutputMap = null;
if (proguardOutput.hasMapping()) {
// Determine the Proguard map artifacts for the additional steps (if any) if shrinking of
// the app is enabled.
if (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 (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);
Artifact jarToDex = proguardOutput.getOutputJar();
DexingOutput dexingOutput =
dex(
ruleContext,
androidSemantics,
binaryJar,
jarToDex,
isBinaryJarFiltered,
androidCommon,
resourceApk.getMainDexProguardConfig(),
resourceClasses,
derivedJarFunction,
proguardOutputMap);
// 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 (Map.Entry<
com.google.common.base.Optional<String>,
? extends List<? extends TransitiveInfoCollection>>
entry : ruleContext.getSplitPrerequisites("deps").entrySet()) {
for (AndroidNativeLibsInfo provider :
AnalysisUtils.getProviders(entry.getValue(), AndroidNativeLibsInfo.PROVIDER)) {
transitiveNativeLibs.addTransitive(provider.getNativeLibs());
}
}
NestedSet<Artifact> nativeLibsAar = transitiveNativeLibs.build();
DexPostprocessingOutput dexPostprocessingOutput =
androidSemantics.postprocessClassesDexZip(
ruleContext,
filesBuilder,
dexingOutput.classesDexZip,
proguardOutput,
postProcessingOutputMap);
// Compute the final DEX files by appending Java 8 legacy .dex if used.
Artifact finalClassesDex;
Java8LegacyDexOutput java8LegacyDexOutput;
ImmutableList<Artifact> 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 %s", ruleContext.getLabel())
.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 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.create("apk")
.setClassesDex(finalClassesDex)
.addInputZip(resourceApk.getArtifact())
.setJavaResourceZip(dexingOutput.javaResourceJar, resourceExtractor)
.addInputZips(nativeLibsAar.toList())
.setNativeLibs(nativeLibs)
.setUnsignedApk(unsignedApk)
.setSignedApk(zipAlignedApk)
.setSigningKeys(signingKeys)
.setSigningLineageFile(signingLineage)
.setSigningKeyRotationMinSdk(keyRotationMinSdk)
.setV4Signature(v4Signature)
.setZipalignApk(true)
.setDeterministicSigning(androidSemantics.deterministicSigning())
.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));
}
androidCommon.addTransitiveInfoProviders(
builder,
/* aar= */ null,
resourceApk,
zipAlignedApk,
apksUnderTest,
nativeLibs,
androidCommon.isNeverLink(),
/* isLibrary = */ false);
if (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,
dexingOutput.javaResourceJar,
finalShardDexZips,
javaSemantics,
nativeLibs,
resourceApk,
mobileInstallResourceApks,
resourceExtractor,
nativeLibsAar,
signingKeys,
signingLineage,
additionalMergedManifests);
}
// 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<? extends TransitiveInfoCollection> deps :
ruleContext.getSplitPrerequisites("deps").values()) {
for (OutputGroupInfo provider :
AnalysisUtils.getProviders(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;
}
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(new AndroidPreDexJarProvider(jarToDex))
.addNativeDeclaredProvider(
AndroidFeatureFlagSetProvider.create(
AndroidFeatureFlagSetProvider.getAndValidateFlagMapFromRuleContext(ruleContext)))
.addOutputGroup("android_deploy_info", deployInfo);
}