diff options
author | 2017-03-23 14:19:56 +0000 | |
---|---|---|
committer | 2017-03-23 14:41:39 +0000 | |
commit | df366408188f0451bae9b2ed079c795a4beb2e2b (patch) | |
tree | 55771076f6546fd2922c79993e13f983f8887e19 /src/main/java/com/google/devtools | |
parent | 750d718bab83be113b1841b1f408dd4b3f5e82f6 (diff) |
Rollback of commit 1e18045ed9d6ab9c945cec69286a7d8bd288a507.
--
PiperOrigin-RevId: 151000602
MOS_MIGRATED_REVID=151000602
Diffstat (limited to 'src/main/java/com/google/devtools')
10 files changed, 273 insertions, 536 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java index 32e2aa5b70..ad872d4e0e 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java @@ -237,9 +237,10 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { resourceDeps, ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), null, /* Artifact symbolsTxt */ - ResourceFilter.fromRuleContext(ruleContext), + ResourceConfigurationFilter.fromRuleContext(ruleContext), ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), + ruleContext.getTokenizedStringListAttr("densities"), false, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, ""), createMainDexProguardSpec(ruleContext), @@ -260,9 +261,10 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { resourceDeps, null, /* Artifact rTxt */ null, /* Artifact symbolsTxt */ - ResourceFilter.fromRuleContext(ruleContext), + ResourceConfigurationFilter.fromRuleContext(ruleContext), ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), + ruleContext.getTokenizedStringListAttr("densities"), true, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental"), null, /* mainDexProguardCfg */ @@ -282,9 +284,10 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { resourceDeps, null, /* Artifact rTxt */ null, /* Artifact symbolsTxt */ - ResourceFilter.fromRuleContext(ruleContext), + ResourceConfigurationFilter.fromRuleContext(ruleContext), ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), + ruleContext.getTokenizedStringListAttr("densities"), true, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, "instant_run"), null, /* mainDexProguardCfg */ @@ -304,9 +307,10 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { resourceDeps, null, /* Artifact rTxt */ null, /* Artifact symbolsTxt */ - ResourceFilter.fromRuleContext(ruleContext), + ResourceConfigurationFilter.fromRuleContext(ruleContext), ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), + ruleContext.getTokenizedStringListAttr("densities"), true, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental_split"), null, /* mainDexProguardCfg */ @@ -1160,8 +1164,7 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { .withProguardMapping(proguardOutput.getMapping()) .withPrimary(resourceApk.getPrimaryResource()) .withDependencies(resourceApk.getResourceDependencies()) - .setResourceFilter(ResourceFilter.fromRuleContext(ruleContext)) - + .setConfigurationFilters(ResourceConfigurationFilter.fromRuleContext(ruleContext)) .setUncompressedExtensions( ruleContext.getTokenizedStringListAttr("nocompress_extensions")) .build(); @@ -1770,14 +1773,16 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { * <p>The resources should be regenerated (using aapt) if any of the following are true: * <ul> * <li>There is more than one resource container - * <li>There are resource filters. + * <li>There are densities to filter by. + * <li>There are resource configuration filters. * <li>There are extensions that should be compressed. * </ul> */ public static boolean shouldRegenerate(RuleContext ruleContext, ResourceDependencies resourceDeps) { return Iterables.size(resourceDeps.getResources()) > 1 - || ResourceFilter.hasFilters(ruleContext) + || ruleContext.attributes().isAttributeValueExplicitlySpecified("densities") + || ResourceConfigurationFilter.hasFilters(ruleContext) || ruleContext.attributes().isAttributeValueExplicitlySpecified("nocompress_extensions"); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java index 3d8efbaade..d4bb19487f 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java @@ -84,7 +84,7 @@ public final class AndroidBinaryOnlyRule implements RuleDefinition { A list of resource configuration filters, such 'en' that will limit the resources in the apk to only the ones in the 'en' configuration. <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ - .add(attr(ResourceFilter.RESOURCE_CONFIGURATION_FILTERS_NAME, STRING_LIST)) + .add(attr(ResourceConfigurationFilter.ATTR_NAME, STRING_LIST)) /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(shrink_resources) --> Whether to perform resource shrinking. Resources that are not used by the binary will be removed from the APK. This is only supported for rules using local resources (i.e. the @@ -116,7 +116,7 @@ public final class AndroidBinaryOnlyRule implements RuleDefinition { section will also be added to the manifest if it does not already contain a superset listing. <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ - .add(attr(ResourceFilter.DENSITIES_NAME, STRING_LIST)) + .add(attr("densities", STRING_LIST)) .add(attr("$android_manifest_merge_tool", LABEL) .cfg(HOST) .exec() diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java index 8f5b893f49..bd57dffab5 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java @@ -97,9 +97,10 @@ public abstract class AndroidLibrary implements RuleConfiguredTargetFactory { ResourceDependencies.fromRuleDeps(ruleContext, JavaCommon.isNeverLink(ruleContext)), ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_SYMBOLS), - ResourceFilter.empty(ruleContext), + ResourceConfigurationFilter.empty(ruleContext), ImmutableList.<String>of(), /* uncompressedExtensions */ false, /* crunchPng */ + ImmutableList.<String>of(), /* densities */ false, /* incremental */ null, /* proguardCfgOut */ null, /* mainDexProguardCfg */ diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java index 6bee442d83..75fbfa697a 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java @@ -69,12 +69,13 @@ public class AndroidResourcesProcessorBuilder { private Artifact rTxtOut; private Artifact sourceJarOut; private boolean debug = false; - private ResourceFilter resourceFilter; + private ResourceConfigurationFilter resourceConfigs; private List<String> uncompressedExtensions = Collections.emptyList(); private Artifact apkOut; private final AndroidSdkProvider sdk; private List<String> assetsToIgnore = Collections.emptyList(); private SpawnAction.Builder spawnActionBuilder; + private List<String> densities = Collections.emptyList(); private String customJavaPackage; private final RuleContext ruleContext; private String versionCode; @@ -97,7 +98,7 @@ public class AndroidResourcesProcessorBuilder { this.sdk = AndroidSdkProvider.fromRuleContext(ruleContext); this.ruleContext = ruleContext; this.spawnActionBuilder = new SpawnAction.Builder(); - this.resourceFilter = ResourceFilter.empty(ruleContext); + this.resourceConfigs = ResourceConfigurationFilter.empty(ruleContext); } /** @@ -135,9 +136,14 @@ public class AndroidResourcesProcessorBuilder { return this; } - public AndroidResourcesProcessorBuilder setResourceFilter( - ResourceFilter resourceFilter) { - this.resourceFilter = resourceFilter; + public AndroidResourcesProcessorBuilder setDensities(List<String> densities) { + this.densities = densities; + return this; + } + + public AndroidResourcesProcessorBuilder setConfigurationFilters( + ResourceConfigurationFilter resourceConfigs) { + this.resourceConfigs = resourceConfigs; return this; } @@ -209,7 +215,7 @@ public class AndroidResourcesProcessorBuilder { public ResourceContainer build(ActionConstructionContext context) { List<Artifact> outs = new ArrayList<>(); CustomCommandLine.Builder builder = new CustomCommandLine.Builder(); - + // Set the busybox tool. builder.add("--tool").add("PACKAGE").add("--"); @@ -279,15 +285,11 @@ public class AndroidResourcesProcessorBuilder { builder.addExecPath("--packagePath", apkOut); outs.add(apkOut); } - if (resourceFilter.hasConfigurationFilters() && !resourceFilter.isPrefiltering()) { - builder.add("--resourceConfigs").add(resourceFilter.getConfigurationFilterString()); - } - if (resourceFilter.hasDensities() && !resourceFilter.isPrefiltering()) { - builder.add("--densities").add(resourceFilter.getDensityString()); + if (!resourceConfigs.isEmpty()) { + builder.add("--resourceConfigs").add(resourceConfigs.getFilterString()); } - ImmutableList<String> filteredResources = resourceFilter.getFilteredResources(); - if (!filteredResources.isEmpty()) { - builder.addJoinStrings("--prefilteredResources", ",", filteredResources); + if (!densities.isEmpty()) { + builder.addJoinStrings("--densities", ",", densities); } if (!uncompressedExtensions.isEmpty()) { builder.addJoinStrings("--uncompressedExtensions", ",", uncompressedExtensions); diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java index 8a28a16a6e..f5fabfc623 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java @@ -300,9 +300,10 @@ public final class ApplicationManifest { ruleContext, false, /* isLibrary */ resourceDeps, - ResourceFilter.empty(ruleContext), + ResourceConfigurationFilter.empty(ruleContext), ImmutableList.<String>of(), /* uncompressedExtensions */ true, /* crunchPng */ + ImmutableList.<String>of(), /* densities */ incremental, ResourceContainer.builderFromRule(ruleContext) .setAssetsAndResourcesFrom(data) @@ -347,9 +348,10 @@ public final class ApplicationManifest { ruleContext, true, /* isLibrary */ resourceDeps, - ResourceFilter.empty(ruleContext), + ResourceConfigurationFilter.empty(ruleContext), ImmutableList.<String>of(), /* List<String> uncompressedExtensions */ false, /* crunchPng */ + ImmutableList.<String>of(), /* List<String> densities */ false, /* incremental */ resourceContainer.build(), data, @@ -371,9 +373,10 @@ public final class ApplicationManifest { ResourceDependencies resourceDeps, Artifact rTxt, Artifact symbols, - ResourceFilter resourceFilter, + ResourceConfigurationFilter configurationFilters, List<String> uncompressedExtensions, boolean crunchPng, + List<String> densities, boolean incremental, Artifact proguardCfg, @Nullable Artifact mainDexProguardCfg, @@ -402,9 +405,10 @@ public final class ApplicationManifest { ruleContext, isLibrary, resourceDeps, - resourceFilter, + configurationFilters, uncompressedExtensions, crunchPng, + densities, incremental, ResourceContainer.builderFromRule(ruleContext) .setAssetsAndResourcesFrom(data) @@ -427,9 +431,10 @@ public final class ApplicationManifest { RuleContext ruleContext, boolean isLibrary, ResourceDependencies resourceDeps, - ResourceFilter resourceFilter, + ResourceConfigurationFilter configurationFilters, List<String> uncompressedExtensions, boolean crunchPng, + List<String> densities, boolean incremental, ResourceContainer maybeInlinedResourceContainer, LocalResourceContainer data, @@ -450,11 +455,11 @@ public final class ApplicationManifest { if (ruleContext.hasErrors()) { return null; } - + // Filter the resources during analysis to prevent processing of and dependencies on unwanted // resources during execution. - resourceContainer = resourceContainer.filter(resourceFilter); - resourceDeps = resourceDeps.filter(resourceFilter); + resourceContainer = resourceContainer.filter(configurationFilters); + resourceDeps = resourceDeps.filter(configurationFilters); ResourceContainer processed; if (isLibrary && AndroidCommon.getAndroidConfig(ruleContext).useParallelResourceProcessing()) { @@ -497,7 +502,7 @@ public final class ApplicationManifest { new AndroidResourcesProcessorBuilder(ruleContext) .setLibrary(isLibrary) .setApkOut(resourceContainer.getApk()) - .setResourceFilter(resourceFilter) + .setConfigurationFilters(configurationFilters) .setUncompressedExtensions(uncompressedExtensions) .setCrunchPng(crunchPng) .setJavaPackage(resourceContainer.getJavaPackage()) @@ -506,6 +511,7 @@ public final class ApplicationManifest { .setMergedResourcesOut(mergedResources) .withPrimary(resourceContainer) .withDependencies(resourceDeps) + .setDensities(densities) .setProguardOut(proguardCfg) .setMainDexProguardOut(mainDexProguardCfg) .setDataBindingInfoZip(dataBindingInfoZip) @@ -612,8 +618,7 @@ public final class ApplicationManifest { AndroidAaptActionHelper aaptActionHelper = new AndroidAaptActionHelper(ruleContext, getManifest(), Lists.newArrayList(resourceContainers)); - ResourceFilter resourceFilter = ResourceFilter.fromRuleContext(ruleContext); - + String resourceConfigurationFilters = ResourceConfigurationFilter.extractFilters(ruleContext); List<String> uncompressedExtensions = ruleContext.getTokenizedStringListAttr("nocompress_extensions"); @@ -622,8 +627,8 @@ public final class ApplicationManifest { for (String extension : uncompressedExtensions) { additionalAaptOpts.add("-0").add(extension); } - if (resourceFilter.hasConfigurationFilters() && !resourceFilter.isPrefiltering()) { - additionalAaptOpts.add("-c").add(resourceFilter.getConfigurationFilterString()); + if (!resourceConfigurationFilters.isEmpty()) { + additionalAaptOpts.add("-c").add(resourceConfigurationFilters); } Artifact javaSourcesJar = null; @@ -635,11 +640,9 @@ public final class ApplicationManifest { javaSourcesJar, null, resourceContainer.getJavaPackage(), true); } - aaptActionHelper.createGenerateApkAction( - resourceApk, - resourceContainer.getRenameManifestPackage(), - additionalAaptOpts.build(), - resourceFilter.getDensities()); + List<String> densities = ruleContext.getTokenizedStringListAttr("densities"); + aaptActionHelper.createGenerateApkAction(resourceApk, + resourceContainer.getRenameManifestPackage(), additionalAaptOpts.build(), densities); ResourceContainer updatedResources = resourceContainer.toBuilder() .setLabel(ruleContext.getLabel()) diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceConfigurationFilter.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceConfigurationFilter.java new file mode 100644 index 0000000000..fb27cdd833 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceConfigurationFilter.java @@ -0,0 +1,203 @@ +// Copyright 2017 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.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.VersionQualifier; +import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.collect.nestedset.NestedSet; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; + +/** Filters resources based on their qualifiers. */ +public class ResourceConfigurationFilter { + public static final String ATTR_NAME = "resource_configuration_filters"; + + /** The current {@link RuleContext}, used for reporting errors. */ + private final RuleContext ruleContext; + + /** + * A list of filters that should be applied during the analysis phase. If resources should not be + * filtered in analysis (e.g., if prefilter_resources = 0), this list should be empty. + */ + private final ImmutableList<FolderConfiguration> filters; + + /** + * The raw value of the resource_configuration_filters attribute, as a string of comma-separated + * qualifier strings. This value is passed directly as input to resource processing utilities that + * run in the execution phase, so it may represent different qualifiers than those in {@link + * #filters} if resources should not be pre-filtered during analysis. + */ + private final String filterString; + + /** + * @param ruleContext the current {@link RuleContext}, used for reporting errors + * @param filters a list of filters that should be applied during analysis time. If resources + * should not be filtered in analysis (e.g., if prefilter_resources = 0), this list should be + * empty. + * @param filterString the raw value of the resource configuration filters, as a comma-seperated + * string + */ + private ResourceConfigurationFilter( + RuleContext ruleContext, ImmutableList<FolderConfiguration> filters, String filterString) { + this.ruleContext = ruleContext; + this.filters = filters; + this.filterString = filterString; + } + + static boolean hasFilters(RuleContext ruleContext) { + return ruleContext.attributes().isAttributeValueExplicitlySpecified(ATTR_NAME); + } + + /** + * Extracts the filters from the current RuleContext, as a string. + * + * <p>In BUILD files, filters can be represented as a list of strings, a single comma-seperated + * string, or a combination of both. This method outputs a single comma-seperated string of + * filters, which can then be passed directly to resource processing actions. + * + * @param ruleContext the current {@link RuleContext} + * @return the resource configuration filters contained in the {@link RuleContext}, in a single + * comma-separated string, or an empty string if no filters exist. + */ + static String extractFilters(RuleContext ruleContext) { + if (!hasFilters(ruleContext)) { + return ""; + } + + return Joiner.on(',').join(ruleContext.getTokenizedStringListAttr(ATTR_NAME)); + } + + static ResourceConfigurationFilter fromRuleContext(RuleContext ruleContext) { + Preconditions.checkNotNull(ruleContext); + + String resourceConfigurationFilters = extractFilters(ruleContext); + + if (resourceConfigurationFilters.isEmpty()) { + return empty(ruleContext); + } + + boolean shouldPrefilter = + ruleContext.getFragment(AndroidConfiguration.class).useResourcePrefiltering(); + + if (!shouldPrefilter) { + return new ResourceConfigurationFilter( + ruleContext, ImmutableList.<FolderConfiguration>of(), resourceConfigurationFilters); + } + + ImmutableList.Builder<FolderConfiguration> builder = ImmutableList.builder(); + + for (String filter : resourceConfigurationFilters.split(",")) { + FolderConfiguration folderConfig = FolderConfiguration.getConfigForQualifierString(filter); + + if (folderConfig == null) { + ruleContext.attributeError( + ATTR_NAME, "String '" + filter + "' is not a valid resource configuration filter"); + } else { + builder.add(folderConfig); + } + } + + return new ResourceConfigurationFilter( + ruleContext, builder.build(), resourceConfigurationFilters); + } + + static ResourceConfigurationFilter empty(RuleContext ruleContext) { + return new ResourceConfigurationFilter( + ruleContext, ImmutableList.<FolderConfiguration>of(), ""); + } + + NestedSet<ResourceContainer> filter(NestedSet<ResourceContainer> resources) { + if (filters.isEmpty()) { + /* + * If the filter is empty or resource prefiltering is disabled, just return the original, + * rather than make a copy. + * + * Resources should only be prefiltered in top-level android targets (such as android_binary). + * The output of resource processing, which includes the input NestedSet<ResourceContainer> + * returned by this method, is exposed to other actions via the AndroidResourcesProvider. If + * this method did a no-op copy and collapse in those cases, rather than just return the + * original NestedSet, we would lose all of the advantages around memory and time that + * NestedSets provide: each android_library target would have to copy the resources provided + * by its dependencies into a new NestedSet rather than just create a NestedSet pointing at + * its dependencies's NestedSets. + */ + return resources; + } + + NestedSetBuilder<ResourceContainer> builder = new NestedSetBuilder<>(resources.getOrder()); + + for (ResourceContainer resource : resources) { + builder.add(resource.filter(this)); + } + + return builder.build(); + } + + ImmutableList<Artifact> filter(ImmutableList<Artifact> artifacts) { + if (filters.isEmpty()) { + return artifacts; + } + + ImmutableList.Builder<Artifact> builder = ImmutableList.builder(); + + for (Artifact artifact : artifacts) { + String containingFolder = artifact.getPath().getParentDirectory().getBaseName(); + if (matches(containingFolder)) { + builder.add(artifact); + } + } + + return builder.build(); + } + + private boolean matches(String containingFolder) { + FolderConfiguration config = FolderConfiguration.getConfigForFolder(containingFolder); + + if (config == null) { + ruleContext.ruleError( + "Resource folder '" + containingFolder + "' has invalid resource qualifiers"); + + return true; + } + + // aapt explicitly ignores the version qualifier; duplicate this behavior here. + config.setVersionQualifier(VersionQualifier.getQualifier("")); + + for (FolderConfiguration filter : filters) { + if (config.isMatchFor(filter)) { + return true; + } + } + + return false; + } + + /** + * Returns if this object represents an empty filter. + * + * <p>Note that non-empty filters are not guaranteed to filter resources during the analysis + * phase. + */ + boolean isEmpty() { + return filterString.isEmpty(); + } + + String getFilterString() { + return filterString; + } +} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java index a18daa0027..c89de68856 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java @@ -128,7 +128,7 @@ public abstract class ResourceContainer { /** * Returns a copy of this container with filtered resources. The original container is unchanged. */ - public ResourceContainer filter(ResourceFilter filter) { + public ResourceContainer filter(ResourceConfigurationFilter filter) { return toBuilder().setResources(filter.filter(getResources())).build(); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceDependencies.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceDependencies.java index 72e76c8763..03f85b3556 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceDependencies.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceDependencies.java @@ -29,7 +29,7 @@ import com.google.devtools.build.lib.packages.BuildType; * abstraction simplifies the process of managing and exporting the direct and transitive resource * dependencies of an android rule, as well as providing type safety. * - * <p>The transitive and direct dependencies are not guaranteed to be disjoint. If a + * <p>The transitive and direct dependencies are not guaranteed to be disjoint. If a * library is included in both the transitive and direct dependencies, it will appear twice. This * requires consumers to manage duplicated resources gracefully. */ @@ -42,7 +42,7 @@ public final class ResourceDependencies { private final NestedSet<ResourceContainer> transitiveResources; /** * Contains all the direct dependencies of the current target. Since a given direct dependency can - * act as a "forwarding" library, collecting all the direct resource from it's dependencies + * act as a "forwarding" library, collecting all the direct resource from it's dependencies * and providing them as "direct" dependencies to maintain merge order, this uses a NestedSet to * properly maintain ordering and ease of merging. */ @@ -151,9 +151,9 @@ public final class ResourceDependencies { this.transitiveResources = transitiveResources; this.directResources = directResources; } - + /** Returns a copy of this instance with filtered resources. The original object is unchanged. */ - public ResourceDependencies filter(ResourceFilter filter) { + public ResourceDependencies filter(ResourceConfigurationFilter filter) { return new ResourceDependencies( neverlink, filter.filter(transitiveResources), filter.filter(directResources)); } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceFilter.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceFilter.java deleted file mode 100644 index 26c89cb6b3..0000000000 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceFilter.java +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright 2017 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.android.ide.common.resources.configuration.DensityQualifier; -import com.android.ide.common.resources.configuration.FolderConfiguration; -import com.android.ide.common.resources.configuration.VersionQualifier; -import com.android.resources.Density; -import com.android.resources.ResourceFolderType; -import com.google.common.base.Joiner; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import com.google.devtools.build.lib.actions.Artifact; -import com.google.devtools.build.lib.analysis.RuleContext; -import com.google.devtools.build.lib.collect.nestedset.NestedSet; -import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; -import com.google.devtools.build.lib.syntax.Type; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Filters resources based on their qualifiers. - * - * <p>This includes filtering resources based on both the "resource_configuration_filters" and - * "densities" attributes. - */ -public class ResourceFilter { - public static final String RESOURCE_CONFIGURATION_FILTERS_NAME = "resource_configuration_filters"; - public static final String DENSITIES_NAME = "densities"; - - /** The current {@link RuleContext}, used for reporting errors. */ - private final RuleContext ruleContext; - - /** - * A list of configuration filters that should be applied during the analysis phase. If resources - * should not be filtered in analysis (e.g., if android_binary.prefilter_resources is set to 0), - * this list should be empty. - */ - private final ImmutableList<FolderConfiguration> configurationFilters; - - /** - * The raw value of the {@link #RESOURCE_CONFIGURATION_FILTERS_NAME} attribute, as a string of - * comma-separated qualifier strings. This value is passed directly as input to resource - * processing utilities that run in the execution phase, so it may represent different qualifiers - * than those in {@link #configurationFilters} if resources should not be pre-filtered during - * analysis. - */ - private final String configurationFilterString; - - /** - * A list of density filters that should be applied during the analysis phase. If resources should - * not be filtered in analysis (e.g., if prefilter_resources = 0), this list should be empty. - */ - private final ImmutableList<Density> densities; - - /** - * The raw value of the {@link #DENSITIES_NAME} attribute, as a string of comma-separated - * qualifier strings. This value is passed directly as input to resource processing utilities that - * run in the execution phase, so it may represent different qualifiers than those in {@link - * #densities} if resources should not be pre-filtered during analysis. - */ - private final String densityString; - - private final ImmutableSet.Builder<String> filteredResources = ImmutableSet.builder(); - - /** - * Constructor. - * - * @param ruleContext the current {@link RuleContext}, used for reporting errors - * @param configurationFilters a list of configuration filters that should be applied during - * analysis time. If resources should not be filtered in analysis (e.g., if - * prefilter_resources = 0), this list should be empty. - * @param configurationFilterString the raw value of the resource configuration filters, as a - * comma-separated string - * @param densities a list of densities that should be applied to filter resources during analysis - * time. If resources should not be filtered in analysis (e.g., if prefilter_resources = 0), - * this list should be empty. - * @param densityString the raw value of the densities, as a comma-separated string - */ - private ResourceFilter( - RuleContext ruleContext, - ImmutableList<FolderConfiguration> configurationFilters, - String configurationFilterString, - ImmutableList<Density> densities, - String densityString) { - this.ruleContext = ruleContext; - this.configurationFilters = configurationFilters; - this.configurationFilterString = configurationFilterString; - this.densities = densities; - this.densityString = densityString; - } - - private static boolean hasAttr(RuleContext ruleContext, String attrName) { - return ruleContext.attributes().isAttributeValueExplicitlySpecified(attrName); - } - - static boolean hasFilters(RuleContext ruleContext) { - return hasAttr(ruleContext, RESOURCE_CONFIGURATION_FILTERS_NAME) - || hasAttr(ruleContext, DENSITIES_NAME); - } - - /** - * Extracts filters from the current RuleContext, as a string. - * - * <p>In BUILD files, string lists can be represented as a list of strings, a single - * comma-separated string, or a combination of both. This method outputs a single comma-separated - * string of values, which can then be passed directly to resource processing actions. - * - * @return the values of this attribute contained in the {@link RuleContext}, in a single - * comma-separated string, or an empty string if no filters exist. - */ - private static String extractFilters(RuleContext ruleContext, String attrName) { - if (!hasAttr(ruleContext, attrName)) { - return ""; - } - - return Joiner.on(',').join(ruleContext.attributes().get(attrName, Type.STRING_LIST)); - } - - static ResourceFilter fromRuleContext(RuleContext ruleContext) { - Preconditions.checkNotNull(ruleContext); - - if (!hasFilters(ruleContext)) { - return empty(ruleContext); - } - - String resourceConfigurationFilters = - extractFilters(ruleContext, RESOURCE_CONFIGURATION_FILTERS_NAME); - String densities = extractFilters(ruleContext, DENSITIES_NAME); - - boolean usePrefiltering = - ruleContext.getFragment(AndroidConfiguration.class).useResourcePrefiltering(); - - ImmutableList.Builder<FolderConfiguration> filterBuilder = ImmutableList.builder(); - if (usePrefiltering && !resourceConfigurationFilters.isEmpty()) { - for (String filter : resourceConfigurationFilters.split(",")) { - addIfNotNull( - FolderConfiguration.getConfigForQualifierString(filter), - filter, - filterBuilder, - ruleContext, - RESOURCE_CONFIGURATION_FILTERS_NAME); - } - } - - ImmutableList.Builder<Density> densityBuilder = ImmutableList.builder(); - if (usePrefiltering && !densities.isEmpty()) { - for (String density : densities.split(",")) { - addIfNotNull( - Density.getEnum(density), density, densityBuilder, ruleContext, DENSITIES_NAME); - } - } - - return new ResourceFilter( - ruleContext, - filterBuilder.build(), - resourceConfigurationFilters, - densityBuilder.build(), - densities); - } - - /** Reports an attribute error if the given item is null, and otherwise adds it to the builder. */ - private static <T> void addIfNotNull( - T item, - String itemString, - ImmutableList.Builder<T> builder, - RuleContext ruleContext, - String attrName) { - if (item == null) { - ruleContext.attributeError( - attrName, "String '" + itemString + "' is not a valid value for " + attrName); - } else { - builder.add(item); - } - } - - static ResourceFilter empty(RuleContext ruleContext) { - return new ResourceFilter( - ruleContext, ImmutableList.<FolderConfiguration>of(), "", ImmutableList.<Density>of(), ""); - } - - /** - * Filters a NestedSet of resource containers. This may be a no-op if this filter is empty or if - * resource prefiltering is disabled. - */ - NestedSet<ResourceContainer> filter(NestedSet<ResourceContainer> resources) { - if (!isPrefiltering()) { - /* - * If the filter is empty or resource prefiltering is disabled, just return the original, - * rather than make a copy. - * - * Resources should only be prefiltered in top-level android targets (such as android_binary). - * The output of resource processing, which includes the input NestedSet<ResourceContainer> - * returned by this method, is exposed to other actions via the AndroidResourcesProvider. If - * this method did a no-op copy and collapse in those cases, rather than just return the - * original NestedSet, we would lose all of the advantages around memory and time that - * NestedSets provide: each android_library target would have to copy the resources provided - * by its dependencies into a new NestedSet rather than just create a NestedSet pointing at - * its dependencies's NestedSets. - */ - return resources; - } - - NestedSetBuilder<ResourceContainer> builder = new NestedSetBuilder<>(resources.getOrder()); - - for (ResourceContainer resource : resources) { - builder.add(resource.filter(this)); - } - - return builder.build(); - } - - ImmutableList<Artifact> filter(ImmutableList<Artifact> artifacts) { - if (!isPrefiltering()) { - return artifacts; - } - - /* - * Build an ImmutableSet rather than an ImmutableList to remove duplicate Artifacts in the case - * where one Artifact is the best option for multiple densities. - */ - ImmutableSet.Builder<Artifact> builder = ImmutableSet.builder(); - - List<BestArtifactsForDensity> bestArtifactsForAllDensities = new ArrayList<>(); - for (Density density : densities) { - bestArtifactsForAllDensities.add(new BestArtifactsForDensity(density)); - } - - for (Artifact artifact : artifacts) { - FolderConfiguration configuration = getConfigForArtifact(artifact); - if (!matchesConfigurationFilters(configuration)) { - continue; - } - - if (!shouldFilterByDensity(artifact)) { - builder.add(artifact); - continue; - } - - for (BestArtifactsForDensity bestArtifactsForDensity : bestArtifactsForAllDensities) { - bestArtifactsForDensity.maybeAddArtifact(artifact); - } - } - - for (BestArtifactsForDensity bestArtifactsForDensity : bestArtifactsForAllDensities) { - builder.addAll(bestArtifactsForDensity.get()); - } - - ImmutableSet<Artifact> keptArtifacts = builder.build(); - for (Artifact artifact : artifacts) { - if (keptArtifacts.contains(artifact)) { - continue; - } - - String parentDir = artifact.getPath().getParentDirectory().getBaseName(); - filteredResources.add(parentDir + "/" + artifact.getFilename()); - } - - return keptArtifacts.asList(); - } - - /** - * Tracks the best artifact for a desired density for each combination of filename and non-density - * qualifiers. - */ - private class BestArtifactsForDensity { - private final Density desiredDensity; - - // Use a LinkedHashMap to preserve determinism. - private final Map<String, Artifact> nameAndConfigurationToBestArtifact = new LinkedHashMap<>(); - - public BestArtifactsForDensity(Density density) { - desiredDensity = density; - } - - /** - * @param artifact if this artifact is a better match for this object's desired density than any - * other artifacts with the same name and non-density configuration, adds it to this object. - */ - public void maybeAddArtifact(Artifact artifact) { - FolderConfiguration config = getConfigForArtifact(artifact); - - // We want to find a single best artifact for each combination of non-density qualifiers and - // filename. Combine those two values to create a single unique key. - config.setDensityQualifier(null); - String nameAndConfiguration = config.getUniqueKey() + "/" + artifact.getFilename(); - - Artifact currentBest = nameAndConfigurationToBestArtifact.get(nameAndConfiguration); - - if (currentBest == null || computeAffinity(artifact) < computeAffinity(currentBest)) { - nameAndConfigurationToBestArtifact.put(nameAndConfiguration, artifact); - } - } - - /** @return the collection of best Artifacts for this density. */ - public Collection<Artifact> get() { - return nameAndConfigurationToBestArtifact.values(); - } - - /** - * Compute how well this artifact matches the {@link #desiredDensity}. - * - * <p>Various different codebases have different and sometimes contradictory methods for which - * resources are better in different situations. All of them agree that an exact match is best, - * but: - * - * <p>The android common code (see {@link FolderConfiguration#getDensityQualifier()} treats - * larger densities as better than non-matching smaller densities. - * - * <p>aapt code to filter assets by density prefers the smallest density that is larger than or - * the same as the desired density, or, lacking that, the largest available density. - * - * <p>Other implementations of density filtering include Gradle (to filter which resources - * actually get built into apps) and Android code itself (for the device to decide which - * resource to use). - * - * <p>This particular implementation is based on {@link - * com.google.devtools.build.android.DensitySpecificResourceFilter}, which filters resources by - * density during execution. It prefers to use exact matches when possible, then tries to find - * resources with exactly double the desired density for particularly easy downsizing, and - * otherwise prefers resources that are closest to the desired density, relative to the smaller - * of the available and desired densities. - * - * <p>Once we always filter resources during analysis, we should be able to completely remove - * that code. - * - * @return a score for how well the artifact matches. Lower scores indicate better matches. - */ - private double computeAffinity(Artifact artifact) { - DensityQualifier resourceQualifier = getConfigForArtifact(artifact).getDensityQualifier(); - if (resourceQualifier == null) { - return Double.MAX_VALUE; - } - - int resourceDensity = resourceQualifier.getValue().getDpiValue(); - int density = desiredDensity.getDpiValue(); - - if (resourceDensity == density) { - // Exact match is the best. - return -2; - } - - if (resourceDensity == 2 * density) { - // It's very efficient to downsample an image that's exactly twice the screen - // density, so we prefer that over other non-perfect matches. - return -1; - } - - // Find the ratio between the larger and smaller of the available and desired densities. - double densityRatio = - Math.max(density, resourceDensity) / (double) Math.min(density, resourceDensity); - - if (density < resourceDensity) { - return densityRatio; - } - - // Apply a slight bias against resources that are smaller than those of the desired density. - // This becomes relevant only when we are considering multiple resources with the same ratio. - return densityRatio + 0.01; - } - } - - private FolderConfiguration getConfigForArtifact(Artifact artifact) { - String containingFolder = getContainingFolder(artifact); - FolderConfiguration config = FolderConfiguration.getConfigForFolder(containingFolder); - - if (config == null) { - ruleContext.ruleError( - "Resource folder '" + containingFolder + "' has invalid resource qualifiers"); - - return FolderConfiguration.getConfigForQualifierString(""); - } - - // aapt explicitly ignores the version qualifier; duplicate this behavior here. - config.setVersionQualifier(VersionQualifier.getQualifier("")); - - return config; - } - - /** - * Checks if we should filter this artifact by its density. - * - * <p>We filter by density if there are densities to filter by, the artifact is in a Drawable - * directory, and the artifact is not an XML file. - * - * <p>Similarly-named XML files may contain different resource definitions, so it's impossible to - * ensure that all required resources will be provided without that XML file unless we parse it. - */ - private boolean shouldFilterByDensity(Artifact artifact) { - return !densities.isEmpty() - && !artifact.getExtension().equals("xml") - && ResourceFolderType.getFolderType(getContainingFolder(artifact)) - == ResourceFolderType.DRAWABLE; - } - - private static String getContainingFolder(Artifact artifact) { - return artifact.getPath().getParentDirectory().getBaseName(); - } - - private boolean matchesConfigurationFilters(FolderConfiguration config) { - for (FolderConfiguration filter : configurationFilters) { - if (config.isMatchFor(filter)) { - return true; - } - } - - return configurationFilters.isEmpty(); - } - - /** - * Returns if this object contains a non-empty resource configuration filter. - * - * <p>Note that non-empty filters are not guaranteed to filter resources during the analysis - * phase. - */ - boolean hasConfigurationFilters() { - return !configurationFilterString.isEmpty(); - } - - String getConfigurationFilterString() { - return configurationFilterString; - } - - /** - * Returns if this object contains a non-empty density filter. - * - * <p>Note that non-empty filters are not guaranteed to filter resources during the analysis - * phase. - */ - boolean hasDensities() { - return !densityString.isEmpty(); - } - - String getDensityString() { - return densityString; - } - - List<String> getDensities() { - if (hasDensities()) { - return ImmutableList.copyOf(densityString.split(",")); - } - - return ImmutableList.<String>of(); - } - - boolean isPrefiltering() { - return !densities.isEmpty() || !configurationFilters.isEmpty(); - } - - /** - * TODO: Stop tracking these once android_library targets also filter resources correctly. - * - * <p>Currently, android_library targets pass do no filtering, and all resources are built into - * their symbol files. The android_binary target filters out these resources in analysis. However, - * the filtered resources must be passed to resource processing at execution time so the code - * knows to ignore resources that were filtered out. Without this, resource processing code would - * see references to those resources in dependencies's symbol files, but then be unable to follow - * those references or know whether they were missing due to resource filtering or a bug. - * - * @return a list of resources that were filtered out by this filter - */ - ImmutableList<String> getFilteredResources() { - return filteredResources.build().asList(); - } -} diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceShrinkerActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceShrinkerActionBuilder.java index 60d414ddc4..517b124370 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceShrinkerActionBuilder.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceShrinkerActionBuilder.java @@ -44,7 +44,7 @@ public class ResourceShrinkerActionBuilder { private List<String> uncompressedExtensions = Collections.emptyList(); private List<String> assetsToIgnore = Collections.emptyList(); - private ResourceFilter resourceFilter; + private ResourceConfigurationFilter resourceConfigs; /** * @param ruleContext The RuleContext of the owning rule. @@ -53,7 +53,7 @@ public class ResourceShrinkerActionBuilder { this.ruleContext = ruleContext; this.spawnActionBuilder = new SpawnAction.Builder(); this.sdk = AndroidSdkProvider.fromRuleContext(ruleContext); - this.resourceFilter = ResourceFilter.empty(ruleContext); + this.resourceConfigs = ResourceConfigurationFilter.empty(ruleContext); } public ResourceShrinkerActionBuilder setUncompressedExtensions( @@ -67,9 +67,12 @@ public class ResourceShrinkerActionBuilder { return this; } - /** @param resourceFilter The filters to apply to the resources. */ - public ResourceShrinkerActionBuilder setResourceFilter(ResourceFilter resourceFilter) { - this.resourceFilter = resourceFilter; + /** + * @param resourceConfigs The configuration filters to apply to the resources. + */ + public ResourceShrinkerActionBuilder setConfigurationFilters( + ResourceConfigurationFilter resourceConfigs) { + this.resourceConfigs = resourceConfigs; return this; } @@ -146,7 +149,7 @@ public class ResourceShrinkerActionBuilder { ImmutableList.Builder<Artifact> outputs = ImmutableList.builder(); CustomCommandLine.Builder commandLine = new CustomCommandLine.Builder(); - + // Set the busybox tool. commandLine.add("--tool").add("SHRINK").add("--"); @@ -173,8 +176,8 @@ public class ResourceShrinkerActionBuilder { if (ruleContext.getConfiguration().getCompilationMode() != CompilationMode.OPT) { commandLine.add("--debug"); } - if (resourceFilter.hasConfigurationFilters()) { - commandLine.add("--resourceConfigs").add(resourceFilter.getConfigurationFilterString()); + if (!resourceConfigs.isEmpty()) { + commandLine.add("--resourceConfigs").add(resourceConfigs.getFilterString()); } checkNotNull(resourceFilesZip); |