diff options
author | 2018-05-10 11:19:51 -0700 | |
---|---|---|
committer | 2018-05-10 11:21:47 -0700 | |
commit | b5cd7065aeb0370fb6c341bc2578315ca1c62297 (patch) | |
tree | ee969586e61f7f23cd7a836b65e185722b13efc3 /src/main/java/com/google/devtools/build/lib/rules | |
parent | f9e7529908149e0511cc666c8fed879a50dbaea8 (diff) |
Expose android_binary data processing methods to Skylark
Create an AndroidBinaryDataInfo to wrap binary-specific artifacts that
shouldn't be exposed for libraries. This is currently only the final resource
APK.
Continue to extract attribute references from lower-level methods and bubble
them up to the top level.
RELNOTES: none
PiperOrigin-RevId: 196143940
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/rules')
10 files changed, 695 insertions, 124 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 835a2f61a7..0b7706918c 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 @@ -73,6 +73,7 @@ import com.google.devtools.build.lib.rules.java.JavaToolchainProvider; import com.google.devtools.build.lib.rules.java.OneVersionCheckActionBuilder; import com.google.devtools.build.lib.rules.java.ProguardHelper; import com.google.devtools.build.lib.rules.java.ProguardHelper.ProguardOutput; +import com.google.devtools.build.lib.rules.java.ProguardSpecProvider; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; @@ -80,6 +81,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import javax.annotation.Nullable; /** An implementation for the "android_binary" rule. */ @@ -210,7 +212,15 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { /* conditionalKeepRules = */ shouldShrinkResourceCycles( ruleContext, shrinkResources), applicationManifest.getManifestValues(), - aaptVersion) + aaptVersion, + AndroidResources.from(ruleContext, "resource_files"), + AndroidAssets.from(ruleContext), + resourceDeps, + AssetDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false), + ResourceFilterFactory.fromRuleContextAndAttrs(ruleContext), + ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions"), + ruleContext.attributes().get("crunch_png", Type.BOOLEAN), + DataBinding.isEnabled(ruleContext)) .generateRClass(ruleContext, aaptVersion); } else { applicationManifest = @@ -231,7 +241,7 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK), resourceDeps, ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), - ResourceFilterFactory.fromRuleContext(ruleContext), + ResourceFilterFactory.fromRuleContextAndAttrs(ruleContext), ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), ProguardHelper.getProguardConfigArtifact(ruleContext, ""), @@ -361,22 +371,18 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { throws InterruptedException, RuleErrorException { ImmutableList<Artifact> proguardSpecs = - ProguardHelper.collectTransitiveProguardSpecs( - ruleContext, ImmutableList.of(resourceApk.getResourceProguardConfig())); - - boolean assumeMinSdkVersion = - ruleContext.getFragment(AndroidConfiguration.class).assumeMinSdkVersion(); - if (!proguardSpecs.isEmpty() && assumeMinSdkVersion) { - // NB: Order here is important. We're including generated Proguard specs before the user's - // specs so that they can override values. - proguardSpecs = - ImmutableList.<Artifact>builder() - .addAll( - androidSemantics.getProguardSpecsForManifest( - ruleContext, applicationManifest.getManifest())) - .addAll(proguardSpecs) - .build(); - } + getProguardSpecs( + ruleContext, + androidSemantics, + resourceApk.getResourceProguardConfig(), + applicationManifest.getManifest(), + ruleContext.attributes().has(ProguardHelper.PROGUARD_SPECS, BuildType.LABEL_LIST) + ? ruleContext + .getPrerequisiteArtifacts(ProguardHelper.PROGUARD_SPECS, Mode.TARGET) + .list() + : ImmutableList.<Artifact>of(), + ruleContext.getPrerequisiteArtifacts(":extra_proguard_specs", Mode.TARGET).list(), + ruleContext.getPrerequisites("deps", Mode.TARGET, ProguardSpecProvider.class)); // TODO(bazel-team): Verify that proguard spec files don't contain -printmapping directions // which this -printmapping command line flag will override. @@ -763,6 +769,38 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { return new ProguardOutput(deployJarArtifact, null, null, null, null, null, null); } + static ImmutableList<Artifact> getProguardSpecs( + RuleContext ruleContext, + AndroidSemantics androidSemantics, + Artifact resourceProguardConfig, + Artifact mergedManifest, + ImmutableList<Artifact> localProguardSpecs, + ImmutableList<Artifact> extraProguardSpecs, + Iterable<ProguardSpecProvider> proguardDeps) { + + ImmutableList<Artifact> proguardSpecs = + ProguardHelper.collectTransitiveProguardSpecs( + ruleContext, + Iterables.concat( + ImmutableList.of(resourceProguardConfig), extraProguardSpecs), + localProguardSpecs, + proguardDeps); + + boolean assumeMinSdkVersion = + ruleContext.getFragment(AndroidConfiguration.class).assumeMinSdkVersion(); + if (!proguardSpecs.isEmpty() && assumeMinSdkVersion) { + // NB: Order here is important. We're including generated Proguard specs before the user's + // specs so that they can override values. + proguardSpecs = + ImmutableList.<Artifact>builder() + .addAll(androidSemantics.getProguardSpecsForManifest(ruleContext, mergedManifest)) + .addAll(proguardSpecs) + .build(); + } + + return proguardSpecs; + } + /** Returns {@code true} if resource shrinking should be performed. */ private static boolean shouldShrinkResources(RuleContext ruleContext) { TriState state = ruleContext.attributes().get("shrink_resources", BuildType.TRISTATE); @@ -776,8 +814,8 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { } /** Returns {@code true} if resource shrinking should be performed. */ - private static boolean shouldShrinkResourceCycles( - RuleContext ruleContext, boolean shrinkResources) throws RuleErrorException { + static boolean shouldShrinkResourceCycles(RuleContext ruleContext, boolean shrinkResources) + throws RuleErrorException { boolean global = ruleContext.getFragment(AndroidConfiguration.class).useAndroidResourceCycleShrinking(); if (global && !shrinkResources) { @@ -793,39 +831,68 @@ public abstract class AndroidBinary implements RuleConfiguredTargetFactory { ImmutableList<Artifact> proguardSpecs, ProguardOutput proguardOutput, NestedSetBuilder<Artifact> filesBuilder) - throws InterruptedException, RuleErrorException { + throws RuleErrorException, InterruptedException { - if (!proguardSpecs.isEmpty()) { + Optional<Artifact> maybeShrunkApk = + maybeShrinkResources( + ruleContext, + resourceApk.getValidatedResources(), + resourceApk.getResourceDependencies(), + proguardSpecs, + proguardOutput.getOutputJar(), + proguardOutput.getMapping(), + AndroidAaptVersion.chooseTargetAaptVersion(ruleContext), + ResourceFilterFactory.fromRuleContextAndAttrs(ruleContext), + ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions")); - Artifact apk = - new ResourceShrinkerActionBuilder(ruleContext) - .setResourceApkOut( - ruleContext.getImplicitOutputArtifact( - AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_APK)) - .setShrunkResourcesOut( - ruleContext.getImplicitOutputArtifact( - AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_ZIP)) - .setLogOut( - ruleContext.getImplicitOutputArtifact( - AndroidRuleClasses.ANDROID_RESOURCE_SHRINKER_LOG)) - .withResourceFiles( - ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) - .withShrunkJar(proguardOutput.getOutputJar()) - .withProguardMapping(proguardOutput.getMapping()) - .withPrimary(resourceApk.getValidatedResources()) - .withDependencies(resourceApk.getResourceDependencies()) - .setTargetAaptVersion(AndroidAaptVersion.chooseTargetAaptVersion(ruleContext)) - .setResourceFilterFactory(ResourceFilterFactory.fromRuleContext(ruleContext)) - .setUncompressedExtensions( - ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions")) - .build(); + if (maybeShrunkApk.isPresent()) { filesBuilder.add( ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCE_SHRINKER_LOG)); - return resourceApk.withApk(apk); + return resourceApk.withApk(maybeShrunkApk.get()); } + return resourceApk; } + static Optional<Artifact> maybeShrinkResources( + RuleContext ruleContext, + ValidatedAndroidData validatedResources, + ResourceDependencies resourceDeps, + ImmutableList<Artifact> proguardSpecs, + Artifact proguardOutputJar, + Artifact proguardMapping, + AndroidAaptVersion aaptVersion, + ResourceFilterFactory resourceFilterFactory, + List<String> noCompressExtensions) + throws InterruptedException { + + if (proguardSpecs.isEmpty()) { + return Optional.empty(); + } + + return Optional.of( + new ResourceShrinkerActionBuilder(ruleContext) + .setResourceApkOut( + ruleContext.getImplicitOutputArtifact( + AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_APK)) + .setShrunkResourcesOut( + ruleContext.getImplicitOutputArtifact( + AndroidRuleClasses.ANDROID_RESOURCES_SHRUNK_ZIP)) + .setLogOut( + ruleContext.getImplicitOutputArtifact( + AndroidRuleClasses.ANDROID_RESOURCE_SHRINKER_LOG)) + .withResourceFiles( + ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_ZIP)) + .withShrunkJar(proguardOutputJar) + .withProguardMapping(proguardMapping) + .withPrimary(validatedResources) + .withDependencies(resourceDeps) + .setTargetAaptVersion(aaptVersion) + .setResourceFilterFactory(resourceFilterFactory) + .setUncompressedExtensions(noCompressExtensions) + .build()); + } + @Immutable static final class DexingOutput { private final Artifact classesDexZip; diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryDataInfo.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryDataInfo.java index e17ddefbc9..d3de6d02ba 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryDataInfo.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidBinaryDataInfo.java @@ -79,4 +79,9 @@ public class AndroidBinaryDataInfo extends NativeInfo { public AndroidManifestInfo getManifestInfo() { return manifestInfo; } + + public AndroidBinaryDataInfo withShrunkApk(Artifact shrunkApk) { + return new AndroidBinaryDataInfo( + shrunkApk, resourceProguardConfig, resourcesInfo, assetsInfo, manifestInfo); + } } diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java index 7fc06665c0..93cf70a9b9 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidLocalTestBase.java @@ -117,7 +117,7 @@ public abstract class AndroidLocalTestBase implements RuleConfiguredTargetFactor ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_RESOURCES_APK), resourceDependencies, ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_R_TXT), - ResourceFilterFactory.fromRuleContext(ruleContext), + ResourceFilterFactory.fromRuleContextAndAttrs(ruleContext), ImmutableList.of(), /* list of uncompressed extensions */ false, /* crunch png */ ProguardHelper.getProguardConfigArtifact(ruleContext, ""), diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifest.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifest.java index 44891cd415..fc5cd5ebfb 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifest.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidManifest.java @@ -104,7 +104,7 @@ public class AndroidManifest { * * <p>AndroidSemantics-specific processing will be used if a non-null AndroidSemantics is passed. */ - private static AndroidManifest from( + static AndroidManifest from( RuleContext ruleContext, @Nullable Artifact rawManifest, @Nullable AndroidSemantics androidSemantics, diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java index ab8fdb9022..827daaa3c0 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidSkylarkData.java @@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.FileProvider; +import com.google.devtools.build.lib.analysis.TransitiveInfoProvider; import com.google.devtools.build.lib.analysis.skylark.SkylarkRuleContext; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; @@ -30,6 +31,7 @@ import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidA import com.google.devtools.build.lib.rules.android.AndroidLibraryAarInfo.Aar; import com.google.devtools.build.lib.rules.java.JavaCompilationInfoProvider; import com.google.devtools.build.lib.rules.java.JavaInfo; +import com.google.devtools.build.lib.rules.java.ProguardSpecProvider; import com.google.devtools.build.lib.skylarkinterface.Param; import com.google.devtools.build.lib.skylarkinterface.SkylarkCallable; import com.google.devtools.build.lib.skylarkinterface.SkylarkModule; @@ -40,6 +42,7 @@ import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.List; import java.util.Objects; +import java.util.Optional; import javax.annotation.Nullable; /** Skylark-visible methods for working with Android data (manifests, resources, and assets). */ @@ -502,11 +505,6 @@ public abstract class AndroidSkylarkData { + " resources_from_deps to inherit without defining them."); } - ImmutableList.Builder<Artifact> proguardSpecBuilder = ImmutableList.builder(); - for (FileProvider provider : getFileProviders(proguardSpecs)) { - proguardSpecBuilder.addAll(provider.getFilesToBuild()); - } - return Aar.makeAar( ctx.getRuleContext(), resources, @@ -514,7 +512,7 @@ public abstract class AndroidSkylarkData { resourcesInfo.getManifest(), resourcesInfo.getRTxt(), libraryClassJar, - proguardSpecBuilder.build()) + filesFromConfiguredTargets(proguardSpecs)) .toProvider(deps, definesLocalResources); } @@ -865,6 +863,437 @@ public abstract class AndroidSkylarkData { } } + /** + * Skylark API for bundling common setting for working with resources in android_binary + * + * <p>TODO(b/79159379): Stop passing SkylarkRuleContext here + * + * @param ctx the SkylarkRuleContext. We will soon change to using an ActionConstructionContext + * instead. See b/79159379 + */ + @SkylarkCallable( + name = "make_binary_settings", + mandatoryPositionals = 1, // SkylarkRuleContext is mandatory + parameters = { + @Param( + name = "shrink_resources", + positional = false, + noneable = true, + defaultValue = "None", + type = Boolean.class, + named = true, + doc = + "Whether to shrink resources. Defaults to the value used in Android" + + " configuration."), + @Param( + name = "resource_configuration_filters", + positional = false, + defaultValue = "[]", + type = SkylarkList.class, + generic1 = String.class, + named = true, + doc = + "A list of resource configuration filters, such 'en' that will limit the resources" + + " in the apk to only the ones in the 'en' configuration."), + @Param( + name = "densities", + positional = false, + defaultValue = "[]", + type = SkylarkList.class, + generic1 = String.class, + named = true, + doc = + "Densities to filter for when building the apk. A corresponding compatible-screens" + + " section will also be added to the manifest if it does not already contain a" + + " superset listing."), + @Param( + name = "nocompress_extensions", + positional = false, + defaultValue = "[]", + type = SkylarkList.class, + generic1 = String.class, + named = true, + doc = "A list of file extension to leave uncompressed in apk."), + @Param( + name = "aapt_version", + positional = false, + defaultValue = "'auto'", + type = String.class, + named = true, + doc = + "The version of aapt to use. Defaults to 'auto'. 'aapt' and 'aapt2' are also" + + " supported."), + }, + doc = + "Returns a wrapper object containing various settings shared across multiple methods for" + + " processing binary data.") + public BinaryDataSettings makeBinarySettings( + SkylarkRuleContext ctx, + Object shrinkResources, + SkylarkList<String> resourceConfigurationFilters, + SkylarkList<String> densities, + SkylarkList<String> rawNoCompressExtensions, + String aaptVersionString) + throws EvalException { + AndroidConfiguration androidConfig = AndroidCommon.getAndroidConfig(ctx.getRuleContext()); + + AndroidAaptVersion aaptVersion; + try { + aaptVersion = + AndroidAaptVersion.chooseTargetAaptVersion( + ctx.getRuleContext(), androidConfig, aaptVersionString); + } catch (RuleErrorException e) { + throw new EvalException(Location.BUILTIN, e); + } + + return new BinaryDataSettings( + aaptVersion, + fromNoneableOrDefault( + shrinkResources, Boolean.class, androidConfig.useAndroidResourceShrinking()), + ResourceFilterFactory.from(aaptVersion, resourceConfigurationFilters, densities), + ctx.getRuleContext() + .getExpander() + .withDataLocations() + .tokenized("nocompress_extensions", rawNoCompressExtensions)); + } + + /** + * Helper method to get default {@link + * com.google.devtools.build.lib.rules.android.AndroidSkylarkData.BinaryDataSettings}. + */ + private BinaryDataSettings defaultBinaryDataSettings(SkylarkRuleContext ctx) + throws EvalException { + return makeBinarySettings( + ctx, + Runtime.NONE, + SkylarkList.createImmutable(ImmutableList.of()), + SkylarkList.createImmutable(ImmutableList.of()), + SkylarkList.createImmutable(ImmutableList.of()), + "auto"); + } + + @SkylarkModule( + name = "AndroidBinaryDataSettings", + doc = "Wraps common settings for working with android_binary assets, resources, and manifest") + private static class BinaryDataSettings { + private final AndroidAaptVersion aaptVersion; + private final boolean shrinkResources; + private final ResourceFilterFactory resourceFilterFactory; + private final ImmutableList<String> noCompressExtensions; + + private BinaryDataSettings( + AndroidAaptVersion aaptVersion, + boolean shrinkResources, + ResourceFilterFactory resourceFilterFactory, + ImmutableList<String> noCompressExtensions) { + this.aaptVersion = aaptVersion; + this.shrinkResources = shrinkResources; + this.resourceFilterFactory = resourceFilterFactory; + this.noCompressExtensions = noCompressExtensions; + } + } + + /** + * Skylark API for processing assets, resources, and manifest for android_binary + * + * <p>TODO(b/79159379): Stop passing SkylarkRuleContext here + * + * @param ctx the SkylarkRuleContext. We will soon change to using an ActionConstructionContext + * instead. See b/79159379 + */ + @SkylarkCallable( + name = "process_binary_data", + mandatoryPositionals = 1, // SkylarkRuleContext is mandatory + parameters = { + @Param( + name = "resources", + positional = false, + defaultValue = "[]", + type = SkylarkList.class, + generic1 = FileProvider.class, + named = true, + doc = "Providers of this target's resources"), + @Param( + name = "assets", + positional = false, + defaultValue = "None", + type = SkylarkList.class, + generic1 = ConfiguredTarget.class, + noneable = true, + named = true, + doc = + "Targets containing raw assets for this target. If passed, 'assets_dir' must also" + + " be passed."), + @Param( + name = "assets_dir", + positional = false, + defaultValue = "None", + type = String.class, + noneable = true, + named = true, + doc = + "Directory the assets are contained in. Must be passed if and only if 'assets' is" + + " passed. This path will be split off of the asset paths on the device."), + @Param( + name = "manifest", + positional = false, + type = Artifact.class, + defaultValue = "None", + named = true, + noneable = true, + doc = + "If passed, the manifest to use for this target. Otherwise, a dummy manifest will" + + " be generated."), + @Param( + name = "custom_package", + positional = false, + defaultValue = "None", + type = String.class, + noneable = true, + named = true, + doc = + "The Android application package to stamp the manifest with. If not provided, the" + + " current Java package, derived from the location of this target's BUILD" + + " file, will be used. For example, given a BUILD file in" + + " 'java/com/foo/bar/BUILD', the package would be 'com.foo.bar'."), + @Param( + name = "manifest_values", + positional = false, + defaultValue = "{}", + type = SkylarkDict.class, + generic1 = String.class, + named = true, + doc = "A dictionary of values to be overridden in the manifest."), + @Param( + name = "deps", + positional = false, + defaultValue = "[]", + type = SkylarkList.class, + generic1 = ConfiguredTarget.class, + named = true, + doc = + "Dependency targets. Providers will be extracted from these dependencies for each" + + " type of data."), + @Param( + name = "manifest_merger", + type = String.class, + defaultValue = "'auto'", + positional = false, + named = true, + doc = + "The manifest merger to use. Defaults to 'auto', but 'android' and 'legacy' are" + + " also supported."), + @Param( + name = "binary_settings", + type = BinaryDataSettings.class, + noneable = true, + defaultValue = "None", + positional = false, + named = true, + doc = + "Settings common to various binary processing methods, created with" + + " make_binary_data_settings"), + @Param( + name = "crunch_png", + positional = false, + defaultValue = "True", + type = Boolean.class, + named = true, + doc = "Whether PNG crunching should be done. Defaults to True."), + @Param( + name = "enable_data_binding", + positional = false, + defaultValue = "False", + type = Boolean.class, + named = true, + doc = + "Defaults to False. If True, processes data binding expressions in layout" + + " resources."), + }, + doc = + "Processes resources, assets, and manifests for android_binary and returns the" + + " appropriate providers.") + public AndroidBinaryDataInfo processBinaryData( + SkylarkRuleContext ctx, + SkylarkList<ConfiguredTarget> resources, + Object assets, + Object assetsDir, + Object manifest, + Object customPackage, + SkylarkDict<String, String> rawManifestValues, + SkylarkList<ConfiguredTarget> deps, + String manifestMerger, + Object maybeSettings, + boolean crunchPng, + boolean dataBindingEnabled) + throws InterruptedException, RuleErrorException, EvalException { + + BinaryDataSettings settings = + fromNoneableOrDefault( + maybeSettings, BinaryDataSettings.class, defaultBinaryDataSettings(ctx)); + + AndroidManifest rawManifest = + AndroidManifest.from( + ctx.getRuleContext(), + fromNoneable(manifest, Artifact.class), + getAndroidSemantics(), + fromNoneable(customPackage, String.class), + /* exportsManifest = */ false); + + ResourceDependencies resourceDeps = + ResourceDependencies.fromProviders( + getProviders(deps, AndroidResourcesInfo.PROVIDER), /* neverlink = */ false); + + ImmutableMap<String, String> manifestValues = + ApplicationManifest.getManifestValues(ctx.getRuleContext(), rawManifestValues); + + StampedAndroidManifest stampedManifest = + rawManifest.mergeWithDeps( + ctx.getRuleContext(), + resourceDeps, + manifestValues, + ApplicationManifest.useLegacyMerging(ctx.getRuleContext(), manifestMerger)); + + ResourceApk resourceApk = + ProcessedAndroidData.processBinaryDataFrom( + ctx.getRuleContext(), + stampedManifest, + AndroidBinary.shouldShrinkResourceCycles( + ctx.getRuleContext(), settings.shrinkResources), + manifestValues, + settings.aaptVersion, + AndroidResources.from( + ctx.getRuleContext(), getFileProviders(resources), "resource_files"), + AndroidAssets.from( + ctx.getRuleContext(), + listFromNoneable(assets, ConfiguredTarget.class), + isNone(assetsDir) + ? null + : PathFragment.create(fromNoneable(assetsDir, String.class))), + resourceDeps, + AssetDependencies.fromProviders( + getProviders(deps, AndroidAssetsInfo.PROVIDER), /* neverlink = */ false), + settings.resourceFilterFactory, + settings.noCompressExtensions, + crunchPng, + dataBindingEnabled) + .generateRClass(ctx.getRuleContext(), settings.aaptVersion); + + return AndroidBinaryDataInfo.of( + resourceApk.getArtifact(), + resourceApk.getResourceProguardConfig(), + resourceApk.toResourceInfo(ctx.getLabel()), + resourceApk.toAssetsInfo(ctx.getLabel()).get(), + resourceApk.toManifestInfo().get()); + } + + /** + * Skylark API for shrinking a resource APK + * + * <p>TODO(b/79159379): Stop passing SkylarkRuleContext here + * + * @param ctx the SkylarkRuleContext. We will soon change to using an ActionConstructionContext + * instead. See b/79159379 + */ + @SkylarkCallable( + name = "shrink_data_apk", + // Required: SkylarkRuleContext, AndroidBinaryDataInfo to shrink, and two proguard outputs + mandatoryPositionals = 4, + parameters = { + @Param( + name = "binary_settings", + type = BinaryDataSettings.class, + noneable = true, + defaultValue = "None", + positional = false, + named = true, + doc = + "Settings common to various binary processing methods, created with" + + " make_binary_data_settings"), + @Param( + name = "deps", + positional = false, + defaultValue = "[]", + type = SkylarkList.class, + generic1 = ConfiguredTarget.class, + named = true, + doc = + "Dependency targets. Providers will be extracted from these dependencies for each" + + " type of data."), + @Param( + name = "proguard_specs", + type = SkylarkList.class, + generic1 = ConfiguredTarget.class, + defaultValue = "[]", + positional = false, + named = true, + doc = + "Files to be used as Proguard specification for this target, which will be" + + " inherited in the top-level target"), + @Param( + name = "extra_proguard_specs,", + type = SkylarkList.class, + generic1 = ConfiguredTarget.class, + defaultValue = "[]", + positional = false, + named = true, + doc = + "Additional proguard specs that should be added for top-level targets. This value" + + " is controlled by Java configuration."), + }, + doc = + "Possibly shrinks the data APK by removing resources that were marked as unused during" + + " proguarding.") + public AndroidBinaryDataInfo shrinkDataApk( + SkylarkRuleContext ctx, + AndroidBinaryDataInfo binaryDataInfo, + Artifact proguardOutputJar, + Artifact proguardMapping, + Object maybeSettings, + SkylarkList<ConfiguredTarget> deps, + SkylarkList<ConfiguredTarget> localProguardSpecs, + SkylarkList<ConfiguredTarget> extraProguardSpecs) + throws EvalException, InterruptedException { + BinaryDataSettings settings = + fromNoneableOrDefault( + maybeSettings, BinaryDataSettings.class, defaultBinaryDataSettings(ctx)); + + if (!settings.shrinkResources) { + return binaryDataInfo; + } + + ImmutableList<Artifact> proguardSpecs = + AndroidBinary.getProguardSpecs( + ctx.getRuleContext(), + getAndroidSemantics(), + binaryDataInfo.getResourceProguardConfig(), + binaryDataInfo.getManifestInfo().getManifest(), + filesFromConfiguredTargets(localProguardSpecs), + filesFromConfiguredTargets(extraProguardSpecs), + getProviders(deps, ProguardSpecProvider.class)); + + // TODO(asteinb): There should never be more than one direct resource exposed in the provider. + // Can we adjust its structure to take this into account? + if (!binaryDataInfo.getResourcesInfo().getDirectAndroidResources().isSingleton()) { + throw new EvalException(Location.BUILTIN, "TODO"); + } + + Optional<Artifact> maybeShrunkApk = + AndroidBinary.maybeShrinkResources( + ctx.getRuleContext(), + binaryDataInfo.getResourcesInfo().getDirectAndroidResources().toList().get(0), + ResourceDependencies.fromProviders( + getProviders(deps, AndroidResourcesInfo.PROVIDER), /* neverlink = */ false), + proguardSpecs, + proguardOutputJar, + proguardMapping, + settings.aaptVersion, + settings.resourceFilterFactory, + settings.noCompressExtensions); + + return maybeShrunkApk.map(binaryDataInfo::withShrunkApk).orElse(binaryDataInfo); + } + public static SkylarkDict<NativeProvider<?>, NativeInfo> getNativeInfosFrom( ResourceApk resourceApk, Label label) { ImmutableMap.Builder<NativeProvider<?>, NativeInfo> builder = ImmutableMap.builder(); @@ -943,25 +1372,30 @@ public abstract class AndroidSkylarkData { return SkylarkList.castList(asList, clazz, null); } + private static ImmutableList<Artifact> filesFromConfiguredTargets( + SkylarkList<ConfiguredTarget> targets) { + ImmutableList.Builder<Artifact> builder = ImmutableList.builder(); + for (FileProvider provider : getFileProviders(targets)) { + builder.addAll(provider.getFilesToBuild()); + } + + return builder.build(); + } + private static ImmutableList<FileProvider> getFileProviders( SkylarkList<ConfiguredTarget> targets) { + return getProviders(targets, FileProvider.class); + } + + private static <T extends TransitiveInfoProvider> ImmutableList<T> getProviders( + SkylarkList<ConfiguredTarget> targets, Class<T> clazz) { return targets .stream() - .map(target -> target.getProvider(FileProvider.class)) + .map(target -> target.getProvider(clazz)) .filter(Objects::nonNull) .collect(ImmutableList.toImmutableList()); } - private static <T> SkylarkList<T> listFromNoneableOrEmpty(Object object, Class<T> clazz) - throws EvalException { - List<T> value = listFromNoneable(object, clazz); - if (value == null) { - return SkylarkList.createImmutable(ImmutableList.of()); - } - - return SkylarkList.createImmutable(value); - } - public static <T extends NativeInfo> SkylarkList<T> getProviders( SkylarkList<ConfiguredTarget> targets, NativeProvider<T> provider) { return SkylarkList.createImmutable( @@ -971,4 +1405,14 @@ public abstract class AndroidSkylarkData { .filter(Objects::nonNull) .collect(ImmutableList.toImmutableList())); } + + private static <T> SkylarkList<T> listFromNoneableOrEmpty(Object object, Class<T> clazz) + throws EvalException { + List<T> value = listFromNoneable(object, clazz); + if (value == null) { + return SkylarkList.createImmutable(ImmutableList.of()); + } + + return SkylarkList.createImmutable(value); + } } 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 deb2cb530e..a5d3be231f 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 @@ -528,7 +528,7 @@ public final class ApplicationManifest { // Filter the resources during analysis to prevent processing of dependencies on unwanted // resources during execution. ResourceFilterFactory resourceFilterFactory = - ResourceFilterFactory.fromRuleContext(ruleContext); + ResourceFilterFactory.fromRuleContextAndAttrs(ruleContext); ResourceFilter resourceFilter = resourceFilterFactory.getResourceFilter(ruleContext, resourceDeps, resources); resources = resources.filterLocalResources(ruleContext, resourceFilter); diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ProcessedAndroidData.java b/src/main/java/com/google/devtools/build/lib/rules/android/ProcessedAndroidData.java index 6cbaa5ee23..3cffbf4dd3 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ProcessedAndroidData.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ProcessedAndroidData.java @@ -19,9 +19,11 @@ import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.config.CompilationMode; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; +import com.google.devtools.build.lib.packages.RuleErrorConsumer; import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidAaptVersion; import com.google.devtools.build.lib.rules.java.ProguardHelper; import com.google.devtools.build.lib.syntax.Type; +import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -57,10 +59,17 @@ public class ProcessedAndroidData { StampedAndroidManifest manifest, boolean conditionalKeepRules, Map<String, String> manifestValues, - AndroidAaptVersion aaptVersion) + AndroidAaptVersion aaptVersion, + AndroidResources resources, + AndroidAssets assets, + ResourceDependencies resourceDeps, + AssetDependencies assetDeps, + ResourceFilterFactory resourceFilterFactory, + List<String> noCompressExtensions, + boolean crunchPng, + boolean dataBindingEnabled) throws RuleErrorException, InterruptedException { - if (conditionalKeepRules - && AndroidAaptVersion.chooseTargetAaptVersion(ruleContext) != AndroidAaptVersion.AAPT2) { + if (conditionalKeepRules && aaptVersion != AndroidAaptVersion.AAPT2) { throw ruleContext.throwWithRuleError( "resource cycle shrinking can only be enabled for builds with aapt2"); } @@ -68,7 +77,7 @@ public class ProcessedAndroidData { AndroidResourcesProcessorBuilder builder = builderForNonIncrementalTopLevelTarget(ruleContext, manifest, manifestValues, aaptVersion) .setUseCompiledResourcesForMerge( - AndroidAaptVersion.chooseTargetAaptVersion(ruleContext) == AndroidAaptVersion.AAPT2 + aaptVersion == AndroidAaptVersion.AAPT2 && AndroidCommon.getAndroidConfig(ruleContext).skipParsingAction()) .setManifestOut( ruleContext.getImplicitOutputArtifact( @@ -78,9 +87,7 @@ public class ProcessedAndroidData { .setMainDexProguardOut(AndroidBinary.createMainDexProguardSpec(ruleContext)) .conditionalKeepRules(conditionalKeepRules) .setDataBindingInfoZip( - DataBinding.isEnabled(ruleContext) - ? DataBinding.getLayoutInfoFile(ruleContext) - : null) + dataBindingEnabled ? DataBinding.getLayoutInfoFile(ruleContext) : null) .setFeatureOf( ruleContext.attributes().isAttributeValueExplicitlySpecified("feature_of") ? ruleContext @@ -93,7 +100,17 @@ public class ProcessedAndroidData { .getPrerequisite("feature_after", Mode.TARGET, ApkInfo.PROVIDER) .getApk() : null); - return buildActionForBinary(ruleContext, builder, manifest); + return buildActionForBinary( + ruleContext, + builder, + manifest, + resources, + assets, + resourceDeps, + assetDeps, + resourceFilterFactory, + noCompressExtensions, + crunchPng); } public static ProcessedAndroidData processIncrementalBinaryDataFrom( @@ -108,36 +125,46 @@ public class ProcessedAndroidData { builderForTopLevelTarget(ruleContext, manifest, proguardPrefix, manifestValues) .setApkOut(apkOut); - return buildActionForBinary(ruleContext, builder, manifest); + return buildActionForBinary( + ruleContext, + builder, + manifest, + AndroidResources.from(ruleContext, "resource_files"), + AndroidAssets.from(ruleContext), + ResourceDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false), + AssetDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false), + ResourceFilterFactory.fromRuleContextAndAttrs(ruleContext), + ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions"), + ruleContext.attributes().get("crunch_png", Type.BOOLEAN)); } private static ProcessedAndroidData buildActionForBinary( - RuleContext ruleContext, + RuleErrorConsumer errorConsumer, AndroidResourcesProcessorBuilder builder, - StampedAndroidManifest manifest) + StampedAndroidManifest manifest, + AndroidResources resources, + AndroidAssets assets, + ResourceDependencies resourceDeps, + AssetDependencies assetDeps, + ResourceFilterFactory resourceFilterFactory, + List<String> noCompressExtensions, + boolean crunchPng) throws RuleErrorException { - AndroidResources resources = AndroidResources.from(ruleContext, "resource_files"); - ResourceDependencies resourceDeps = - ResourceDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false); - ResourceFilterFactory resourceFilterFactory = - ResourceFilterFactory.fromRuleContext(ruleContext); - ResourceFilter resourceFilter = - resourceFilterFactory.getResourceFilter(ruleContext, resourceDeps, resources); + resourceFilterFactory.getResourceFilter(errorConsumer, resourceDeps, resources); // Filter unwanted resources out - resources = resources.filterLocalResources(ruleContext, resourceFilter); - resourceDeps = resourceDeps.filter(ruleContext, resourceFilter); + resources = resources.filterLocalResources(errorConsumer, resourceFilter); + resourceDeps = resourceDeps.filter(errorConsumer, resourceFilter); return builder .setResourceFilterFactory(resourceFilterFactory) - .setUncompressedExtensions( - ruleContext.getExpander().withDataLocations().tokenized("nocompress_extensions")) - .setCrunchPng(ruleContext.attributes().get("crunch_png", Type.BOOLEAN)) + .setUncompressedExtensions(noCompressExtensions) + .setCrunchPng(crunchPng) .withResourceDependencies(resourceDeps) - .withAssetDependencies(AssetDependencies.fromRuleDeps(ruleContext, /* neverlink = */ false)) - .build(resources, AndroidAssets.from(ruleContext), manifest); + .withAssetDependencies(assetDeps) + .build(resources, assets, manifest); } /** Processes Android data (assets, resources, and manifest) for android_local_test targets. */ diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceFilterFactory.java b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceFilterFactory.java index 14e20362ff..848125a81f 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/ResourceFilterFactory.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ResourceFilterFactory.java @@ -84,13 +84,14 @@ public class ResourceFilterFactory { this.filterInAnalysis = filterInAnalysis; } - private static boolean hasAttr(AttributeMap attrs, String attrName) { - if (!attrs.isAttributeValueExplicitlySpecified(attrName)) { - return false; + private static List<String> rawFiltersFromAttrs(AttributeMap attrs, String attrName) { + if (attrs.isAttributeValueExplicitlySpecified(attrName)) { + List<String> rawValue = attrs.get(attrName, Type.STRING_LIST); + if (rawValue != null) { + return rawValue; + } } - - List<String> values = attrs.get(attrName, Type.STRING_LIST); - return values != null && !values.isEmpty(); + return ImmutableList.of(); } /** @@ -102,8 +103,8 @@ public class ResourceFilterFactory { * * @return the values of this attribute contained in the {@link AttributeMap}, as a list. */ - private static ImmutableList<String> extractFilters(AttributeMap attrs, String attrName) { - if (!hasAttr(attrs, attrName)) { + private static ImmutableList<String> extractFilters(List<String> rawValues) { + if (rawValues.isEmpty()) { return ImmutableList.of(); } @@ -117,7 +118,6 @@ public class ResourceFilterFactory { * empty filter values result in all resources matching the empty filter, meaning that filtering * does nothing (even if non-empty filters were also provided). */ - List<String> rawValues = attrs.get(attrName, Type.STRING_LIST); // Use an ImmutableSet to remove duplicate values ImmutableSet.Builder<String> builder = ImmutableSet.builder(); @@ -139,30 +139,37 @@ public class ResourceFilterFactory { return ImmutableList.sortedCopyOf(builder.build()); } - static ResourceFilterFactory fromRuleContext(RuleContext ruleContext) throws RuleErrorException { + static ResourceFilterFactory fromRuleContextAndAttrs(RuleContext ruleContext) + throws RuleErrorException { Preconditions.checkNotNull(ruleContext); if (!ruleContext.isLegalFragment(AndroidConfiguration.class)) { return empty(); } - // aapt2 must have access to all of the resources in execution, so don't filter in analysis. - boolean filterInAnalysis = - AndroidAaptVersion.chooseTargetAaptVersion(ruleContext) != AndroidAaptVersion.AAPT2; - - return from(filterInAnalysis, ruleContext.attributes()); + return fromAttrs( + AndroidAaptVersion.chooseTargetAaptVersion(ruleContext), ruleContext.attributes()); } @VisibleForTesting - static ResourceFilterFactory from(boolean filterInAnalysis, AttributeMap attrs) { - if (!hasAttr(attrs, RESOURCE_CONFIGURATION_FILTERS_NAME) && !hasAttr(attrs, DENSITIES_NAME)) { + static ResourceFilterFactory fromAttrs(AndroidAaptVersion aaptVersion, AttributeMap attrs) { + return from( + aaptVersion, + rawFiltersFromAttrs(attrs, RESOURCE_CONFIGURATION_FILTERS_NAME), + rawFiltersFromAttrs(attrs, DENSITIES_NAME)); + } + + static ResourceFilterFactory from( + AndroidAaptVersion aaptVersion, List<String> configFilters, List<String> densities) { + if (configFilters.isEmpty() && densities.isEmpty()) { return empty(); } + // aapt2 must have access to all of the resources in execution, so don't filter in analysis. + boolean filterInAnalysis = aaptVersion != AndroidAaptVersion.AAPT2; + return new ResourceFilterFactory( - extractFilters(attrs, RESOURCE_CONFIGURATION_FILTERS_NAME), - extractFilters(attrs, DENSITIES_NAME), - filterInAnalysis); + extractFilters(configFilters), extractFilters(densities), filterInAnalysis); } private ImmutableList<FolderConfiguration> getConfigurationFilters( 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 70dfa8b3b8..4e6c395287 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 @@ -26,7 +26,6 @@ import com.google.devtools.build.lib.analysis.actions.CustomCommandLine.VectorAr import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.config.CompilationMode; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; -import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidAaptVersion; import com.google.devtools.build.lib.util.OS; import java.util.Collections; @@ -52,7 +51,7 @@ public class ResourceShrinkerActionBuilder { private ResourceFilterFactory resourceFilterFactory; /** @param ruleContext The RuleContext of the owning rule. */ - public ResourceShrinkerActionBuilder(RuleContext ruleContext) throws RuleErrorException { + public ResourceShrinkerActionBuilder(RuleContext ruleContext) { this.ruleContext = ruleContext; this.spawnActionBuilder = new SpawnAction.Builder(); this.sdk = AndroidSdkProvider.fromRuleContext(ruleContext); @@ -132,7 +131,7 @@ public class ResourceShrinkerActionBuilder { return this; } - public Artifact build() throws RuleErrorException { + public Artifact build() { ImmutableList.Builder<Artifact> inputs = ImmutableList.builder(); ImmutableList.Builder<Artifact> outputs = ImmutableList.builder(); diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java index 7296e50a01..398041d7a5 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java +++ b/src/main/java/com/google/devtools/build/lib/rules/java/ProguardHelper.java @@ -244,16 +244,41 @@ public abstract class ProguardHelper { */ public static ImmutableList<Artifact> collectTransitiveProguardSpecs( RuleContext ruleContext, Iterable<Artifact> specsToInclude) { + return collectTransitiveProguardSpecs( + ruleContext, + Iterables.concat( + specsToInclude, + ruleContext.getPrerequisiteArtifacts(":extra_proguard_specs", Mode.TARGET).list()), + ruleContext.attributes().has(PROGUARD_SPECS, BuildType.LABEL_LIST) + ? ruleContext.getPrerequisiteArtifacts(PROGUARD_SPECS, Mode.TARGET).list() + : ImmutableList.<Artifact>of(), + ruleContext.getPrerequisites("deps", Mode.TARGET, ProguardSpecProvider.class)); + } + + /** + * Retrieves the full set of proguard specs that should be applied to this binary, including the + * specs passed in, if Proguard should run on the given rule. + * + * <p>Unlike {@link #collectTransitiveProguardSpecs(RuleContext, Iterable)}, this method requires + * values to be passed in explicitly, and does not extract them from rule attributes. + * + * <p>If Proguard shouldn't be applied, or the legacy link mode is used and there are no + * proguard_specs on this rule, an empty list will be returned, regardless of any given specs or + * specs from dependencies. {@link + * com.google.devtools.build.lib.rules.android.AndroidBinary#createAndroidBinary} relies on that + * behavior. + */ + public static ImmutableList<Artifact> collectTransitiveProguardSpecs( + RuleContext ruleContext, + Iterable<Artifact> specsToInclude, + ImmutableList<Artifact> localProguardSpecs, + Iterable<ProguardSpecProvider> proguardDeps) { JavaOptimizationMode optMode = getJavaOptimizationMode(ruleContext); if (optMode == JavaOptimizationMode.NOOP) { return ImmutableList.of(); } - ImmutableList<Artifact> proguardSpecs = - ruleContext.attributes().has(PROGUARD_SPECS, BuildType.LABEL_LIST) - ? ruleContext.getPrerequisiteArtifacts(PROGUARD_SPECS, Mode.TARGET).list() - : ImmutableList.<Artifact>of(); - if (optMode == JavaOptimizationMode.LEGACY && proguardSpecs.isEmpty()) { + if (optMode == JavaOptimizationMode.LEGACY && localProguardSpecs.isEmpty()) { return ImmutableList.of(); } @@ -261,12 +286,9 @@ public abstract class ProguardHelper { // flags since those flags would override the desired optMode ImmutableSortedSet.Builder<Artifact> builder = ImmutableSortedSet.orderedBy(Artifact.EXEC_PATH_COMPARATOR) - .addAll(proguardSpecs) - .addAll(specsToInclude) - .addAll( - ruleContext.getPrerequisiteArtifacts(":extra_proguard_specs", Mode.TARGET).list()); - for (ProguardSpecProvider dep : - ruleContext.getPrerequisites("deps", Mode.TARGET, ProguardSpecProvider.class)) { + .addAll(localProguardSpecs) + .addAll(specsToInclude); + for (ProguardSpecProvider dep : proguardDeps) { builder.addAll(dep.getTransitiveProguardSpecs()); } |