| // 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 static com.google.devtools.build.lib.analysis.config.CompilationMode.OPT; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.actions.ActionAnalysisMetadata; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.ArtifactRoot; |
| import com.google.devtools.build.lib.analysis.Allowlist; |
| import com.google.devtools.build.lib.analysis.FilesToRunProvider; |
| import com.google.devtools.build.lib.analysis.RuleContext; |
| import com.google.devtools.build.lib.analysis.RuleErrorConsumer; |
| import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; |
| import com.google.devtools.build.lib.analysis.actions.SpawnAction; |
| import com.google.devtools.build.lib.cmdline.Label; |
| import com.google.devtools.build.lib.packages.BuildType; |
| import com.google.devtools.build.lib.packages.ImplicitOutputsFunction.SafeImplicitOutputsFunction; |
| import com.google.devtools.build.lib.packages.TargetUtils; |
| import com.google.devtools.build.lib.packages.TriState; |
| import com.google.devtools.build.lib.starlarkbuildapi.android.AndroidDataContextApi; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Wraps common tools and settings used for working with Android assets, resources, and manifests. |
| * |
| * <p>Do not create implementation classes directly - instead, get the appropriate one from {@link |
| * com.google.devtools.build.lib.rules.android.AndroidSemantics}. |
| * |
| * <p>The {@link Label}, {@link ActionConstructionContext}, and BusyBox {@link FilesToRunProvider} |
| * are needed to create virtually all actions for working with Android data, so it makes sense to |
| * bundle them together. Additionally, this class includes some common tools (such as an SDK) that |
| * are used in BusyBox actions. |
| */ |
| public class AndroidDataContext implements AndroidDataContextApi { |
| |
| // Feature which would cause AndroidCompiledResourceMerger actions to pass a flag with the same |
| // name to ResourceProcessorBusyBox. |
| private static final String ANNOTATE_R_FIELDS_FROM_TRANSITIVE_DEPS = |
| "annotate_r_fields_from_transitive_deps"; |
| |
| // If specified, omit resources from transitive dependencies when generating Android R classes. |
| private static final String OMIT_TRANSITIVE_RESOURCES_FROM_ANDROID_R_CLASSES = |
| "android_resources_strict_deps"; |
| |
| // Feature which would enable AAPT2's resource name obfuscation optimization for android_binary |
| // rules with resource shrinking and ProGuard/AppReduce enabled. |
| private static final String FEATURE_RESOURCE_NAME_OBFUSCATION = "resource_name_obfuscation"; |
| |
| private final RuleContext ruleContext; |
| private final FilesToRunProvider busybox; |
| private final AndroidSdkProvider sdk; |
| private final boolean persistentBusyboxToolsEnabled; |
| private final boolean persistentMultiplexBusyboxToolsEnabled; |
| private final boolean optOutOfResourcePathShortening; |
| private final boolean optOutOfResourceNameObfuscation; |
| private final boolean throwOnShrinkResources; |
| private final boolean throwOnProguardApplyDictionary; |
| private final boolean throwOnProguardApplyMapping; |
| private final boolean throwOnResourceConflict; |
| private final boolean useDataBindingV2; |
| private final boolean useDataBindingAndroidX; |
| private final boolean includeProguardLocationReferences; |
| private final ImmutableMap<String, String> executionInfo; |
| |
| public static AndroidDataContext forNative(RuleContext ruleContext) { |
| return makeContext(ruleContext); |
| } |
| |
| public static AndroidDataContext makeContext(RuleContext ruleContext) { |
| AndroidConfiguration androidConfig = |
| ruleContext.getConfiguration().getFragment(AndroidConfiguration.class); |
| |
| ImmutableMap<String, String> executionInfo = |
| TargetUtils.getExecutionInfo(ruleContext.getRule(), ruleContext.isAllowTagsPropagation()); |
| |
| return new AndroidDataContext( |
| ruleContext, |
| ruleContext.getExecutablePrerequisite("$android_resources_busybox"), |
| androidConfig.persistentBusyboxTools(), |
| androidConfig.persistentMultiplexBusyboxTools(), |
| AndroidSdkProvider.fromRuleContext(ruleContext), |
| hasExemption(ruleContext, "allow_raw_access_to_resource_paths", false), |
| hasExemption(ruleContext, "allow_resource_name_obfuscation_opt_out", false), |
| !hasExemption(ruleContext, "allow_shrink_resources_attribute", true), |
| !hasExemption(ruleContext, "allow_proguard_apply_dictionary", true), |
| !hasExemption(ruleContext, "allow_proguard_apply_mapping", true), |
| !hasExemption(ruleContext, "allow_resource_conflicts", true), |
| androidConfig.useDataBindingV2(), |
| androidConfig.useDataBindingAndroidX(), |
| androidConfig.includeProguardLocationReferences(), |
| executionInfo); |
| } |
| |
| private static boolean hasExemption( |
| RuleContext ruleContext, String exemptionName, boolean valueIfNoAllowlist) { |
| return Allowlist.hasAllowlist(ruleContext, exemptionName) |
| ? Allowlist.isAvailable(ruleContext, exemptionName) |
| : valueIfNoAllowlist; |
| } |
| |
| protected AndroidDataContext( |
| RuleContext ruleContext, |
| FilesToRunProvider busybox, |
| boolean persistentBusyboxToolsEnabled, |
| boolean persistentMultiplexBusyboxToolsEnabled, |
| AndroidSdkProvider sdk, |
| boolean optOutOfResourcePathShortening, |
| boolean optOutOfResourceNameObfuscation, |
| boolean throwOnShrinkResources, |
| boolean throwOnProguardApplyDictionary, |
| boolean throwOnProguardApplyMapping, |
| boolean throwOnResourceConflict, |
| boolean useDataBindingV2, |
| boolean useDataBindingAndroidX, |
| boolean includeProguardLocationReferences, |
| ImmutableMap<String, String> executionInfo) { |
| this.persistentBusyboxToolsEnabled = persistentBusyboxToolsEnabled; |
| this.persistentMultiplexBusyboxToolsEnabled = persistentMultiplexBusyboxToolsEnabled; |
| this.ruleContext = ruleContext; |
| this.busybox = busybox; |
| this.sdk = sdk; |
| this.optOutOfResourcePathShortening = optOutOfResourcePathShortening; |
| this.optOutOfResourceNameObfuscation = optOutOfResourceNameObfuscation; |
| this.throwOnShrinkResources = throwOnShrinkResources; |
| this.throwOnProguardApplyDictionary = throwOnProguardApplyDictionary; |
| this.throwOnProguardApplyMapping = throwOnProguardApplyMapping; |
| this.throwOnResourceConflict = throwOnResourceConflict; |
| this.useDataBindingV2 = useDataBindingV2; |
| this.useDataBindingAndroidX = useDataBindingAndroidX; |
| this.includeProguardLocationReferences = includeProguardLocationReferences; |
| this.executionInfo = executionInfo; |
| } |
| |
| public Label getLabel() { |
| return ruleContext.getLabel(); |
| } |
| |
| public ActionConstructionContext getActionConstructionContext() { |
| return ruleContext; |
| } |
| |
| public RuleErrorConsumer getRuleErrorConsumer() { |
| return ruleContext; |
| } |
| |
| public FilesToRunProvider getBusybox() { |
| return busybox; |
| } |
| |
| public AndroidSdkProvider getSdk() { |
| return sdk; |
| } |
| |
| public ImmutableMap<String, String> getExecutionInfo() { |
| return executionInfo; |
| } |
| |
| /* |
| * Convenience methods. These are just slightly cleaner ways of doing common tasks. |
| */ |
| |
| /** Builds and registers a {@link SpawnAction.Builder}. */ |
| public void registerAction(SpawnAction.Builder spawnActionBuilder) { |
| registerAction(spawnActionBuilder.build(ruleContext)); |
| } |
| |
| /** Registers an action. */ |
| public void registerAction(ActionAnalysisMetadata action) { |
| ruleContext.registerAction(action); |
| } |
| |
| public Artifact createOutputArtifact(SafeImplicitOutputsFunction function) |
| throws InterruptedException { |
| return ruleContext.getImplicitOutputArtifact(function); |
| } |
| |
| public Artifact getUniqueDirectoryArtifact(String uniqueDirectorySuffix, String relative) { |
| return ruleContext.getUniqueDirectoryArtifact(uniqueDirectorySuffix, relative); |
| } |
| |
| public Artifact getUniqueDirectoryArtifact(String uniqueDirectorySuffix, PathFragment relative) { |
| return ruleContext.getUniqueDirectoryArtifact(uniqueDirectorySuffix, relative); |
| } |
| |
| public PathFragment getUniqueDirectory(PathFragment fragment) { |
| return ruleContext.getUniqueDirectory(fragment); |
| } |
| |
| public ArtifactRoot getBinOrGenfilesDirectory() { |
| return ruleContext.getBinOrGenfilesDirectory(); |
| } |
| |
| public PathFragment getPackageDirectory() { |
| return ruleContext.getPackageDirectory(); |
| } |
| |
| public AndroidConfiguration getAndroidConfig() { |
| return ruleContext.getConfiguration().getFragment(AndroidConfiguration.class); |
| } |
| |
| @Nullable |
| public BazelAndroidConfiguration getBazelAndroidConfig() { |
| return ruleContext.getConfiguration().getFragment(BazelAndroidConfiguration.class); |
| } |
| |
| /** Indicates whether Busybox actions should be passed the "--debug" flag */ |
| public boolean useDebug() { |
| return getActionConstructionContext().getConfiguration().getCompilationMode() != OPT; |
| } |
| |
| public boolean isPersistentBusyboxToolsEnabled() { |
| return persistentBusyboxToolsEnabled; |
| } |
| |
| public boolean isPersistentMultiplexBusyboxToolsEnabled() { |
| return persistentMultiplexBusyboxToolsEnabled; |
| } |
| |
| public boolean optOutOfResourcePathShortening() { |
| return optOutOfResourcePathShortening; |
| } |
| |
| public boolean optOutOfResourceNameObfuscation() { |
| return optOutOfResourceNameObfuscation; |
| } |
| |
| public boolean throwOnShrinkResources() { |
| return throwOnShrinkResources; |
| } |
| |
| public boolean throwOnProguardApplyDictionary() { |
| return throwOnProguardApplyDictionary; |
| } |
| |
| public boolean throwOnProguardApplyMapping() { |
| return throwOnProguardApplyMapping; |
| } |
| |
| public boolean throwOnResourceConflict() { |
| return throwOnResourceConflict; |
| } |
| |
| public boolean useDataBindingV2() { |
| return useDataBindingV2; |
| } |
| |
| public boolean useDataBindingAndroidX() { |
| return useDataBindingAndroidX; |
| } |
| |
| public boolean includeProguardLocationReferences() { |
| return includeProguardLocationReferences; |
| } |
| |
| public boolean annotateRFieldsFromTransitiveDeps() { |
| return ruleContext.getFeatures().contains(ANNOTATE_R_FIELDS_FROM_TRANSITIVE_DEPS); |
| } |
| |
| boolean omitTransitiveResourcesFromAndroidRClasses() { |
| return ruleContext.getFeatures().contains(OMIT_TRANSITIVE_RESOURCES_FROM_ANDROID_R_CLASSES); |
| } |
| |
| /** Returns true if the context dictates that resource shrinking should be performed. */ |
| boolean useResourceShrinking(boolean hasProguardSpecs) { |
| return isResourceShrinkingEnabled() && hasProguardSpecs; |
| } |
| |
| /** |
| * Returns true if the context dictates that resource shrinking is enabled. This doesn't |
| * necessarily mean that shrinking should be performed - for that, use {@link |
| * #useResourceShrinking(boolean)}, which calls this. |
| */ |
| boolean isResourceShrinkingEnabled() { |
| if (!ruleContext.attributes().has("shrink_resources")) { |
| return false; |
| } |
| |
| TriState state = ruleContext.attributes().get("shrink_resources", BuildType.TRISTATE); |
| if (state == TriState.AUTO) { |
| state = getAndroidConfig().useAndroidResourceShrinking() ? TriState.YES : TriState.NO; |
| } |
| |
| return state == TriState.YES; |
| } |
| |
| /** |
| * Returns {@code true} if resource shrinking should be performed. This should be true when the |
| * resource cycle shrinking flag is enabled, resource shrinking itself is enabled, and the build |
| * is ProGuarded/optimized. The last condition is important because resource cycle shrinking |
| * generates non-final fields that are not inlined by javac. In non-optimized builds, these can |
| * noticeably increase Apk size. |
| */ |
| boolean shouldShrinkResourceCycles(RuleErrorConsumer errorConsumer, boolean shrinkResources) { |
| boolean isProguarded = |
| ruleContext.attributes().has(ProguardHelper.PROGUARD_SPECS, BuildType.LABEL_LIST) |
| && !ruleContext |
| .getPrerequisiteArtifacts(ProguardHelper.PROGUARD_SPECS) |
| .list() |
| .isEmpty(); |
| return isProguarded && getAndroidConfig().useAndroidResourceCycleShrinking() && shrinkResources; |
| } |
| |
| boolean useResourcePathShortening() { |
| // Use resource path shortening iff: |
| // 1) --experimental_android_resource_path_shortening |
| // 2) -c opt |
| // 3) Not opting out by being on allowlist named allow_raw_access_to_resource_paths |
| return getAndroidConfig().useAndroidResourcePathShortening() |
| && getActionConstructionContext().getConfiguration().getCompilationMode() == OPT |
| && !optOutOfResourcePathShortening; |
| } |
| |
| boolean useResourceNameObfuscation(boolean hasProguardSpecs) { |
| // Use resource name obfuscation iff: |
| // 1) --experimental_android_resource_name_obfuscation or feature enabled for rule's package |
| // 2) resource shrinking is on (implying proguard specs are present) |
| // 3) Not opting out by being on allowlist named allow_resource_name_obfuscation_opt_out |
| return (getAndroidConfig().useAndroidResourceNameObfuscation() |
| || ruleContext.getFeatures().contains(FEATURE_RESOURCE_NAME_OBFUSCATION)) |
| && useResourceShrinking(hasProguardSpecs) |
| && !optOutOfResourceNameObfuscation; |
| } |
| } |