// Copyright 2015 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 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.ImmutableMap; import com.google.common.collect.ImmutableSortedMap; import com.google.devtools.build.lib.actions.Artifact; 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.FileWriteAction; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.actions.SymlinkAction; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.packages.RuleErrorConsumer; import com.google.devtools.build.lib.rules.android.AndroidConfiguration.AndroidManifestMerger; import com.google.devtools.build.lib.syntax.Type; import java.util.Map; import java.util.Optional; import java.util.TreeMap; import javax.annotation.Nullable; /** Represents a AndroidManifest, that may have been merged from dependencies. */ public final class ApplicationManifest { static Artifact createSplitManifest( RuleContext ruleContext, Artifact manifest, String splitName, boolean hasCode) { // aapt insists that manifests be called AndroidManifest.xml, even though they have to be // explicitly designated as manifests on the command line Artifact result = AndroidBinary.getDxArtifact(ruleContext, "split_" + splitName + "/AndroidManifest.xml"); SpawnAction.Builder builder = new SpawnAction.Builder() .setExecutable( ruleContext.getExecutablePrerequisite("$build_split_manifest", Mode.HOST)) .setProgressMessage("Creating manifest for split %s", splitName) .setMnemonic("AndroidBuildSplitManifest") .addInput(manifest) .addOutput(result); CustomCommandLine.Builder commandLine = CustomCommandLine.builder() .addExecPath("--main_manifest", manifest) .addExecPath("--split_manifest", result) .add("--split", splitName); if (hasCode) { commandLine.add("--hascode"); } else { commandLine.add("--nohascode"); } String overridePackage = getManifestValues(ruleContext).get("applicationId"); if (overridePackage != null) { commandLine.add("--override_package", overridePackage); } builder.addCommandLine(commandLine.build()); ruleContext.registerAction(builder.build(ruleContext)); return result; } static Artifact addMobileInstallStubApplication(RuleContext ruleContext, Artifact manifest) throws InterruptedException { Artifact stubManifest = ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.MOBILE_INSTALL_STUB_APPLICATION_MANIFEST); Artifact stubData = ruleContext.getImplicitOutputArtifact( AndroidRuleClasses.MOBILE_INSTALL_STUB_APPLICATION_DATA); SpawnAction.Builder builder = new SpawnAction.Builder() .setExecutable(ruleContext.getExecutablePrerequisite("$stubify_manifest", Mode.HOST)) .setProgressMessage("Injecting mobile install stub application") .setMnemonic("InjectMobileInstallStubApplication") .addInput(manifest) .addOutput(stubManifest) .addOutput(stubData); CustomCommandLine.Builder commandLine = CustomCommandLine.builder() .add("--mode=mobile_install") .addExecPath("--input_manifest", manifest) .addExecPath("--output_manifest", stubManifest) .addExecPath("--output_datafile", stubData); String overridePackage = getManifestValues(ruleContext).get("applicationId"); if (overridePackage != null) { commandLine.add("--override_package", overridePackage); } builder.addCommandLine(commandLine.build()); ruleContext.registerAction(builder.build(ruleContext)); return stubManifest; } public static Artifact getManifestFromAttributes(RuleContext ruleContext) { return ruleContext.getPrerequisiteArtifact("manifest", Mode.TARGET); } static Artifact renameManifestIfNeeded(AndroidDataContext dataContext, Artifact manifest) throws InterruptedException { if (manifest.getFilename().equals("AndroidManifest.xml")) { return manifest; } else { /* * If the manifest file is not named AndroidManifest.xml, we create a symlink named * AndroidManifest.xml to it. aapt requires the manifest to be named as such. */ Artifact manifestSymlink = dataContext.createOutputArtifact(AndroidRuleClasses.ANDROID_SYMLINKED_MANIFEST); dataContext.registerAction( new SymlinkAction( dataContext.getActionConstructionContext().getActionOwner(), manifest, manifestSymlink, "Renaming Android manifest for " + dataContext.getLabel())); return manifestSymlink; } } /** * Creates an action to generate an empty manifest file with a specific package name. * * @return an artifact for the generated manifest */ public static Artifact generateManifest( ActionConstructionContext context, String manifestPackage) { Artifact generatedManifest = context.getUniqueDirectoryArtifact("_generated", "AndroidManifest.xml"); String contents = Joiner.on("\n") .join( "", "", " ", " ", ""); context.registerAction( FileWriteAction.create(context, generatedManifest, contents, /*makeExecutable=*/ false)); return generatedManifest; } /** Gets a map of manifest values from this rule's 'manifest_values' attribute */ public static ImmutableMap getManifestValues(RuleContext context) { Map manifestValues = new TreeMap<>(); 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.getExpander().expand("manifest_values", manifestValues.get(variable))); } return ImmutableMap.copyOf(manifestValues); } private ApplicationManifest() {} static Optional maybeMergeWith( AndroidDataContext dataContext, AndroidSemantics androidSemantics, Artifact primaryManifest, ResourceDependencies resourceDeps, Map manifestValues, boolean useLegacyMerging, String customPackage) { Map mergeeManifests = getMergeeManifests(resourceDeps.getResourceContainers()); if (useLegacyMerging) { return androidSemantics.maybeDoLegacyManifestMerging( mergeeManifests, dataContext, primaryManifest); } else { if (!mergeeManifests.isEmpty() || !manifestValues.isEmpty()) { Artifact outputManifest = dataContext.getUniqueDirectoryArtifact("_merged", "AndroidManifest.xml"); Artifact mergeLog = dataContext.getUniqueDirectoryArtifact("_merged", "manifest_merger_log.txt"); new ManifestMergerActionBuilder() .setManifest(primaryManifest) .setMergeeManifests(mergeeManifests) .setLibrary(false) .setManifestValues(manifestValues) .setCustomPackage(customPackage) .setManifestOutput(outputManifest) .setLogOut(mergeLog) .build(dataContext); return Optional.of(outputManifest); } } return Optional.empty(); } /** Checks if the legacy manifest merger should be used, based on a rule attribute */ public static boolean useLegacyMerging(RuleContext ruleContext) { return ruleContext.isLegalFragment(AndroidConfiguration.class) && ruleContext.getRule().isAttrDefined("manifest_merger", STRING) && useLegacyMerging( ruleContext, AndroidCommon.getAndroidConfig(ruleContext), ruleContext.attributes().get("manifest_merger", STRING)); } /** * Checks if the legacy manifest merger should be used, based on an optional string specifying the * merger to use. */ public static boolean useLegacyMerging( RuleErrorConsumer errorConsumer, AndroidConfiguration androidConfig, @Nullable String mergerString) { AndroidManifestMerger merger = AndroidManifestMerger.fromString(mergerString); if (merger == null) { merger = androidConfig.getManifestMerger(); } if (merger == AndroidManifestMerger.LEGACY) { errorConsumer.ruleWarning( "manifest_merger 'legacy' is deprecated. Please update to 'android'.\n" + "See https://developer.android.com/studio/build/manifest-merge.html for more " + "information about the manifest merger."); } return merger == AndroidManifestMerger.LEGACY; } private static Map getMergeeManifests( Iterable transitiveData) { ImmutableSortedMap.Builder builder = ImmutableSortedMap.orderedBy(Artifact.EXEC_PATH_COMPARATOR); for (ValidatedAndroidResources d : transitiveData) { if (d.isManifestExported()) { builder.put(d.getManifest(), d.getLabel()); } } return builder.build(); } static Optional maybeSetManifestPackage( AndroidDataContext dataContext, Artifact manifest, String customPackage) { if (isNullOrEmpty(customPackage)) { return Optional.empty(); } Artifact outputManifest = dataContext.getUniqueDirectoryArtifact("_renamed", "AndroidManifest.xml"); new ManifestMergerActionBuilder() .setManifest(manifest) .setLibrary(true) .setCustomPackage(customPackage) .setManifestOutput(outputManifest) .build(dataContext); return Optional.of(outputManifest); } }