aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools
diff options
context:
space:
mode:
authorGravatar Tobias Werth <twerth@google.com>2017-03-23 14:19:56 +0000
committerGravatar Yue Gan <yueg@google.com>2017-03-23 14:41:39 +0000
commitdf366408188f0451bae9b2ed079c795a4beb2e2b (patch)
tree55771076f6546fd2922c79993e13f983f8887e19 /src/main/java/com/google/devtools
parent750d718bab83be113b1841b1f408dd4b3f5e82f6 (diff)
-- PiperOrigin-RevId: 151000602 MOS_MIGRATED_REVID=151000602
Diffstat (limited to 'src/main/java/com/google/devtools')
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinary.java21
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryOnlyRule.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidLibrary.java3
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/AndroidResourcesProcessorBuilder.java30
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ApplicationManifest.java39
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ResourceConfigurationFilter.java203
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ResourceContainer.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ResourceDependencies.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ResourceFilter.java480
-rw-r--r--src/main/java/com/google/devtools/build/lib/rules/android/ResourceShrinkerActionBuilder.java19
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);