blob: d89f019aa4d396a7e49162a849fa3cde8b4ebeeb [file] [log] [blame]
// 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)
.targetAaptVersion(AndroidAaptVersion.chooseTargetAaptVersion(ruleContext))
.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,
List<String> noCompressExtensions)
throws InterruptedException {
return builderForNonIncrementalTopLevelTarget(
dataContext, manifest, manifestValues, aaptVersion)
.setUseCompiledResourcesForMerge(aaptVersion == AndroidAaptVersion.AAPT2)
.setManifestOut(
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST))
.setMergedResourcesOut(
dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP))
.setCrunchPng(false)
.withResourceDependencies(resourceDeps)
.withAssetDependencies(assetDeps)
.setUncompressedExtensions(noCompressExtensions)
.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.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;
}
}