// Copyright 2018 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 com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.config.CompilationMode; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidAaptVersion; import com.google.devtools.build.lib.rules.java.ProguardHelper; import com.google.devtools.build.lib.syntax.Type; import java.util.Map; import javax.annotation.Nullable; /** * Processed Android data (assets, resources, and manifest) returned from resource processing. * *

In libraries, data is parsed, merged, and then validated. For top-level targets like * android_binary, however, most of these steps happen in a single monolithic action. The only thing * the monolithic action doesn't do is generate an R.class file for the resources. When combined * with such a file, this object should contain the same data as the results of the individual * actions. * *

In general, the individual actions should be used, as they are decoupled and designed to allow * parallelized processing of a dependency tree of android_library targets. The monolithic action * should only be used as part of building the data into a final APK that can become part of a * produce android_binary or other top-level APK. */ public class ProcessedAndroidData { private final ParsedAndroidResources resources; private final MergedAndroidAssets assets; private final ProcessedAndroidManifest manifest; private final Artifact rTxt; private final Artifact sourceJar; private final Artifact apk; @Nullable private final Artifact dataBindingInfoZip; private final ResourceDependencies resourceDeps; @Nullable private final Artifact resourceProguardConfig; @Nullable private final Artifact mainDexProguardConfig; /** Processes Android data (assets, resources, and manifest) for android_binary targets. */ public static ProcessedAndroidData processBinaryDataFrom( RuleContext ruleContext, StampedAndroidManifest manifest, boolean conditionalKeepRules) throws RuleErrorException, InterruptedException { if (conditionalKeepRules && AndroidAaptVersion.chooseTargetAaptVersion(ruleContext) != AndroidAaptVersion.AAPT2) { throw ruleContext.throwWithRuleError( "resource cycle shrinking can only be enabled for builds with aapt2"); } AndroidResourcesProcessorBuilder builder = builderForNonIncrementalTopLevelTarget(ruleContext, manifest) .setUseCompiledResourcesForMerge( AndroidAaptVersion.chooseTargetAaptVersion(ruleContext) == AndroidAaptVersion.AAPT2 && AndroidCommon.getAndroidConfig(ruleContext).skipParsingAction()) .setManifestOut( ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST)) .setMergedResourcesOut( ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) .setMainDexProguardOut(AndroidBinary.createMainDexProguardSpec(ruleContext)) .conditionalKeepRules(conditionalKeepRules) .setDataBindingInfoZip( DataBinding.isEnabled(ruleContext) ? DataBinding.getLayoutInfoFile(ruleContext) : null) .setFeatureOf( ruleContext.attributes().isAttributeValueExplicitlySpecified("feature_of") ? ruleContext .getPrerequisite("feature_of", Mode.TARGET, ApkInfo.PROVIDER) .getApk() : null) .setFeatureAfter( ruleContext.attributes().isAttributeValueExplicitlySpecified("feature_after") ? ruleContext .getPrerequisite("feature_after", Mode.TARGET, ApkInfo.PROVIDER) .getApk() : null); return buildActionForBinary(ruleContext, builder, manifest); } public static ProcessedAndroidData processIncrementalBinaryDataFrom( RuleContext ruleContext, StampedAndroidManifest manifest, Artifact apkOut, String proguardPrefix) throws RuleErrorException { AndroidResourcesProcessorBuilder builder = builderForTopLevelTarget(ruleContext, manifest, proguardPrefix).setApkOut(apkOut); return buildActionForBinary(ruleContext, builder, manifest); } private static ProcessedAndroidData buildActionForBinary( RuleContext ruleContext, AndroidResourcesProcessorBuilder builder, StampedAndroidManifest manifest) throws RuleErrorException { AndroidResources resources = AndroidResources.from(ruleContext, "resource_files"); ResourceDependencies resourceDeps = ResourceDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false); ResourceFilterFactory resourceFilterFactory = ResourceFilterFactory.fromRuleContext(ruleContext); ResourceFilter resourceFilter = resourceFilterFactory.getResourceFilter(ruleContext, resourceDeps, resources); // Filter unwanted resources out resources = resources.filterLocalResources(ruleContext, resourceFilter); resourceDeps = resourceDeps.filter(ruleContext, resourceFilter); return builder .setResourceFilterFactory(resourceFilterFactory) .setUncompressedExtensions( ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions")) .setCrunchPng(ruleContext.attributes().get("crunch_png", Type.BOOLEAN)) .withResourceDependencies(resourceDeps) .withAssetDependencies(AssetDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false)) .build(resources, AndroidAssets.from(ruleContext), manifest); } /** Processes Android data (assets, resources, and manifest) for android_local_test targets. */ public static ProcessedAndroidData processLocalTestDataFrom( RuleContext ruleContext, StampedAndroidManifest manifest) throws RuleErrorException, InterruptedException { return builderForNonIncrementalTopLevelTarget(ruleContext, manifest) .setUseCompiledResourcesForMerge( AndroidAaptVersion.chooseTargetAaptVersion(ruleContext) == AndroidAaptVersion.AAPT2 && AndroidCommon.getAndroidConfig(ruleContext).skipParsingAction()) .setManifestOut( ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST)) .setMergedResourcesOut( ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) .setCrunchPng(false) .withResourceDependencies( ResourceDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false)) .withAssetDependencies(AssetDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false)) .build( AndroidResources.from(ruleContext, "resource_files"), AndroidAssets.from(ruleContext), manifest); } /** Processes Android data (assets, resources, and manifest) for android_test targets. */ public static ProcessedAndroidData processTestDataFrom( RuleContext ruleContext, StampedAndroidManifest manifest, String packageUnderTest, boolean hasLocalResourceFiles) throws InterruptedException, RuleErrorException { AndroidResourcesProcessorBuilder builder = builderForNonIncrementalTopLevelTarget(ruleContext, manifest) .setMainDexProguardOut(AndroidBinary.createMainDexProguardSpec(ruleContext)) .setPackageUnderTest(packageUnderTest) .setIsTestWithResources(hasLocalResourceFiles); if (hasLocalResourceFiles) { builder .withResourceDependencies( ResourceDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false)) .withAssetDependencies( AssetDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false)); } return builder.build( AndroidResources.from(ruleContext, "local_resource_files"), AndroidAssets.from(ruleContext), manifest); } /** * Common {@link AndroidResourcesProcessorBuilder} builder for non-incremental top-level targets. * *

