Adds the Android manifest merger as an option for android_binary rules. The merger that is used (legacy or android) is controlled by the manifest_merger attribute on android_binary and the default is controlled by the --android_manifest_merger flag. RELNOTES: The Android manifest merger is now available as an option for android_binary rules. The merger will honor tools annotations in AndroidManifest.xml and will perform placeholder substitutions using the values specified in android_binary.manifest_values. The merger may be selected by setting the manifest_merger attribute on android_binary. -- MOS_MIGRATED_REVID=125603954
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 500c601..c9530a6 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
@@ -191,25 +191,6 @@ LocalResourceContainer.validateRuleContext(ruleContext); ApplicationManifest ruleManifest = androidSemantics.getManifestForRule(ruleContext); - String applicationId = null; - String versionCode = null; - String versionName = null; - Map<String, String> manifestValues = - ruleContext.attributes().get("manifest_values", Type.STRING_DICT); - if (manifestValues != null) { - if (manifestValues.containsKey("applicationId")) { - applicationId = manifestValues.get("applicationId"); - } - if (manifestValues.containsKey("versionCode")) { - versionCode = ruleContext.expandMakeVariables("manifest_values", - manifestValues.get("versionCode")); - } - if (manifestValues.containsKey("versionName")) { - versionName = ruleContext.expandMakeVariables("manifest_values", - manifestValues.get("versionName")); - } - } - applicationManifest = ruleManifest.mergeWith(ruleContext, resourceDeps); resourceApk = applicationManifest.packWithDataAndResources( @@ -223,9 +204,6 @@ ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), ruleContext.getTokenizedStringListAttr("densities"), - applicationId, - versionCode, - versionName, false, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, ""), createMainDexProguardSpec(ruleContext), @@ -247,9 +225,6 @@ ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), ruleContext.getTokenizedStringListAttr("densities"), - applicationId, - versionCode, - versionName, true, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental"), null, /* mainDexProguardCfg */ @@ -270,9 +245,6 @@ ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), ruleContext.getTokenizedStringListAttr("densities"), - applicationId, - versionCode, - versionName, true, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, "instant_run"), null, /* mainDexProguardCfg */ @@ -293,9 +265,6 @@ ruleContext.getTokenizedStringListAttr("nocompress_extensions"), ruleContext.attributes().get("crunch_png", Type.BOOLEAN), ruleContext.getTokenizedStringListAttr("densities"), - applicationId, - versionCode, - versionName, true, /* incremental */ ProguardHelper.getProguardConfigArtifact(ruleContext, "incremental_split"), null, /* mainDexProguardCfg */
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 a558b05..e6e116d 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
@@ -26,6 +26,7 @@ import com.google.devtools.build.lib.packages.Attribute.AllowedValueSet; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.packages.RuleClass.Builder.RuleClassType; +import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidManifestMerger; import com.google.devtools.build.lib.rules.android.AndroidRuleClasses.MultidexMode; /** @@ -35,15 +36,40 @@ @Override public RuleClass build(RuleClass.Builder builder, final RuleDefinitionEnvironment env) { return builder -// /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(manifest_values) --> -// A dictionary of values to be overridden in the manifest. Any instance of ${name} in the -// manifest will be replaced with the value corresponding to name in this dictionary. -// applicationId, versionCode, versionName, minSdkVersion, targetSdkVersion and -// maxSdkVersion will also override the corresponding attributes of the manifest and -// uses-sdk tags. packageName will be ignored and will be set from either applicationId if -// specified or the package in manifest. -// <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ - .add(attr("manifest_values", STRING_DICT).undocumented("not ready for production use")) + /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(manifest_merger) --> + Select the manifest merger to use for this rule.<br/> + Possible values: + <ul> + <li><code>manifest_merger = "legacy"</code>: Use the legacy manifest merger. Does not + allow features of the android merger like placeholder substitution and tools + attributes for defining merge behavior. Removes all + <code><uses-permission></code> and <code><uses-permission-sdk-23></code> + tags. Performs a tag-level merge.</li> + <li><code>manifest_merger = "android"</code>: Use the android manifest merger. Allows + features like placeholder substitution and tools attributes for defining merge + behavior. Follows the semantics from + <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/manifest-merger"> + the documentation</a> except it has been modified to also remove all + <code><uses-permission></code> and <code><uses-permission-sdk-23></code> + tags. Performs an attribute-level merge.</li> + <li><code>manifest_merger = "auto"</code>: Merger is controlled by the + <a href="../blaze-user-manual.html#flag--android_manifest_merger"> + --android_manifest_merger</a> flag.</li> + </ul> + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + .add(attr("manifest_merger", STRING) + .allowedValues(new AllowedValueSet(AndroidManifestMerger.getAttributeValues())) + .value(AndroidManifestMerger.getRuleAttributeDefault())) + /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(manifest_values) --> + A dictionary of values to be overridden in the manifest. Any instance of ${name} in the + manifest will be replaced with the value corresponding to name in this dictionary. + applicationId, versionCode, versionName, minSdkVersion, targetSdkVersion and + maxSdkVersion will also override the corresponding attributes of the manifest and + uses-sdk tags. packageName will be ignored and will be set from either applicationId if + specified or the package in manifest. When manifest_merger is set to legacy, only + applicationId, versionCode and versionName will have any effect. + <!-- #END_BLAZE_RULE.ATTRIBUTE --> */ + .add(attr("manifest_values", STRING_DICT)) /* <!-- #BLAZE_RULE(android_binary).ATTRIBUTE(nocompress_extensions) --> A list of file extension to leave uncompressed in apk. <!-- #END_BLAZE_RULE.ATTRIBUTE --> */
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java index cdc30bf..d8f7cbb 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidConfiguration.java
@@ -98,6 +98,16 @@ } /** + * Converter for {@link AndroidManifestMerger} + */ + public static final class AndroidManifestMergerConverter + extends EnumConverter<AndroidManifestMerger> { + public AndroidManifestMergerConverter() { + super(AndroidManifestMerger.class, "android manifest merger"); + } + } + + /** * Value used to avoid multiple configurations from conflicting. * * <p>This is set to {@code ANDROID} in Android configurations and to {@code MAIN} otherwise. This @@ -139,6 +149,30 @@ } } + /** Types of android manifest mergers. */ + public enum AndroidManifestMerger { + LEGACY, + ANDROID; + + public static List<String> getAttributeValues() { + return ImmutableList.of(LEGACY.name().toLowerCase(), ANDROID.name().toLowerCase(), + getRuleAttributeDefault()); + } + + public static String getRuleAttributeDefault() { + return "auto"; + } + + public static AndroidManifestMerger fromString(String value) { + for (AndroidManifestMerger merger : AndroidManifestMerger.values()) { + if (merger.name().equalsIgnoreCase(value)) { + return merger; + } + } + return null; + } + } + /** * Android configuration options. */ @@ -296,6 +330,14 @@ help = "Enables the use of an obfuscation map when generating the main dex jar file") public boolean useProguardPreviousObfuscationMap; + @Option(name = "android_manifest_merger", + defaultValue = "legacy", + category = "semantics", + converter = AndroidManifestMergerConverter.class, + help = "Selects the manifest merger to use for android_binary rules. Flag to help the" + + "transition to the Android manifest merger from the legacy merger.") + public AndroidManifestMerger manifestMerger; + @Override public void addAllLabels(Multimap<String, Label> labelMap) { if (androidCrosstoolTop != null) { @@ -366,6 +408,7 @@ private final boolean allowAndroidLibraryDepsWithoutSrcs; private final boolean useAndroidResourceShrinking; private final boolean useProguardPreviousObfuscationMap; + private final AndroidManifestMerger manifestMerger; AndroidConfiguration(Options options, Label androidSdk) { this.sdk = androidSdk; @@ -389,6 +432,7 @@ this.allowAndroidLibraryDepsWithoutSrcs = options.allowAndroidLibraryDepsWithoutSrcs; this.useAndroidResourceShrinking = options.useAndroidResourceShrinking; this.useProguardPreviousObfuscationMap = options.useProguardPreviousObfuscationMap; + this.manifestMerger = options.manifestMerger; } public String getCpu() { @@ -465,6 +509,10 @@ return useProguardPreviousObfuscationMap; } + public AndroidManifestMerger getManifestMerger() { + return manifestMerger; + } + @Override public void addGlobalMakeVariables(ImmutableMap.Builder<String, String> globalMakeEnvBuilder) { globalMakeEnvBuilder.put("ANDROID_CPU", cpu);
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 8630825..964db8a 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
@@ -25,7 +25,6 @@ import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.packages.BuildType; -import com.google.devtools.build.lib.packages.RuleClass.ConfiguredTargetFactory.RuleErrorException; import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory; import com.google.devtools.build.lib.rules.android.AndroidLibraryAarProvider.Aar; import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer; @@ -78,7 +77,8 @@ final ResourceApk resourceApk; if (definesLocalResources) { - ApplicationManifest applicationManifest = androidSemantics.getManifestForRule(ruleContext); + ApplicationManifest applicationManifest = androidSemantics.getManifestForRule(ruleContext) + .renamePackage(ruleContext, AndroidCommon.getJavaPackage(ruleContext)); resourceApk = applicationManifest.packWithDataAndResources( null, /* resourceApk -- not needed for library */ ruleContext, @@ -90,11 +90,8 @@ ImmutableList.<String>of(), /* uncompressedExtensions */ false, /* crunchPng */ ImmutableList.<String>of(), /* densities */ - null /* applicationId */, - null /* versionCode */, - null /* versionName */, - false, - null /* proguardCfgOut */, + false, /* incremental */ + null, /* proguardCfgOut */ null, /* mainDexProguardCfg */ ruleContext.getImplicitOutputArtifact(AndroidRuleClasses.ANDROID_PROCESSED_MANIFEST), null /* mergedResourcesOut */); @@ -145,7 +142,8 @@ } else { // there are no local resources and resources attribute was not specified either aar = null; - ApplicationManifest applicationManifest = ApplicationManifest.generatedManifest(ruleContext); + ApplicationManifest applicationManifest = ApplicationManifest.generatedManifest(ruleContext) + .renamePackage(ruleContext, AndroidCommon.getJavaPackage(ruleContext)); String javaPackage = AndroidCommon.getJavaPackage(ruleContext); @@ -200,7 +198,7 @@ .add(ProguardSpecProvider.class, new ProguardSpecProvider(transitiveProguardConfigs)) .addOutputGroup(OutputGroupProvider.HIDDEN_TOP_LEVEL, transitiveProguardConfigs) .add(AndroidLibraryAarProvider.class, new AndroidLibraryAarProvider( - aar, transitiveAars.build())) + aar, transitiveAars.build())) .build(); }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java index 306a922..0f2008b 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/AndroidRuleClasses.java
@@ -165,11 +165,10 @@ "//tools/android:incremental_stub_application"; public static final String DEFAULT_INCREMENTAL_SPLIT_STUB_APPLICATION = "//tools/android:incremental_split_stub_application"; - public static final String DEFAULT_RESOURCES_PROCESSOR = - "//tools/android:resources_processor"; - public static final String DEFAULT_RESOURCE_SHRINKER = - "//tools/android:resource_shrinker"; public static final String DEFAULT_AAR_GENERATOR = "//tools/android:aar_generator"; + public static final String DEFAULT_MANIFEST_MERGER = "//tools/android:manifest_merger"; + public static final String DEFAULT_RESOURCES_PROCESSOR = "//tools/android:resources_processor"; + public static final String DEFAULT_RESOURCE_SHRINKER = "//tools/android:resource_shrinker"; public static final String DEFAULT_SDK = "//tools/android:sdk"; /** @@ -398,12 +397,14 @@ @Override public RuleClass build(RuleClass.Builder builder, RuleDefinitionEnvironment env) { return builder + .add(attr("$android_aar_generator", LABEL).cfg(HOST).exec().value( + env.getToolsLabel(DEFAULT_AAR_GENERATOR))) + .add(attr("$android_manifest_merger", LABEL).cfg(HOST).exec().value( + env.getToolsLabel(DEFAULT_MANIFEST_MERGER))) .add(attr("$android_resources_processor", LABEL).cfg(HOST).exec().value( env.getToolsLabel(DEFAULT_RESOURCES_PROCESSOR))) .add(attr("$android_resource_shrinker", LABEL).cfg(HOST).exec().value( env.getToolsLabel(DEFAULT_RESOURCE_SHRINKER))) - .add(attr("$android_aar_generator", LABEL).cfg(HOST).exec().value( - env.getToolsLabel(DEFAULT_AAR_GENERATOR))) .build(); }
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 296f4af..8992776 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
@@ -13,8 +13,12 @@ // limitations under the License. package com.google.devtools.build.lib.rules.android; +import static com.google.common.base.Strings.isNullOrEmpty; +import static com.google.devtools.build.lib.syntax.Type.STRING; + import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; @@ -26,6 +30,7 @@ import com.google.devtools.build.lib.analysis.actions.FileWriteAction; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.config.CompilationMode; +import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidManifestMerger; import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceContainer; import com.google.devtools.build.lib.rules.android.AndroidResourcesProvider.ResourceType; import com.google.devtools.build.lib.syntax.Type; @@ -33,6 +38,7 @@ import java.util.List; import java.util.Map; +import java.util.TreeMap; import javax.annotation.Nullable; @@ -44,9 +50,8 @@ ruleContext.attributeError("manifest", "a resources or manifest attribute is mandatory."); return null; } - return new ApplicationManifest(Iterables.getOnlyElement( - resources.getDirectAndroidResources()) - .getManifest()); + return new ApplicationManifest( + ruleContext, Iterables.getOnlyElement(resources.getDirectAndroidResources()).getManifest()); } public ApplicationManifest createSplitManifest( @@ -67,7 +72,7 @@ .addArgument(splitName) .addArgument(hasCode ? "--hascode" : "--nohascode"); - String overridePackage = getOverridePackage(ruleContext); + String overridePackage = manifestValues.get("applicationId"); if (overridePackage != null) { builder .addArgument("--override_package") @@ -75,31 +80,7 @@ } ruleContext.registerAction(builder.build(ruleContext)); - return new ApplicationManifest(result); - } - - private String getOverridePackage(RuleContext ruleContext) { - // It seems that we sometimes rename the app for God-knows-what reason. If that is the case, - // pass this information to the stubifier script. - if (ruleContext.attributes().isAttributeValueExplicitlySpecified("manifest_values")) { - Map<String, String> manifestValues = - ruleContext.attributes().get("manifest_values", Type.STRING_DICT); - if (manifestValues.containsKey("applicationId")) { - return manifestValues.get("applicationId"); - } - } - if (ruleContext.attributes().isAttributeValueExplicitlySpecified("application_id")) { - return ruleContext.attributes().get("application_id", Type.STRING); - } - - AndroidResourcesProvider resourcesProvider = AndroidCommon.getAndroidResources(ruleContext); - if (resourcesProvider != null) { - ResourceContainer resourceContainer = Iterables.getOnlyElement( - resourcesProvider.getDirectAndroidResources()); - return resourceContainer.getRenameManifestPackage(); - } else { - return null; - } + return new ApplicationManifest(ruleContext, result); } public ApplicationManifest addMobileInstallStubApplication(RuleContext ruleContext) @@ -121,7 +102,7 @@ .addOutputArgument(ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.MOBILE_INSTALL_STUB_APPLICATION_DATA)); - String overridePackage = getOverridePackage(ruleContext); + String overridePackage = manifestValues.get("applicationId"); if (overridePackage != null) { builder.addArgument("--override_package"); builder.addArgument(overridePackage); @@ -129,7 +110,7 @@ ruleContext.registerAction(builder.build(ruleContext)); - return new ApplicationManifest(stubManifest); + return new ApplicationManifest(ruleContext, stubManifest); } public ApplicationManifest addInstantRunStubApplication(RuleContext ruleContext) @@ -150,15 +131,17 @@ ruleContext.registerAction(builder.build(ruleContext)); - return new ApplicationManifest(stubManifest); - } - - public static ApplicationManifest fromRule(RuleContext ruleContext) { - return new ApplicationManifest(ruleContext.getPrerequisiteArtifact("manifest", Mode.TARGET)); + return new ApplicationManifest(ruleContext, stubManifest); } - public static ApplicationManifest fromExplicitManifest(Artifact manifest) { - return new ApplicationManifest(manifest); + public static ApplicationManifest fromRule(RuleContext ruleContext) { + return new ApplicationManifest( + ruleContext, ruleContext.getPrerequisiteArtifact("manifest", Mode.TARGET)); + } + + public static ApplicationManifest fromExplicitManifest( + RuleContext ruleContext, Artifact manifest) { + return new ApplicationManifest(ruleContext, manifest); } /** @@ -184,26 +167,78 @@ "</manifest>"); ruleContext.getAnalysisEnvironment().registerAction(new FileWriteAction( ruleContext.getActionOwner(), generatedManifest, contents, false /* makeExecutable */)); - return new ApplicationManifest(generatedManifest); + return new ApplicationManifest(ruleContext, generatedManifest); + } + + private static ImmutableMap<String, String> getManifestValues(RuleContext context) { + Map<String, String> manifestValues = new TreeMap<>(); + // applicationId is set from manifest_values or android_resources.rename_manifest_package + // with descending priority. + AndroidResourcesProvider resourcesProvider = AndroidCommon.getAndroidResources(context); + if (resourcesProvider != null) { + ResourceContainer resourceContainer = Iterables.getOnlyElement( + resourcesProvider.getDirectAndroidResources()); + if (resourceContainer.getRenameManifestPackage() != null) { + manifestValues.put("applicationId", resourceContainer.getRenameManifestPackage()); + } + } + if (context.attributes().isAttributeValueExplicitlySpecified("manifest_values")) { + manifestValues.putAll(context.attributes().get("manifest_values", Type.STRING_DICT)); + } + + for (String variable : manifestValues.keySet()) { + manifestValues.put( + variable, context.expandMakeVariables("manifest_values", manifestValues.get(variable))); + } + return ImmutableMap.copyOf(manifestValues); } private final Artifact manifest; + private final ImmutableMap<String, String> manifestValues; - private ApplicationManifest(Artifact manifest) { + private ApplicationManifest(RuleContext ruleContext, Artifact manifest) { this.manifest = manifest; + this.manifestValues = getManifestValues(ruleContext); } - public ApplicationManifest mergeWith(RuleContext ruleContext, - ResourceDependencies resourceDeps) { + public ApplicationManifest mergeWith(RuleContext ruleContext, ResourceDependencies resourceDeps) { Iterable<Artifact> mergeeManifests = getMergeeManifests(resourceDeps.getResources()); - if (!Iterables.isEmpty(mergeeManifests)) { - Iterable<Artifact> exportedManifests = mergeeManifests; - Artifact outputManifest = ruleContext.getUniqueDirectoryArtifact( - ruleContext.getRule().getName() + "_merged", "AndroidManifest.xml", - ruleContext.getBinOrGenfilesDirectory()); - AndroidManifestMergeHelper.createMergeManifestAction(ruleContext, getManifest(), - exportedManifests, ImmutableList.of("all"), outputManifest); - return new ApplicationManifest(outputManifest); + + boolean legacy = true; + if (ruleContext.isLegalFragment(AndroidConfiguration.class) + && ruleContext.getRule().isAttrDefined("manifest_merger", STRING)) { + AndroidManifestMerger merger = AndroidManifestMerger.fromString( + ruleContext.attributes().get("manifest_merger", STRING)); + if (merger == null) { + merger = ruleContext.getFragment(AndroidConfiguration.class).getManifestMerger(); + } + legacy = merger == AndroidManifestMerger.LEGACY; + } + + if (legacy) { + if (!Iterables.isEmpty(mergeeManifests)) { + Artifact outputManifest = ruleContext.getUniqueDirectoryArtifact( + ruleContext.getRule().getName() + "_merged", "AndroidManifest.xml", + ruleContext.getBinOrGenfilesDirectory()); + AndroidManifestMergeHelper.createMergeManifestAction(ruleContext, getManifest(), + mergeeManifests, ImmutableList.of("all"), outputManifest); + return new ApplicationManifest(ruleContext, outputManifest); + } + } else { + if (!Iterables.isEmpty(mergeeManifests) || !manifestValues.isEmpty()) { + Artifact outputManifest = ruleContext.getUniqueDirectoryArtifact( + ruleContext.getRule().getName() + "_merged", "AndroidManifest.xml", + ruleContext.getBinOrGenfilesDirectory()); + new ManifestMergerActionBuilder(ruleContext) + .setManifest(getManifest()) + .setMergeeManifests(mergeeManifests) + .setLibrary(false) + .setManifestValues(manifestValues) + .setCustomPackage(AndroidCommon.getJavaPackage(ruleContext)) + .setManifestOutput(outputManifest) + .build(ruleContext); + return new ApplicationManifest(ruleContext, outputManifest); + } } return this; } @@ -220,6 +255,22 @@ return builder.build(); } + public ApplicationManifest renamePackage(RuleContext ruleContext, String customPackage) { + if (isNullOrEmpty(customPackage)) { + return this; + } + Artifact outputManifest = ruleContext.getUniqueDirectoryArtifact( + ruleContext.getRule().getName() + "_renamed", "AndroidManifest.xml", + ruleContext.getBinOrGenfilesDirectory()); + new ManifestMergerActionBuilder(ruleContext) + .setManifest(getManifest()) + .setLibrary(true) + .setCustomPackage(customPackage) + .setManifestOutput(outputManifest) + .build(ruleContext); + return new ApplicationManifest(ruleContext, outputManifest); + } + /** Packages up the manifest with assets from the rule and dependent resources. * @throws InterruptedException */ public ResourceApk packWithAssets( @@ -244,14 +295,11 @@ false, /* isLibrary */ resourceDeps, rTxt, - null, /* symbolsTxt */ + null, /* Artifact symbolsTxt */ ImmutableList.<String>of(), /* configurationFilters */ ImmutableList.<String>of(), /* uncompressedExtensions */ true, /* crunchPng */ ImmutableList.<String>of(), /* densities */ - null, /* String applicationId */ - null, /* String versionCode */ - null, /* String versionName */ incremental, data, proguardCfg, @@ -272,9 +320,6 @@ List<String> uncompressedExtensions, boolean crunchPng, List<String> densities, - String applicationId, - String versionCode, - String versionName, boolean incremental, Artifact proguardCfg, @Nullable Artifact mainDexProguardCfg, @@ -307,9 +352,6 @@ uncompressedExtensions, crunchPng, densities, - applicationId, - versionCode, - versionName, incremental, data, proguardCfg, @@ -329,9 +371,6 @@ List<String> uncompressedExtensions, boolean crunchPng, List<String> densities, - String applicationId, - String versionCode, - String versionName, boolean incremental, LocalResourceContainer data, Artifact proguardCfg, @@ -370,9 +409,9 @@ .setDensities(densities) .setProguardOut(proguardCfg) .setMainDexProguardOut(mainDexProguardCfg) - .setApplicationId(applicationId) - .setVersionCode(versionCode) - .setVersionName(versionName); + .setApplicationId(manifestValues.get("applicationId")) + .setVersionCode(manifestValues.get("versionCode")) + .setVersionName(manifestValues.get("versionName")); if (!incremental) { builder
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java b/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java index acd5cf1..971ccf7 100644 --- a/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java +++ b/src/main/java/com/google/devtools/build/lib/rules/android/LocalResourceContainer.java
@@ -45,10 +45,7 @@ "assets", "assets_dir", "inline_constants", - "exports_manifest", - "application_id", - "version_name", - "version_code" + "exports_manifest" }; /**
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/ManifestMergerActionBuilder.java b/src/main/java/com/google/devtools/build/lib/rules/android/ManifestMergerActionBuilder.java new file mode 100644 index 0000000..2117440 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/rules/android/ManifestMergerActionBuilder.java
@@ -0,0 +1,138 @@ +// Copyright 2016 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.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.actions.Artifact; +import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode; +import com.google.devtools.build.lib.analysis.RuleContext; +import com.google.devtools.build.lib.analysis.actions.ActionConstructionContext; +import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; +import com.google.devtools.build.lib.analysis.actions.SpawnAction; +import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * Builder for creating manifest merger actions. + */ +public class ManifestMergerActionBuilder { + private final RuleContext ruleContext; + private final SpawnAction.Builder spawnActionBuilder; + + private Artifact manifest; + private List<Artifact> mergeeManifests; + private boolean isLibrary; + private Map<String, String> manifestValues; + private String customPackage; + private Artifact manifestOutput; + + public ManifestMergerActionBuilder(RuleContext ruleContext) { + this.ruleContext = ruleContext; + this.spawnActionBuilder = new SpawnAction.Builder(); + } + + public ManifestMergerActionBuilder setManifest(Artifact manifest) { + this.manifest = manifest; + return this; + } + + public ManifestMergerActionBuilder setMergeeManifests(Iterable<Artifact> mergeeManifests) { + this.mergeeManifests = ImmutableList.copyOf(mergeeManifests); + return this; + } + + public ManifestMergerActionBuilder setLibrary(boolean isLibrary) { + this.isLibrary = isLibrary; + return this; + } + + public ManifestMergerActionBuilder setManifestValues(Map<String, String> manifestValues) { + this.manifestValues = manifestValues; + return this; + } + + public ManifestMergerActionBuilder setCustomPackage(String customPackage) { + this.customPackage = customPackage; + return this; + } + + public ManifestMergerActionBuilder setManifestOutput(Artifact manifestOutput) { + this.manifestOutput = manifestOutput; + return this; + } + + public void build(ActionConstructionContext context) { + NestedSetBuilder<Artifact> inputs = NestedSetBuilder.naiveLinkOrder(); + ImmutableList.Builder<Artifact> outputs = ImmutableList.builder(); + CustomCommandLine.Builder builder = new CustomCommandLine.Builder(); + + inputs.addAll(ruleContext.getExecutablePrerequisite("$android_manifest_merger", Mode.HOST) + .getRunfilesSupport() + .getRunfilesArtifactsWithoutMiddlemen()); + + builder.addExecPath("--manifest", manifest); + inputs.add(manifest); + + if (mergeeManifests != null && !mergeeManifests.isEmpty()) { + builder.addJoinExecPaths("--mergeeManifests", ":", mergeeManifests); + inputs.addAll(mergeeManifests); + } + + if (isLibrary) { + builder.add("--mergeType").add("LIBRARY"); + } + + if (manifestValues != null && !manifestValues.isEmpty()) { + builder.add("--manifestValues").add(mapToDictionaryString(manifestValues)); + } + + if (customPackage != null && !customPackage.isEmpty()) { + builder.add("--customPackage").add(customPackage); + } + + builder.addExecPath("--manifestOutput", manifestOutput); + outputs.add(manifestOutput); + + ruleContext.registerAction( + this.spawnActionBuilder + .addTransitiveInputs(inputs.build()) + .addOutputs(outputs.build()) + .setCommandLine(builder.build()) + .setExecutable( + ruleContext.getExecutablePrerequisite("$android_manifest_merger", Mode.HOST)) + .setProgressMessage("Merging manifest") + .setMnemonic("ManifestMerger") + .build(context)); + } + + private <K, V> String mapToDictionaryString(Map<K, V> map) { + StringBuilder sb = new StringBuilder(); + Iterator<Entry<K, V>> iter = map.entrySet().iterator(); + while (iter.hasNext()) { + Entry<K, V> entry = iter.next(); + sb.append(entry.getKey().toString().replace(":", "\\:").replace(",", "\\,")); + sb.append(':'); + sb.append(entry.getValue().toString().replace(":", "\\:").replace(",", "\\,")); + if (iter.hasNext()) { + sb.append(','); + } + } + return sb.toString(); + } +} +
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java index 2ac21a7..6606fc2 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
@@ -161,6 +161,7 @@ .add("sh_binary(name = 'aar_generator', srcs = ['empty.sh'])") .add("sh_binary(name = 'dexbuilder', srcs = ['empty.sh'])") .add("sh_binary(name = 'dexmerger', srcs = ['empty.sh'])") + .add("sh_binary(name = 'manifest_merger', srcs = ['empty.sh'])") .add("sh_binary(name = 'resources_processor', srcs = ['empty.sh'])") .add("sh_binary(name = 'resource_shrinker', srcs = ['empty.sh'])") .add("android_library(name = 'incremental_stub_application')")
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java index 69f58d6..9bbef3d 100644 --- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessingAction.java
@@ -13,6 +13,8 @@ // limitations under the License. package com.google.devtools.build.android; +import static java.nio.charset.StandardCharsets.UTF_8; + import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -30,6 +32,7 @@ import com.google.devtools.common.options.TriState; import com.android.builder.core.VariantConfiguration; +import com.android.builder.core.VariantConfiguration.Type; import com.android.ide.common.internal.AaptCruncher; import com.android.ide.common.internal.CommandLineRunner; import com.android.ide.common.internal.LoggedErrorException; @@ -40,6 +43,7 @@ import java.io.IOException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; +import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.concurrent.TimeUnit; @@ -226,6 +230,7 @@ final Path filteredResources = tmp.resolve("resources-filtered"); final Path densityManifest = tmp.resolve("manifest-filtered/AndroidManifest.xml"); final Path processedManifest = tmp.resolve("manifest-processed/AndroidManifest.xml"); + final Path dummyManifest = tmp.resolve("manifest-aapt-dummy/AndroidManifest.xml"); Path generatedSources = null; if (options.srcJarOutput != null || options.rOutput != null @@ -266,7 +271,7 @@ LOGGER.fine(String.format("Density filtering finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); - final MergedAndroidData processedManifestData = resourceProcessor.processManifest( + MergedAndroidData processedData = resourceProcessor.processManifest( options.packageType, options.packageForR, options.applicationId, @@ -275,6 +280,24 @@ filteredData, processedManifest); + // Write manifestOutput now before the dummy manifest is created. + if (options.manifestOutput != null) { + resourceProcessor.copyManifestToOutput(processedData, options.manifestOutput); + } + + if (options.packageType == Type.LIBRARY) { + Files.createDirectories(dummyManifest.getParent()); + Files.write(dummyManifest, String.format( + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"" + + " package=\"%s\">" + + "</manifest>", options.packageForR).getBytes(UTF_8)); + processedData = new MergedAndroidData( + processedData.getResourceDir(), + processedData.getAssetDir(), + dummyManifest); + } + resourceProcessor.processResources( aaptConfigOptions.aapt, aaptConfigOptions.androidJar, @@ -285,20 +308,17 @@ new FlagAaptOptions(aaptConfigOptions), aaptConfigOptions.resourceConfigs, aaptConfigOptions.splits, - processedManifestData, + processedData, data, generatedSources, options.packagePath, options.proguardOutput, options.mainDexProguardOutput, options.resourcesOutput != null - ? processedManifestData.getResourceDir().resolve("values").resolve("public.xml") + ? processedData.getResourceDir().resolve("values").resolve("public.xml") : null); LOGGER.fine(String.format("aapt finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS))); - if (options.manifestOutput != null) { - resourceProcessor.copyManifestToOutput(processedManifestData, options.manifestOutput); - } if (options.srcJarOutput != null) { resourceProcessor.createSrcJar(generatedSources, options.srcJarOutput, VariantConfiguration.Type.LIBRARY == options.packageType); @@ -312,8 +332,8 @@ VariantConfiguration.Type.LIBRARY == options.packageType); } if (options.resourcesOutput != null) { - resourceProcessor.createResourcesZip(processedManifestData.getResourceDir(), - processedManifestData.getAssetDir(), options.resourcesOutput); + resourceProcessor.createResourcesZip(processedData.getResourceDir(), + processedData.getAssetDir(), options.resourcesOutput); } LOGGER.fine(String.format("Packaging finished at %sms", timer.elapsed(TimeUnit.MILLISECONDS)));
diff --git a/src/tools/android/java/com/google/devtools/build/android/BUILD.tools b/src/tools/android/java/com/google/devtools/build/android/BUILD.tools index 1d5da32..d844ff9 100644 --- a/src/tools/android/java/com/google/devtools/build/android/BUILD.tools +++ b/src/tools/android/java/com/google/devtools/build/android/BUILD.tools
@@ -6,6 +6,14 @@ ) java_binary( + name = "AarGeneratorAction", + main_class = "com.google.devtools.build.android.AarGeneratorAction", + runtime_deps = [ + ":classes", + ], +) + +java_binary( name = "AndroidResourceCompilationAction", main_class = "com.google.devtools.build.android.AndroidResourceCompilationAction", runtime_deps = [ @@ -22,16 +30,16 @@ ) java_binary( - name = "ResourceShrinkerAction", - main_class = "com.google.devtools.build.android.ResourceShrinkerAction", + name = "ManifestMergerAction", + main_class = "com.google.devtools.build.android.ManifestMergerAction", runtime_deps = [ ":classes", ], ) java_binary( - name = "AarGeneratorAction", - main_class = "com.google.devtools.build.android.AarGeneratorAction", + name = "ResourceShrinkerAction", + main_class = "com.google.devtools.build.android.ResourceShrinkerAction", runtime_deps = [ ":classes", ],
diff --git a/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java b/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java index 4a8f0a5..ce1884d 100644 --- a/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java +++ b/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction.java
@@ -15,6 +15,7 @@ import static java.util.logging.Level.SEVERE; +import com.google.common.collect.ImmutableList; import com.google.devtools.build.android.Converters.ExistingPathConverter; import com.google.devtools.build.android.Converters.ExistingPathListConverter; import com.google.devtools.build.android.Converters.MergeTypeConverter; @@ -27,6 +28,11 @@ import com.android.manifmerger.ManifestMerger2.MergeType; import com.android.utils.StdLogger; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -36,6 +42,16 @@ import java.util.Map; import java.util.logging.Logger; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + /** * An action to perform manifest merging using the Gradle manifest merger. * @@ -102,11 +118,34 @@ public Path manifestOutput; } + private static final String[] PERMISSION_TAGS = + new String[] {"uses-permission", "uses-permission-sdk-23"}; private static final StdLogger stdLogger = new StdLogger(StdLogger.Level.WARNING); private static final Logger logger = Logger.getLogger(ManifestMergerAction.class.getName()); private static Options options; + private static Path removePermissions(Path manifest, Path outputDir) + throws IOException, ParserConfigurationException, TransformerConfigurationException, + TransformerException, TransformerFactoryConfigurationError, SAXException { + DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = docBuilder.parse(manifest.toFile()); + for (String tag : PERMISSION_TAGS) { + NodeList permissions = doc.getElementsByTagName(tag); + if (permissions != null) { + for (int i = permissions.getLength() - 1; i >= 0; i--) { + Node permission = permissions.item(i); + permission.getParentNode().removeChild(permission); + } + } + } + Path output = outputDir.resolve(manifest.getFileName()); + TransformerFactory.newInstance().newTransformer().transform( + new DOMSource(doc), + new StreamResult(output.toFile())); + return output; + } + public static void main(String[] args) throws Exception { OptionsParser optionsParser = OptionsParser.newOptionsParser(Options.class); optionsParser.parseAndExitUponError(args); @@ -117,10 +156,18 @@ try { Path mergedManifest; if (options.mergeType == MergeType.APPLICATION) { + // Remove uses-permission tags from mergees before the merge. + Path tmp = Files.createTempDirectory("manifest_merge_tmp"); + tmp.toFile().deleteOnExit(); + ImmutableList.Builder<Path> mergeeManifests = ImmutableList.builder(); + for (Path mergeeManifest : options.mergeeManifests) { + mergeeManifests.add(removePermissions(mergeeManifest, tmp)); + } + // Ignore custom package at the binary level. mergedManifest = resourceProcessor.mergeManifest( options.manifest, - options.mergeeManifests, + mergeeManifests.build(), options.mergeType, options.manifestValues, options.manifestOutput);
diff --git a/tools/android/BUILD b/tools/android/BUILD index b6e7cb4..1056ebc 100644 --- a/tools/android/BUILD +++ b/tools/android/BUILD
@@ -22,6 +22,12 @@ ) sh_binary( + name = "manifest_merger", + srcs = ["manifest_merger.sh"], + data = ["//src/tools/android/java/com/google/devtools/build/android:ManifestMergerAction"], +) + +sh_binary( name = "resources_processor", srcs = ["resources_processor.sh"], data = ["//src/tools/android/java/com/google/devtools/build/android:AndroidResourceProcessingAction"],
diff --git a/tools/android/manifest_merger.sh b/tools/android/manifest_merger.sh new file mode 100755 index 0000000..755bf4c --- /dev/null +++ b/tools/android/manifest_merger.sh
@@ -0,0 +1,16 @@ +#!/bin/bash +# Copyright 2016 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. + +exec "${TEST_SRCDIR-$0.runfiles}/bazel_tools/src/tools/android/java/com/google/devtools/build/android/ManifestMergerAction" "$@"