| // 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.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; |
| import com.google.devtools.build.lib.packages.RuleErrorConsumer; |
| import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidAaptVersion; |
| import com.google.devtools.build.lib.rules.android.databinding.DataBinding; |
| import com.google.devtools.build.lib.rules.android.databinding.DataBindingContext; |
| import com.google.devtools.build.lib.rules.java.ProguardHelper; |
| import com.google.devtools.build.lib.syntax.Type; |
| import java.util.List; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Processed Android data (assets, resources, and manifest) returned from resource processing. |
| * |
| * <p>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. |
| * |
| * <p>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; |
| private final Artifact resourceProguardConfig; |
| @Nullable private final Artifact mainDexProguardConfig; |
| |
| /** Processes Android data (assets, resources, and manifest) for android_binary targets. */ |
| public static ProcessedAndroidData processBinaryDataFrom( |
| AndroidDataContext dataContext, |
| RuleErrorConsumer errorConsumer, |
| StampedAndroidManifest manifest, |
| boolean conditionalKeepRules, |
| Map<String, String> manifestValues, |
| AndroidAaptVersion aaptVersion, |
| AndroidResources resources, |
| AndroidAssets assets, |
| ResourceDependencies resourceDeps, |
| AssetDependencies assetDeps, |
| ResourceFilterFactory resourceFilterFactory, |
| List<String> noCompressExtensions, |
| boolean crunchPng, |
| @Nullable Artifact featureOf, |
| @Nullable Artifact featureAfter, |
| DataBindingContext dataBindingContext) |
| throws RuleErrorException, InterruptedException { |
| if (conditionalKeepRules && aaptVersion != AndroidAaptVersion.AAPT2) { |
| throw errorConsumer.throwWithRuleError( |
| "resource cycle shrinking can only be enabled for builds with aapt2"); |
| } |
| |
| AndroidResourcesProcessorBuilder builder = |
| builderForNonIncrementalTopLevelTarget(dataContext, manifest, manifestValues, aaptVersion) |
| .setUseCompiledResourcesForMerge(aaptVersion == AndroidAaptVersion.AAPT2) |
| .setManifestOut( |
| dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST)) |
| .setMergedResourcesOut( |
| dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) |
| .setMainDexProguardOut( |
| AndroidBinary.createMainDexProguardSpec( |
| dataContext.getLabel(), dataContext.getActionConstructionContext())) |
| .conditionalKeepRules(conditionalKeepRules) |
| .setFeatureOf(featureOf) |
| .setFeatureAfter(featureAfter); |
| dataBindingContext.supplyLayoutInfo(builder::setDataBindingInfoZip); |
| return buildActionForBinary( |
| dataContext, |
| dataBindingContext, |
| errorConsumer, |
| builder, |
| manifest, |
| resources, |
| assets, |
| resourceDeps, |
| assetDeps, |
| resourceFilterFactory, |
| noCompressExtensions, |
| crunchPng); |
| } |
| |
| public static ProcessedAndroidData processIncrementalBinaryDataFrom( |
| RuleContext ruleContext, |
| AndroidDataContext dataContext, |
| StampedAndroidManifest manifest, |
| Artifact apkOut, |
| Artifact mergedResourcesOut, |
| String proguardPrefix, |
| Map<String, String> manifestValues) |
| throws RuleErrorException { |
| |
| AndroidResourcesProcessorBuilder builder = |
| builderForTopLevelTarget(dataContext, manifest, proguardPrefix, manifestValues) |
| .setApkOut(apkOut) |
| .setMergedResourcesOut(mergedResourcesOut); |
| |
| return buildActionForBinary( |
| dataContext, |
| DataBinding.contextFrom(ruleContext, dataContext.getAndroidConfig()), |
| ruleContext, |
| builder, |
| manifest, |
| AndroidResources.from(ruleContext, "resource_files"), |
| AndroidAssets.from(ruleContext), |
| ResourceDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false), |
| AssetDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false), |
| ResourceFilterFactory.fromRuleContextAndAttrs(ruleContext), |
| ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions"), |
| ruleContext.attributes().get("crunch_png", Type.BOOLEAN)); |
| } |
| |
| private static ProcessedAndroidData buildActionForBinary( |
| AndroidDataContext dataContext, |
| DataBindingContext dataBindingContext, |
| RuleErrorConsumer errorConsumer, |
| AndroidResourcesProcessorBuilder builder, |
| StampedAndroidManifest manifest, |
| AndroidResources resources, |
| AndroidAssets assets, |
| ResourceDependencies resourceDeps, |
| AssetDependencies assetDeps, |
| ResourceFilterFactory resourceFilterFactory, |
| List<String> noCompressExtensions, |
| boolean crunchPng) |
| throws RuleErrorException { |
| |
| ResourceFilter resourceFilter = |
| resourceFilterFactory.getResourceFilter(errorConsumer, resourceDeps, resources); |
| |
| // Filter unwanted resources out |
| resources = resources.filterLocalResources(errorConsumer, resourceFilter); |
| resourceDeps = resourceDeps.filter(errorConsumer, resourceFilter); |
| |
| return builder |
| .setResourceFilterFactory(resourceFilterFactory) |
| .setUncompressedExtensions(noCompressExtensions) |
| .setCrunchPng(crunchPng) |
| .withResourceDependencies(resourceDeps) |
| .withAssetDependencies(assetDeps) |
| .build(dataContext, resources, assets, manifest, dataBindingContext); |
| } |
| |
| /** Processes Android data (assets, resources, and manifest) for android_local_test targets. */ |
| public static ProcessedAndroidData processLocalTestDataFrom( |
| AndroidDataContext dataContext, |
| DataBindingContext dataBindingContext, |
| StampedAndroidManifest manifest, |
| Map<String, String> manifestValues, |
| AndroidAaptVersion aaptVersion, |
| AndroidResources resources, |
| AndroidAssets assets, |
| ResourceDependencies resourceDeps, |
| AssetDependencies assetDeps) |
| throws InterruptedException { |
| |
| return builderForNonIncrementalTopLevelTarget( |
| dataContext, manifest, manifestValues, aaptVersion) |
| .setUseCompiledResourcesForMerge( |
| aaptVersion == AndroidAaptVersion.AAPT2 |
| && dataContext.getAndroidConfig().skipParsingAction()) |
| .setManifestOut( |
| dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST)) |
| .setMergedResourcesOut( |
| dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) |
| .setCrunchPng(false) |
| .withResourceDependencies(resourceDeps) |
| .withAssetDependencies(assetDeps) |
| .build(dataContext, resources, assets, manifest, dataBindingContext); |
| } |
| |
| /** Processes Android data (assets, resources, and manifest) for android_test targets. */ |
| public static ProcessedAndroidData processTestDataFrom( |
| AndroidDataContext dataContext, |
| DataBindingContext dataBindingContext, |
| StampedAndroidManifest manifest, |
| String packageUnderTest, |
| boolean hasLocalResourceFiles, |
| AndroidAaptVersion aaptVersion, |
| AndroidResources resources, |
| ResourceDependencies resourceDeps, |
| AndroidAssets assets, |
| AssetDependencies assetDeps) |
| throws InterruptedException { |
| |
| AndroidResourcesProcessorBuilder builder = |
| builderForNonIncrementalTopLevelTarget( |
| dataContext, manifest, ImmutableMap.of(), aaptVersion) |
| .setMergedResourcesOut( |
| dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) |
| .setMainDexProguardOut( |
| AndroidBinary.createMainDexProguardSpec( |
| dataContext.getLabel(), dataContext.getActionConstructionContext())) |
| .setPackageUnderTest(packageUnderTest) |
| .setIsTestWithResources(hasLocalResourceFiles) |
| .withResourceDependencies(resourceDeps) |
| .withAssetDependencies(assetDeps); |
| |
| return builder.build(dataContext, resources, assets, manifest, dataBindingContext); |
| } |
| |
| /** |
| * Common {@link AndroidResourcesProcessorBuilder} builder for non-incremental top-level targets. |
| * |
| * <p>The builder will be populated with commonly-used settings and outputs. |
| */ |
| private static AndroidResourcesProcessorBuilder builderForNonIncrementalTopLevelTarget( |
| AndroidDataContext dataContext, |
| StampedAndroidManifest manifest, |
| Map<String, String> manifestValues, |
| AndroidAaptVersion aaptVersion) |
| throws InterruptedException { |
| |
| return builderForTopLevelTarget(dataContext, manifest, "", manifestValues) |
| .targetAaptVersion(aaptVersion) |
| |
| // Outputs |
| .setApkOut(dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK)) |
| .setRTxtOut(dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT)) |
| .setSourceJarOut( |
| dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_JAVA_SOURCE_JAR)) |
| .setSymbols(dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_MERGED_SYMBOLS)); |
| } |
| |
| /** |
| * Common {@link AndroidResourcesProcessorBuilder} builder for top-level targets. |
| * |
| * <p>The builder will be populated with commonly-used settings and outputs. |
| */ |
| private static AndroidResourcesProcessorBuilder builderForTopLevelTarget( |
| AndroidDataContext dataContext, |
| StampedAndroidManifest manifest, |
| String proguardPrefix, |
| Map<String, String> manifestValues) { |
| return new AndroidResourcesProcessorBuilder() |
| // Settings |
| .setDebug(dataContext.useDebug()) |
| .setJavaPackage(manifest.getPackage()) |
| .setApplicationId(manifestValues.get("applicationId")) |
| .setVersionCode(manifestValues.get("versionCode")) |
| .setVersionName(manifestValues.get("versionName")) |
| .setThrowOnResourceConflict(dataContext.getAndroidConfig().throwOnResourceConflict()) |
| |
| // Output |
| .setProguardOut( |
| ProguardHelper.getProguardConfigArtifact( |
| dataContext.getLabel(), |
| dataContext.getActionConstructionContext(), |
| proguardPrefix)); |
| } |
| |
| static ProcessedAndroidData of( |
| ParsedAndroidResources resources, |
| MergedAndroidAssets assets, |
| ProcessedAndroidManifest manifest, |
| Artifact rTxt, |
| Artifact sourceJar, |
| Artifact apk, |
| @Nullable Artifact dataBindingInfoZip, |
| ResourceDependencies resourceDeps, |
| 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, |
| 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. |
| * |
| * <p>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(AndroidDataContext dataContext, AndroidAaptVersion aaptVersion) |
| throws InterruptedException { |
| return new RClassGeneratorActionBuilder() |
| .targetAaptVersion(aaptVersion) |
| .withDependencies(resourceDeps) |
| .setClassJarOut( |
| dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_CLASS_JAR)) |
| .build(dataContext, 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; |
| } |
| } |