The builder will be populated with commonly-used settings and outputs. */ private static AndroidResourcesProcessorBuilder builderForNonIncrementalTopLevelTarget( RuleContext ruleContext, StampedAndroidManifest manifest) throws InterruptedException, RuleErrorException { return builderForTopLevelTarget(ruleContext, manifest, "") .targetAaptVersion(AndroidAaptVersion.chooseTargetAaptVersion(ruleContext)) // Outputs .setApkOut(ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK)) .setRTxtOut(ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT)) .setSourceJarOut( ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_JAVA_SOURCE_JAR)) .setSymbols(ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_MERGED_SYMBOLS)); } /** * Common {@link AndroidResourcesProcessorBuilder} builder for top-level targets. * *

The builder will be populated with commonly-used settings and outputs. */ private static AndroidResourcesProcessorBuilder builderForTopLevelTarget( RuleContext ruleContext, StampedAndroidManifest manifest, String proguardPrefix) { Map manifestValues = ApplicationManifest.getManifestValues(ruleContext); return new AndroidResourcesProcessorBuilder(ruleContext) // Settings .setDebug(ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT) .setJavaPackage(manifest.getPackage()) .setApplicationId(manifestValues.get("applicationId")) .setVersionCode(manifestValues.get("versionCode")) .setVersionName(manifestValues.get("versionName")) .setThrowOnResourceConflict( AndroidCommon.getAndroidConfig(ruleContext).throwOnResourceConflict()) // Output .setProguardOut(ProguardHelper.getProguardConfigArtifact(ruleContext, proguardPrefix)); } static ProcessedAndroidData of( ParsedAndroidResources resources, MergedAndroidAssets assets, ProcessedAndroidManifest manifest, Artifact rTxt, Artifact sourceJar, Artifact apk, @Nullable Artifact dataBindingInfoZip, ResourceDependencies resourceDeps, @Nullable Artifact resourceProguardConfig, @Nullable Artifact mainDexProguardConfig) { return new ProcessedAndroidData( resources, assets, manifest, rTxt, sourceJar, apk, dataBindingInfoZip, resourceDeps, resourceProguardConfig, mainDexProguardConfig); } private ProcessedAndroidData( ParsedAndroidResources resources, MergedAndroidAssets assets, ProcessedAndroidManifest manifest, Artifact rTxt, Artifact sourceJar, Artifact apk, @Nullable Artifact dataBindingInfoZip, ResourceDependencies resourceDeps, @Nullable Artifact resourceProguardConfig, @Nullable Artifact mainDexProguardConfig) { this.resources = resources; this.assets = assets; this.manifest = manifest; this.rTxt = rTxt; this.sourceJar = sourceJar; this.apk = apk; this.dataBindingInfoZip = dataBindingInfoZip; this.resourceDeps = resourceDeps; this.resourceProguardConfig = resourceProguardConfig; this.mainDexProguardConfig = mainDexProguardConfig; } /** * Gets the fully processed data from this class. * *

Registers an action to run R class generation, the last step needed in resource processing. * Returns the fully processed data, including validated resources, wrapped in a ResourceApk. */ public ResourceApk generateRClass(RuleContext ruleContext) throws RuleErrorException, InterruptedException { return new RClassGeneratorActionBuilder(ruleContext) .targetAaptVersion(AndroidAaptVersion.chooseTargetAaptVersion(ruleContext)) .withDependencies(resourceDeps) .setClassJarOut( ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR)) .build(this); } /** * Returns fully processed resources. The R class generator action will not be registered. * * @param rClassJar an artifact containing the resource class jar for these resources. An action * to generate it must be registered elsewhere. */ ResourceApk withValidatedResources(Artifact rClassJar) { // When assets and resources are processed together, they are both merged into the same zip Artifact mergedResources = assets.getMergedAssets(); // Since parts of both merging and validation were already done in combined resource processing, // we need to build containers for both here. MergedAndroidResources merged = MergedAndroidResources.of( resources, mergedResources, rClassJar, dataBindingInfoZip, resourceDeps, manifest); // Combined resource processing does not produce aapt2 artifacts; they're nulled out ValidatedAndroidResources validated = ValidatedAndroidResources.of(merged, rTxt, sourceJar, apk, null, null, null); return ResourceApk.of(validated, assets, resourceProguardConfig, mainDexProguardConfig); } public MergedAndroidAssets getAssets() { return assets; } public ProcessedAndroidManifest getManifest() { return manifest; } public Artifact getRTxt() { return rTxt; } }