blob: 6838316ba9bd7a4550a36d1fcd2aa2373b613f87 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.build.gradle.tasks;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.ALL;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactScope.MODULE;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.FEATURE_APPLICATION_ID_DECLARATION;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.MANIFEST;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.METADATA_APP_ID_DECLARATION;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ArtifactType.METADATA_FEATURE_MANIFEST;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.COMPILE_CLASSPATH;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.METADATA_VALUES;
import static com.android.build.gradle.internal.publishing.AndroidArtifacts.ConsumedConfigType.RUNTIME_CLASSPATH;
import static com.android.build.gradle.options.BooleanOption.BUILD_ONLY_TARGET_ABI;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.gradle.internal.core.GradleVariantConfiguration;
import com.android.build.gradle.internal.dependency.ArtifactCollectionWithExtraArtifact.ExtraComponentIdentifier;
import com.android.build.gradle.internal.dsl.CoreBuildType;
import com.android.build.gradle.internal.dsl.CoreProductFlavor;
import com.android.build.gradle.internal.scope.BuildOutput;
import com.android.build.gradle.internal.scope.BuildOutputs;
import com.android.build.gradle.internal.scope.GlobalScope;
import com.android.build.gradle.internal.scope.OutputScope;
import com.android.build.gradle.internal.scope.TaskConfigAction;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.internal.tasks.ApplicationId;
import com.android.build.gradle.internal.tasks.TaskInputHelper;
import com.android.build.gradle.internal.variant.BaseVariantData;
import com.android.build.gradle.internal.variant.FeatureVariantData;
import com.android.build.gradle.internal.variant.TaskContainer;
import com.android.build.gradle.options.ProjectOptions;
import com.android.build.gradle.options.StringOption;
import com.android.builder.core.AndroidBuilder;
import com.android.builder.core.VariantConfiguration;
import com.android.builder.model.ApiVersion;
import com.android.ide.common.build.ApkData;
import com.android.manifmerger.ManifestMerger2;
import com.android.manifmerger.ManifestMerger2.Invoker.Feature;
import com.android.manifmerger.ManifestProvider;
import com.android.manifmerger.MergingReport;
import com.android.manifmerger.XmlDocument;
import com.android.utils.FileUtils;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.artifacts.ArtifactCollection;
import org.gradle.api.artifacts.component.ComponentIdentifier;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.gradle.api.artifacts.component.ProjectComponentIdentifier;
import org.gradle.api.artifacts.result.ResolvedArtifactResult;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.CacheableTask;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.Internal;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.PathSensitive;
import org.gradle.api.tasks.PathSensitivity;
import org.gradle.internal.component.local.model.OpaqueComponentArtifactIdentifier;
/** A task that processes the manifest */
@CacheableTask
public class MergeManifests extends ManifestProcessorTask {
private Supplier<String> minSdkVersion;
private Supplier<String> targetSdkVersion;
private Supplier<Integer> maxSdkVersion;
private VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor>
variantConfiguration;
private ArtifactCollection manifests;
private ArtifactCollection featureManifests;
private FileCollection microApkManifest;
private FileCollection compatibleScreensManifest;
private FileCollection packageManifest;
private List<Feature> optionalFeatures;
private OutputScope outputScope;
private Set<String> supportedAbis;
private String buildTargetAbi;
private String buildTargetDensity;
private String featureName;
@Override
protected void doFullTaskAction() throws IOException {
// read the output of the compatible screen manifest.
Collection<BuildOutput> compatibleScreenManifests =
BuildOutputs.load(
VariantScope.TaskOutputType.COMPATIBLE_SCREEN_MANIFEST,
compatibleScreensManifest);
String packageOverride;
if (packageManifest != null && !packageManifest.isEmpty()) {
packageOverride =
ApplicationId.load(packageManifest.getSingleFile()).getApplicationId();
} else {
packageOverride = getPackageOverride();
}
@Nullable BuildOutput compatibleScreenManifestForSplit;
List<ApkData> splitsToGenerate =
ProcessAndroidResources.getApksToGenerate(
outputScope, supportedAbis, buildTargetAbi, buildTargetDensity);
// FIX ME : multi threading.
for (ApkData apkData : splitsToGenerate) {
compatibleScreenManifestForSplit =
OutputScope.getOutput(
compatibleScreenManifests,
VariantScope.TaskOutputType.COMPATIBLE_SCREEN_MANIFEST,
apkData);
File manifestOutputFile =
FileUtils.join(
getManifestOutputDirectory(),
apkData.getDirName(),
SdkConstants.ANDROID_MANIFEST_XML);
File instantRunManifestOutputFile =
FileUtils.join(
getInstantRunManifestOutputDirectory(),
apkData.getDirName(),
SdkConstants.ANDROID_MANIFEST_XML);
MergingReport mergingReport =
getBuilder()
.mergeManifestsForApplication(
getMainManifest(),
getManifestOverlays(),
computeFullProviderList(compatibleScreenManifestForSplit),
getFeatureName(),
packageOverride,
apkData.getVersionCode(),
apkData.getVersionName(),
getMinSdkVersion(),
getTargetSdkVersion(),
getMaxSdkVersion(),
manifestOutputFile.getAbsolutePath(),
// no aapt friendly merged manifest file necessary for applications.
null /* aaptFriendlyManifestOutputFile */,
instantRunManifestOutputFile.getAbsolutePath(),
ManifestMerger2.MergeType.APPLICATION,
variantConfiguration.getManifestPlaceholders(),
getOptionalFeatures(),
getReportFile());
XmlDocument mergedXmlDocument =
mergingReport.getMergedXmlDocument(MergingReport.MergedManifestKind.MERGED);
ImmutableMap<String, String> properties =
mergedXmlDocument != null
? ImmutableMap.of(
"packageId",
mergedXmlDocument.getPackageName(),
"split",
mergedXmlDocument.getSplitName(),
SdkConstants.ATTR_MIN_SDK_VERSION,
mergedXmlDocument.getMinSdkVersion())
: ImmutableMap.of();
outputScope.addOutputForSplit(
VariantScope.TaskOutputType.MERGED_MANIFESTS,
apkData,
manifestOutputFile,
properties);
outputScope.addOutputForSplit(
VariantScope.TaskOutputType.INSTANT_RUN_MERGED_MANIFESTS,
apkData,
instantRunManifestOutputFile,
properties);
}
outputScope.save(
ImmutableList.of(VariantScope.TaskOutputType.MERGED_MANIFESTS),
getManifestOutputDirectory());
outputScope.save(
ImmutableList.of(VariantScope.TaskOutputType.INSTANT_RUN_MERGED_MANIFESTS),
getInstantRunManifestOutputDirectory());
}
@Nullable
@Override
@Internal
public File getAaptFriendlyManifestOutputFile() {
return null;
}
@Optional
@InputFile
@PathSensitive(PathSensitivity.RELATIVE)
public File getMainManifest() {
return variantConfiguration.getMainManifest();
}
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
public List<File> getManifestOverlays() {
return variantConfiguration.getManifestOverlays();
}
@Input
@Optional
public String getPackageOverride() {
return variantConfiguration.getIdOverride();
}
@Input
public List<Integer> getVersionCodes() {
return outputScope
.getApkDatas()
.stream()
.map(ApkData::getVersionCode)
.collect(Collectors.toList());
}
@Input
@Optional
public List<String> getVersionNames() {
return outputScope
.getApkDatas()
.stream()
.map(ApkData::getVersionName)
.collect(Collectors.toList());
}
/**
* Returns a serialized version of our map of key value pairs for placeholder substitution.
*
* This serialized form is only used by gradle to compare past and present tasks to determine
* whether a task need to be re-run or not.
*/
@Input
@Optional
public String getManifestPlaceholders() {
return serializeMap(variantConfiguration.getManifestPlaceholders());
}
/**
* Compute the final list of providers based on the manifest file collection and the other
* providers.
*
* @return the list of providers.
*/
private List<ManifestProvider> computeFullProviderList(
@Nullable BuildOutput compatibleScreenManifestForSplit) {
final Set<ResolvedArtifactResult> artifacts = manifests.getArtifacts();
List<ManifestProvider> providers = Lists.newArrayListWithCapacity(artifacts.size() + 2);
for (ResolvedArtifactResult artifact : artifacts) {
providers.add(new ConfigAction.ManifestProviderImpl(
artifact.getFile(),
getArtifactName(artifact)));
}
if (microApkManifest != null) {
// this is now always present if embedding is enabled, but it doesn't mean
// anything got embedded so the file may not run (the file path exists and is
// returned by the FC but the file doesn't exist.
File microManifest = microApkManifest.getSingleFile();
if (microManifest.isFile()) {
providers.add(new ConfigAction.ManifestProviderImpl(
microManifest,
"Wear App sub-manifest"));
}
}
if (compatibleScreenManifestForSplit != null) {
providers.add(
new ConfigAction.ManifestProviderImpl(
compatibleScreenManifestForSplit.getOutputFile(),
"Compatible-Screens sub-manifest"));
}
if (featureManifests != null) {
final Set<ResolvedArtifactResult> featureArtifacts = featureManifests.getArtifacts();
for (ResolvedArtifactResult artifact : featureArtifacts) {
File directory = artifact.getFile();
Collection<BuildOutput> splitOutputs =
BuildOutputs.load(VariantScope.TaskOutputType.MERGED_MANIFESTS, directory);
if (splitOutputs.isEmpty()) {
throw new GradleException("Could not load manifest from " + directory);
}
providers.add(
new ConfigAction.ManifestProviderImpl(
splitOutputs.iterator().next().getOutputFile(),
getArtifactName(artifact)));
}
}
return providers;
}
// TODO put somewhere else?
@NonNull
@Internal
public static String getArtifactName(@NonNull ResolvedArtifactResult artifact) {
ComponentIdentifier id = artifact.getId().getComponentIdentifier();
if (id instanceof ProjectComponentIdentifier) {
return ((ProjectComponentIdentifier) id).getProjectPath();
} else if (id instanceof ModuleComponentIdentifier) {
ModuleComponentIdentifier mID = (ModuleComponentIdentifier) id;
return mID.getGroup() + ":" + mID.getModule() + ":" + mID.getVersion();
} else if (id instanceof OpaqueComponentArtifactIdentifier) {
// this is the case for local jars.
// FIXME: use a non internal class.
return id.getDisplayName();
} else if (id instanceof ExtraComponentIdentifier) {
return id.getDisplayName();
} else {
throw new RuntimeException("Unsupported type of ComponentIdentifier");
}
}
@Input
@Optional
public String getMinSdkVersion() {
return minSdkVersion.get();
}
@Input
@Optional
public String getTargetSdkVersion() {
return targetSdkVersion.get();
}
@Input
@Optional
public Integer getMaxSdkVersion() {
return maxSdkVersion.get();
}
/** Not an input, see {@link #getOptionalFeaturesString()}. */
@Internal
public List<Feature> getOptionalFeatures() {
return optionalFeatures;
}
/** Synthetic input for {@link #getOptionalFeatures()} */
@Input
public List<String> getOptionalFeaturesString() {
return optionalFeatures.stream().map(Enum::toString).collect(Collectors.toList());
}
@Internal
public VariantConfiguration getVariantConfiguration() {
return variantConfiguration;
}
public void setVariantConfiguration(
VariantConfiguration<CoreBuildType, CoreProductFlavor, CoreProductFlavor> variantConfiguration) {
this.variantConfiguration = variantConfiguration;
}
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
public FileCollection getManifests() {
return manifests.getArtifactFiles();
}
@InputFiles
@Optional
@PathSensitive(PathSensitivity.RELATIVE)
public FileCollection getFeatureManifests() {
if (featureManifests == null) {
return null;
}
return featureManifests.getArtifactFiles();
}
@InputFiles
@Optional
@PathSensitive(PathSensitivity.RELATIVE)
public FileCollection getMicroApkManifest() {
return microApkManifest;
}
@InputFiles
@Optional
@PathSensitive(PathSensitivity.RELATIVE)
public FileCollection getCompatibleScreensManifest() {
return compatibleScreensManifest;
}
@InputFiles
@Optional
@PathSensitive(PathSensitivity.RELATIVE)
public FileCollection getPackageManifest() {
return packageManifest;
}
@Input
@Optional
public Set<String> getSupportedAbis() {
return supportedAbis;
}
@Input
@Optional
public String getBuildTargetAbi() {
return buildTargetAbi;
}
@Input
@Optional
public String getBuildTargetDensity() {
return buildTargetDensity;
}
@Input
@Optional
public String getFeatureName() {
return featureName;
}
public static class ConfigAction implements TaskConfigAction<MergeManifests> {
protected final VariantScope variantScope;
protected final List<Feature> optionalFeatures;
@Nullable private final File reportFile;
public ConfigAction(
@NonNull VariantScope scope,
@NonNull List<Feature> optionalFeatures,
@Nullable File reportFile) {
this.variantScope = scope;
this.optionalFeatures = optionalFeatures;
this.reportFile = reportFile;
}
@NonNull
@Override
public String getName() {
return variantScope.getTaskName("process", "Manifest");
}
@NonNull
@Override
public Class<MergeManifests> getType() {
return MergeManifests.class;
}
@Override
public void execute(@NonNull MergeManifests processManifestTask) {
final BaseVariantData variantData = variantScope.getVariantData();
final GradleVariantConfiguration config = variantData.getVariantConfiguration();
GlobalScope globalScope = variantScope.getGlobalScope();
AndroidBuilder androidBuilder = globalScope.getAndroidBuilder();
ProjectOptions projectOptions = variantScope.getGlobalScope().getProjectOptions();
processManifestTask.setAndroidBuilder(androidBuilder);
processManifestTask.setVariantName(config.getFullName());
processManifestTask.outputScope = variantData.getOutputScope();
processManifestTask.setVariantConfiguration(config);
Project project = globalScope.getProject();
// This includes the dependent libraries.
processManifestTask.manifests =
variantScope.getArtifactCollection(RUNTIME_CLASSPATH, ALL, MANIFEST);
// optional manifest files too.
if (variantScope.getMicroApkTask() != null &&
config.getBuildType().isEmbedMicroApp()) {
processManifestTask.microApkManifest = project.files(
variantScope.getMicroApkManifestFile());
}
processManifestTask.compatibleScreensManifest =
variantScope.getOutput(VariantScope.TaskOutputType.COMPATIBLE_SCREEN_MANIFEST);
processManifestTask.minSdkVersion =
TaskInputHelper.memoize(
() -> {
ApiVersion minSdk = config.getMergedFlavor().getMinSdkVersion();
return minSdk == null ? null : minSdk.getApiString();
});
processManifestTask.targetSdkVersion =
TaskInputHelper.memoize(
() -> {
ApiVersion targetSdk =
config.getMergedFlavor().getTargetSdkVersion();
return targetSdk == null ? null : targetSdk.getApiString();
});
processManifestTask.maxSdkVersion =
TaskInputHelper.memoize(config.getMergedFlavor()::getMaxSdkVersion);
processManifestTask.setManifestOutputDirectory(
variantScope.getManifestOutputDirectory());
processManifestTask.setInstantRunManifestOutputDirectory(
variantScope.getInstantRunManifestOutputDirectory());
processManifestTask.setReportFile(reportFile);
processManifestTask.optionalFeatures = optionalFeatures;
processManifestTask.supportedAbis =
variantData.getVariantConfiguration().getSupportedAbis();
processManifestTask.buildTargetAbi =
projectOptions.get(BUILD_ONLY_TARGET_ABI)
|| variantScope
.getGlobalScope()
.getExtension()
.getSplits()
.getAbi()
.isEnable()
? projectOptions.get(StringOption.IDE_BUILD_TARGET_ABI)
: null;
processManifestTask.buildTargetDensity =
projectOptions.get(StringOption.IDE_BUILD_TARGET_DENSITY);
variantScope
.getVariantData()
.addTask(TaskContainer.TaskKind.PROCESS_MANIFEST, processManifestTask);
}
/**
* Implementation of AndroidBundle that only contains a manifest.
*
* This is used to pass to the merger manifest snippet that needs to be added during
* merge.
*/
public static class ManifestProviderImpl implements ManifestProvider {
@NonNull
private final File manifest;
@NonNull
private final String name;
public ManifestProviderImpl(@NonNull File manifest, @NonNull String name) {
this.manifest = manifest;
this.name = name;
}
@NonNull
@Override
public File getManifest() {
return manifest;
}
@NonNull
@Override
public String getName() {
return name;
}
}
}
public static class FeatureConfigAction extends ConfigAction {
public FeatureConfigAction(
@NonNull VariantScope scope, @NonNull List<Feature> optionalFeatures) {
super(scope, optionalFeatures, null);
}
@Override
public void execute(@NonNull MergeManifests processManifestTask) {
super.execute(processManifestTask);
processManifestTask.featureName =
((FeatureVariantData) variantScope.getVariantData()).getFeatureName();
processManifestTask.packageManifest =
variantScope.getArtifactFileCollection(
COMPILE_CLASSPATH, MODULE, FEATURE_APPLICATION_ID_DECLARATION);
}
}
public static class BaseFeatureConfigAction extends ConfigAction {
public BaseFeatureConfigAction(
@NonNull VariantScope scope, @NonNull List<Feature> optionalFeatures) {
super(scope, optionalFeatures, null);
}
@Override
public void execute(@NonNull MergeManifests processManifestTask) {
super.execute(processManifestTask);
processManifestTask.packageManifest =
variantScope.getArtifactFileCollection(
METADATA_VALUES, MODULE, METADATA_APP_ID_DECLARATION);
// This includes the other features.
processManifestTask.featureManifests =
variantScope.getArtifactCollection(
METADATA_VALUES, MODULE, METADATA_FEATURE_MANIFEST);
}
}
}