blob: 838ad3e3aea9c331bdc6e8087dedee6dca377363 [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.internal.model;
import static com.android.builder.model.AndroidProject.ARTIFACT_MAIN;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.OutputFile;
import com.android.build.gradle.BaseExtension;
import com.android.build.gradle.api.ApkOutputFile;
import com.android.build.gradle.internal.BuildTypeData;
import com.android.build.gradle.internal.ExtraModelInfo;
import com.android.build.gradle.internal.ProductFlavorData;
import com.android.build.gradle.internal.TaskManager;
import com.android.build.gradle.internal.VariantManager;
import com.android.build.gradle.internal.core.GradleVariantConfiguration;
import com.android.build.gradle.internal.dsl.GroupableProductFlavor;
import com.android.build.gradle.internal.scope.VariantScope;
import com.android.build.gradle.internal.variant.ApkVariantOutputData;
import com.android.build.gradle.internal.variant.BaseVariantData;
import com.android.build.gradle.internal.variant.BaseVariantOutputData;
import com.android.build.gradle.internal.variant.TestVariantData;
import com.android.build.gradle.internal.variant.TestedVariantData;
import com.android.builder.Version;
import com.android.builder.core.AndroidBuilder;
import com.android.builder.core.DefaultProductFlavor;
import com.android.builder.core.VariantType;
import com.android.builder.model.AaptOptions;
import com.android.builder.model.AndroidArtifact;
import com.android.builder.model.AndroidArtifactOutput;
import com.android.builder.model.AndroidProject;
import com.android.builder.model.ApiVersion;
import com.android.builder.model.ArtifactMetaData;
import com.android.builder.model.JavaArtifact;
import com.android.builder.model.LintOptions;
import com.android.builder.model.SigningConfig;
import com.android.builder.model.SourceProvider;
import com.android.builder.model.SourceProviderContainer;
import com.android.builder.model.SyncIssue;
import com.android.sdklib.AndroidVersion;
import com.android.sdklib.IAndroidTarget;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
import org.gradle.tooling.provider.model.ToolingModelBuilder;
import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Builder for the custom Android model.
*/
public class ModelBuilder implements ToolingModelBuilder {
@NonNull
private final AndroidBuilder androidBuilder;
@NonNull
private final BaseExtension extension;
@NonNull
private final ExtraModelInfo extraModelInfo;
@NonNull
private final VariantManager variantManager;
@NonNull
private final TaskManager taskManager;
private final boolean isLibrary;
public ModelBuilder(
@NonNull AndroidBuilder androidBuilder,
@NonNull VariantManager variantManager,
@NonNull TaskManager taskManager,
@NonNull BaseExtension extension,
@NonNull ExtraModelInfo extraModelInfo,
boolean isLibrary) {
this.androidBuilder = androidBuilder;
this.extension = extension;
this.extraModelInfo = extraModelInfo;
this.variantManager = variantManager;
this.taskManager = taskManager;
this.isLibrary = isLibrary;
}
public static void clearCaches() {
DependenciesImpl.clearCaches();
}
@Override
public boolean canBuild(String modelName) {
// The default name for a model is the name of the Java interface.
return modelName.equals(AndroidProject.class.getName());
}
@Override
public Object buildAll(String modelName, Project project) {
NamedDomainObjectContainer<SigningConfig> signingConfigs;
// Cast is needed due to covariance issues.
//noinspection unchecked
signingConfigs = (NamedDomainObjectContainer<SigningConfig>) (NamedDomainObjectContainer<?>) extension.getSigningConfigs();
// Get the boot classpath. This will ensure the target is configured.
List<String> bootClasspath = androidBuilder.getBootClasspathAsStrings();
List<File> frameworkSource = Collections.emptyList();
// List of extra artifacts, with all test variants added.
List<ArtifactMetaData> artifactMetaDataList = Lists.newArrayList(
extraModelInfo.getExtraArtifacts());
for (VariantType variantType : VariantType.getTestingTypes()) {
artifactMetaDataList.add(new ArtifactMetaDataImpl(
variantType.getArtifactName(),
true /*isTest*/,
variantType.getArtifactType()));
}
LintOptions lintOptions = com.android.build.gradle.internal.dsl.LintOptions.create(
extension.getLintOptions());
AaptOptions aaptOptions = AaptOptionsImpl.create(extension.getAaptOptions());
List<SyncIssue> syncIssues = Lists.newArrayList(extraModelInfo.getSyncIssues().values());
List<String> flavorDimensionList = (extension.getFlavorDimensionList() != null ?
extension.getFlavorDimensionList() : Lists.<String>newArrayList());
DefaultAndroidProject androidProject = new DefaultAndroidProject(
Version.ANDROID_GRADLE_PLUGIN_VERSION,
project.getName(),
flavorDimensionList,
androidBuilder.getTarget() != null ? androidBuilder.getTarget().hashString() : "",
bootClasspath,
frameworkSource,
cloneSigningConfigs(signingConfigs),
aaptOptions,
artifactMetaDataList,
findUnresolvedDependencies(syncIssues),
syncIssues,
extension.getCompileOptions(),
lintOptions,
project.getBuildDir(),
extension.getResourcePrefix(),
isLibrary,
Version.BUILDER_MODEL_API_VERSION);
androidProject.setDefaultConfig(ProductFlavorContainerImpl.createProductFlavorContainer(
variantManager.getDefaultConfig(),
extraModelInfo.getExtraFlavorSourceProviders(
variantManager.getDefaultConfig().getProductFlavor().getName())));
for (BuildTypeData btData : variantManager.getBuildTypes().values()) {
androidProject.addBuildType(BuildTypeContainerImpl.create(
btData,
extraModelInfo.getExtraBuildTypeSourceProviders(btData.getBuildType().getName())));
}
for (ProductFlavorData pfData : variantManager.getProductFlavors().values()) {
androidProject.addProductFlavors(ProductFlavorContainerImpl.createProductFlavorContainer(
pfData,
extraModelInfo.getExtraFlavorSourceProviders(pfData.getProductFlavor().getName())));
}
for (BaseVariantData<? extends BaseVariantOutputData> variantData : variantManager.getVariantDataList()) {
if (!variantData.getType().isForTesting()) {
androidProject.addVariant(createVariant(variantData));
}
}
return androidProject;
}
@NonNull
private VariantImpl createVariant(
@NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
AndroidArtifact mainArtifact = createAndroidArtifact(ARTIFACT_MAIN, variantData);
GradleVariantConfiguration variantConfiguration = variantData.getVariantConfiguration();
String variantName = variantConfiguration.getFullName();
List<AndroidArtifact> extraAndroidArtifacts = Lists.newArrayList(
extraModelInfo.getExtraAndroidArtifacts(variantName));
// Make sure all extra artifacts are serializable.
Collection<JavaArtifact> extraJavaArtifacts = extraModelInfo.getExtraJavaArtifacts(variantName);
List<JavaArtifact> clonedExtraJavaArtifacts = Lists.newArrayListWithCapacity(extraJavaArtifacts.size());
for (JavaArtifact javaArtifact : extraJavaArtifacts) {
clonedExtraJavaArtifacts.add(JavaArtifactImpl.clone(javaArtifact));
}
if (variantData instanceof TestedVariantData) {
for (VariantType variantType : VariantType.getTestingTypes()) {
TestVariantData testVariantData = ((TestedVariantData) variantData).getTestVariantData(variantType);
if (testVariantData != null) {
VariantType type = testVariantData.getType();
if (type != null) {
switch (type) {
case ANDROID_TEST:
extraAndroidArtifacts.add(createAndroidArtifact(
variantType.getArtifactName(),
testVariantData));
break;
case UNIT_TEST:
clonedExtraJavaArtifacts.add(createUnitTestsJavaArtifact(
variantType,
testVariantData));
break;
default:
throw new IllegalArgumentException(
"Unsupported test variant type ${variantType}.");
}
}
}
}
}
// if the target is a codename, override the model value.
ApiVersion sdkVersionOverride = null;
// we know the getTargetInfo won't return null here.
@SuppressWarnings("ConstantConditions")
IAndroidTarget androidTarget = androidBuilder.getTargetInfo().getTarget();
AndroidVersion version = androidTarget.getVersion();
if (version.getCodename() != null) {
sdkVersionOverride = ApiVersionImpl.clone(version);
}
return new VariantImpl(
variantName,
variantConfiguration.getBaseName(),
variantConfiguration.getBuildType().getName(),
getProductFlavorNames(variantData),
ProductFlavorImpl.cloneFlavor(
variantConfiguration.getMergedFlavor(),
sdkVersionOverride,
sdkVersionOverride),
mainArtifact,
extraAndroidArtifacts,
clonedExtraJavaArtifacts);
}
private JavaArtifactImpl createUnitTestsJavaArtifact(
@NonNull VariantType variantType,
@NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
SourceProviders sourceProviders = determineSourceProviders(variantData);
DependenciesImpl dependencies = DependenciesImpl.cloneDependencies(variantData,
androidBuilder);
// Add the mockable JAR path. It will be created before tests are actually run from the IDE.
dependencies.getJavaLibraries().add(
new JavaLibraryImpl(
taskManager.createMockableJar.getOutputFile(),
null,
null));
List<File> extraGeneratedSourceFolders = variantData.getExtraGeneratedSourceFolders();
return new JavaArtifactImpl(
variantType.getArtifactName(),
variantData.assembleVariantTask.getName(),
variantData.compileTask.getName(),
Sets.newHashSet(variantData.prepareDependenciesTask.getName(),
taskManager.createMockableJar.getName()),
extraGeneratedSourceFolders != null ? extraGeneratedSourceFolders : Collections.<File>emptyList(),
(variantData.javaCompileTask != null) ?
variantData.javaCompileTask.getDestinationDir() :
variantData.getScope().getJavaOutputDir(),
variantData.processJavaResourcesTask.getDestinationDir(),
dependencies,
sourceProviders.variantSourceProvider,
sourceProviders.multiFlavorSourceProvider);
}
private AndroidArtifact createAndroidArtifact(
@NonNull String name,
@NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
GradleVariantConfiguration variantConfiguration = variantData.getVariantConfiguration();
SigningConfig signingConfig = variantConfiguration.getSigningConfig();
String signingConfigName = null;
if (signingConfig != null) {
signingConfigName = signingConfig.getName();
}
SourceProviders sourceProviders = determineSourceProviders(variantData);
// get the outputs
List<? extends BaseVariantOutputData> variantOutputs = variantData.getOutputs();
List<AndroidArtifactOutput> outputs = Lists.newArrayListWithCapacity(variantOutputs.size());
for (BaseVariantOutputData variantOutputData : variantOutputs) {
int intVersionCode;
if (variantOutputData instanceof ApkVariantOutputData) {
intVersionCode = variantOutputData.getVersionCode();
} else {
Integer versionCode = variantConfiguration.getMergedFlavor().getVersionCode();
intVersionCode = versionCode != null ? versionCode : 1;
}
ImmutableCollection.Builder<OutputFile> outputFiles = ImmutableList.builder();
// add the main APK
outputFiles.add(new OutputFileImpl(
variantOutputData.getMainOutputFile().getFilters(),
variantOutputData.getMainOutputFile().getType().name(),
variantOutputData.getOutputFile()));
for (ApkOutputFile splitApk : variantOutputData.getOutputs()) {
if (splitApk.getType() == OutputFile.OutputType.SPLIT) {
outputFiles.add(new OutputFileImpl(
splitApk.getFilters(), OutputFile.SPLIT, splitApk.getOutputFile()));
}
}
// add the main APK.
outputs.add(new AndroidArtifactOutputImpl(
outputFiles.build(),
variantOutputData.assembleTask.getName(),
variantOutputData.getScope().getManifestOutputFile(),
intVersionCode));
}
VariantScope scope = variantData.getScope();
return new AndroidArtifactImpl(
name,
outputs,
variantData.assembleVariantTask.getName(),
variantConfiguration.isSigningReady() || variantData.outputsAreSigned,
signingConfigName,
variantConfiguration.getApplicationId(),
scope.getSourceGenTask().getName(),
scope.getCompileTask().getName(),
getGeneratedSourceFolders(variantData),
getGeneratedResourceFolders(variantData),
(variantData.javaCompileTask != null) ?
variantData.javaCompileTask.getDestinationDir() :
scope.getJavaOutputDir(),
scope.getJavaResourcesDestinationDir(),
DependenciesImpl.cloneDependencies(variantData, androidBuilder),
sourceProviders.variantSourceProvider,
sourceProviders.multiFlavorSourceProvider,
variantConfiguration.getSupportedAbis(),
variantConfiguration.getMergedBuildConfigFields(),
variantConfiguration.getMergedResValues());
}
private static SourceProviders determineSourceProviders(
@NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
SourceProvider variantSourceProvider =
variantData.getVariantConfiguration().getVariantSourceProvider();
SourceProvider multiFlavorSourceProvider =
variantData.getVariantConfiguration().getMultiFlavorSourceProvider();
return new SourceProviders(
variantSourceProvider != null ?
SourceProviderImpl.cloneProvider(variantSourceProvider) :
null,
multiFlavorSourceProvider != null ?
SourceProviderImpl.cloneProvider(multiFlavorSourceProvider) :
null);
}
@NonNull
private static List<String> getProductFlavorNames(
@NonNull BaseVariantData<? extends BaseVariantOutputData> variantData) {
List<GroupableProductFlavor> productFlavors = variantData.getVariantConfiguration()
.getProductFlavors();
List<String> flavorNames = Lists.newArrayListWithCapacity(productFlavors.size());
for (DefaultProductFlavor flavor : productFlavors) {
flavorNames.add(flavor.getName());
}
return flavorNames;
}
@NonNull
private static List<File> getGeneratedSourceFolders(
@Nullable BaseVariantData<? extends BaseVariantOutputData> variantData) {
if (variantData == null) {
return Collections.emptyList();
}
List<File> extraFolders = variantData.getExtraGeneratedSourceFolders();
List<File> folders;
if (extraFolders != null) {
folders = Lists.newArrayListWithExpectedSize(5 + extraFolders.size());
folders.addAll(extraFolders);
} else {
folders = Lists.newArrayListWithExpectedSize(5);
}
VariantScope scope = variantData.getScope();
// The R class is only generated by the first output.
folders.add(scope.getRClassSourceOutputDir());
folders.add(scope.getAidlSourceOutputDir());
folders.add(scope.getBuildConfigSourceOutputDir());
Boolean ndkMode = variantData.getVariantConfiguration().getMergedFlavor().getRenderscriptNdkModeEnabled();
if (ndkMode == null || !ndkMode) {
folders.add(scope.getRenderscriptSourceOutputDir());
}
return folders;
}
@NonNull
private static List<File> getGeneratedResourceFolders(
@Nullable BaseVariantData<? extends BaseVariantOutputData> variantData) {
if (variantData == null) {
return Collections.emptyList();
}
List<File> result;
List<File> extraFolders = variantData.getExtraGeneratedResFolders();
if (extraFolders != null && !extraFolders.isEmpty()) {
result = Lists.newArrayListWithCapacity(extraFolders.size() + 2);
result.addAll(extraFolders);
} else {
result = Lists.newArrayListWithCapacity(2);
}
VariantScope scope = variantData.getScope();
result.add(scope.getRenderscriptResOutputDir());
result.add(scope.getGeneratedResOutputDir());
return result;
}
@NonNull
private static Collection<SigningConfig> cloneSigningConfigs(
@NonNull Collection<SigningConfig> signingConfigs) {
Collection<SigningConfig> results = Lists.newArrayListWithCapacity(signingConfigs.size());
for (SigningConfig signingConfig : signingConfigs) {
results.add(SigningConfigImpl.createSigningConfig(signingConfig));
}
return results;
}
@Nullable
private static SourceProviderContainer getSourceProviderContainer(
@NonNull Collection<SourceProviderContainer> items,
@NonNull String name) {
for (SourceProviderContainer item : items) {
if (name.equals(item.getArtifactName())) {
return item;
}
}
return null;
}
private static class SourceProviders {
protected SourceProviderImpl variantSourceProvider;
protected SourceProviderImpl multiFlavorSourceProvider;
public SourceProviders(
SourceProviderImpl variantSourceProvider,
SourceProviderImpl multiFlavorSourceProvider) {
this.variantSourceProvider = variantSourceProvider;
this.multiFlavorSourceProvider = multiFlavorSourceProvider;
}
}
/**
* Return the unresolved dependencies in SyncIssues
*/
private static Collection<String> findUnresolvedDependencies(
@NonNull Collection<SyncIssue> syncIssues) {
List<String> unresolvedDependencies = Lists.newArrayList();
for (SyncIssue issue : syncIssues) {
if (issue.getType() == SyncIssue.TYPE_UNRESOLVED_DEPENDENCY) {
unresolvedDependencies.add(issue.getData());
}
}
return unresolvedDependencies;
}
}