aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/android/ResourceConfigurationFilter.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules/android/ResourceConfigurationFilter.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ResourceConfigurationFilter.java203
1 files changed, 203 insertions, 0 deletions
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;
+ }
+}