blob: b4f4bb03b61262a24b8504225307192a848627ee [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;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.build.gradle.api.AndroidSourceSet;
import com.android.build.gradle.api.BaseVariant;
import com.android.build.gradle.api.VariantFilter;
import com.android.build.gradle.internal.CompileOptions;
import com.android.build.gradle.internal.ExtraModelInfo;
import com.android.build.gradle.internal.LoggingUtil;
import com.android.build.gradle.internal.SdkHandler;
import com.android.build.gradle.internal.SourceSetSourceProviderWrapper;
import com.android.build.gradle.internal.coverage.JacocoExtension;
import com.android.build.gradle.internal.dsl.AaptOptions;
import com.android.build.gradle.internal.dsl.AdbOptions;
import com.android.build.gradle.internal.dsl.AndroidSourceSetFactory;
import com.android.build.gradle.internal.dsl.BuildType;
import com.android.build.gradle.internal.dsl.CoreBuildType;
import com.android.build.gradle.internal.dsl.CoreProductFlavor;
import com.android.build.gradle.internal.dsl.DexOptions;
import com.android.build.gradle.internal.dsl.LintOptions;
import com.android.build.gradle.internal.dsl.PackagingOptions;
import com.android.build.gradle.internal.dsl.PreprocessingOptions;
import com.android.build.gradle.internal.dsl.ProductFlavor;
import com.android.build.gradle.internal.dsl.SigningConfig;
import com.android.build.gradle.internal.dsl.Splits;
import com.android.build.gradle.internal.dsl.TestOptions;
import com.android.builder.core.AndroidBuilder;
import com.android.builder.core.BuilderConstants;
import com.android.builder.core.LibraryRequest;
import com.android.builder.model.SourceProvider;
import com.android.builder.sdk.TargetInfo;
import com.android.builder.testing.api.DeviceProvider;
import com.android.builder.testing.api.TestServer;
import com.android.sdklib.repository.FullRevision;
import com.google.common.annotations.Beta;
import com.google.common.collect.Lists;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.tasks.SourceSet;
import org.gradle.internal.reflect.Instantiator;
import java.io.File;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Base 'android' extension for all android plugins.
*
* <p>This is never used directly. Instead,
*<ul>
* <li>Plugin <code>com.android.application</code> uses {@link AppExtension}</li>
* <li>Plugin <code>com.android.library</code> uses {@link LibraryExtension}</li>
* <li>Plugin <code>com.android.test</code> uses {@link TestedExtension}</li>
* </ul>
*/
@SuppressWarnings("UnnecessaryInheritDoc")
public abstract class BaseExtension implements AndroidConfig {
private String target;
private FullRevision buildToolsRevision;
private List<LibraryRequest> libraryRequests = Lists.newArrayList();
/** Default config, shared by all flavors. */
final ProductFlavor defaultConfig;
/** Options for aapt, tool for packaging resources. */
final AaptOptions aaptOptions;
/** Lint options. */
final LintOptions lintOptions;
/** Dex options. */
final DexOptions dexOptions;
/** Options for running tests. */
final TestOptions testOptions;
/** Compile options */
final CompileOptions compileOptions;
/** Packaging options. */
final PackagingOptions packagingOptions;
/** Options to control resources preprocessing. Not finalized yet.*/
final PreprocessingOptions preprocessingOptions;
/** JaCoCo options. */
final JacocoExtension jacoco;
/**
* APK splits options.
*
* <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits">APK Splits</a>.
*/
final Splits splits;
/** All product flavors used by this project. */
final NamedDomainObjectContainer<CoreProductFlavor> productFlavors;
/** Build types used by this project. */
final NamedDomainObjectContainer<BuildType> buildTypes;
/** Signing configs used by this project. */
final NamedDomainObjectContainer<SigningConfig> signingConfigs;
private ExtraModelInfo extraModelInfo;
protected Project project;
/** Adb options */
final AdbOptions adbOptions;
/** A prefix to be used when creating new resources. Used by Studio */
String resourcePrefix;
List<String> flavorDimensionList;
private String defaultPublishConfig = "release";
private boolean publishNonDefault = false;
private Action<VariantFilter> variantFilter;
private final List<DeviceProvider> deviceProviderList = Lists.newArrayList();
private final List<TestServer> testServerList = Lists.newArrayList();
private final AndroidBuilder androidBuilder;
private final SdkHandler sdkHandler;
protected Logger logger;
private boolean isWritable = true;
/**
* The source sets container.
*/
final NamedDomainObjectContainer<AndroidSourceSet> sourceSetsContainer;
BaseExtension(
@NonNull final ProjectInternal project,
@NonNull Instantiator instantiator,
@NonNull AndroidBuilder androidBuilder,
@NonNull SdkHandler sdkHandler,
@NonNull NamedDomainObjectContainer<BuildType> buildTypes,
@NonNull NamedDomainObjectContainer<ProductFlavor> productFlavors,
@NonNull NamedDomainObjectContainer<SigningConfig> signingConfigs,
@NonNull ExtraModelInfo extraModelInfo,
final boolean isLibrary) {
this.androidBuilder = androidBuilder;
this.sdkHandler = sdkHandler;
this.buildTypes = buildTypes;
//noinspection unchecked
this.productFlavors = (NamedDomainObjectContainer) productFlavors;
this.signingConfigs = signingConfigs;
this.extraModelInfo = extraModelInfo;
this.project = project;
logger = Logging.getLogger(this.getClass());
defaultConfig = instantiator.newInstance(ProductFlavor.class, BuilderConstants.MAIN,
project, instantiator, project.getLogger());
aaptOptions = instantiator.newInstance(AaptOptions.class);
dexOptions = instantiator.newInstance(DexOptions.class);
lintOptions = instantiator.newInstance(LintOptions.class);
testOptions = instantiator.newInstance(TestOptions.class);
compileOptions = instantiator.newInstance(CompileOptions.class);
packagingOptions = instantiator.newInstance(PackagingOptions.class);
preprocessingOptions = instantiator.newInstance(PreprocessingOptions.class);
jacoco = instantiator.newInstance(JacocoExtension.class);
adbOptions = instantiator.newInstance(AdbOptions.class);
splits = instantiator.newInstance(Splits.class, instantiator);
sourceSetsContainer = project.container(AndroidSourceSet.class,
new AndroidSourceSetFactory(instantiator, project, isLibrary));
sourceSetsContainer.whenObjectAdded(new Action<AndroidSourceSet>() {
@Override
public void execute(AndroidSourceSet sourceSet) {
ConfigurationContainer configurations = project.getConfigurations();
createConfiguration(
configurations,
sourceSet.getCompileConfigurationName(),
"Classpath for compiling the " + sourceSet.getName() + " sources.");
String packageConfigDescription;
if (isLibrary) {
packageConfigDescription
= "Classpath only used when publishing '" + sourceSet.getName() + "'.";
} else {
packageConfigDescription
= "Classpath packaged with the compiled '" + sourceSet.getName() + "' classes.";
}
createConfiguration(
configurations,
sourceSet.getPackageConfigurationName(),
packageConfigDescription);
createConfiguration(
configurations,
sourceSet.getProvidedConfigurationName(),
"Classpath for only compiling the " + sourceSet.getName() + " sources.");
createConfiguration(
configurations,
sourceSet.getWearAppConfigurationName(),
"Link to a wear app to embed for object '" + sourceSet.getName() + "'.");
sourceSet.setRoot(String.format("src/%s", sourceSet.getName()));
}
});
sourceSetsContainer.create(defaultConfig.getName());
}
/**
* Disallow further modification on the extension.
*/
public void disableWrite() {
isWritable = false;
}
protected void checkWritability() {
if (!isWritable) {
throw new GradleException(
"Android tasks have already been created.\n" +
"This happens when calling android.applicationVariants,\n" +
"android.libraryVariants or android.testVariants.\n" +
"Once these methods are called, it is not possible to\n" +
"continue configuring the model.");
}
}
protected void createConfiguration(
@NonNull ConfigurationContainer configurations,
@NonNull String configurationName,
@NonNull String configurationDescription) {
logger.info("Creating configuration {}", configurationName);
Configuration configuration = configurations.findByName(configurationName);
if (configuration == null) {
configuration = configurations.create(configurationName);
}
configuration.setVisible(false);
configuration.setDescription(configurationDescription);
}
/**
* Sets the compile SDK version, based on full SDK version string, e.g.
* <code>android-21</code> for Lollipop.
*/
public void compileSdkVersion(String version) {
checkWritability();
this.target = version;
}
/**
* Sets the compile SDK version, based on API level, e.g. 21 for Lollipop.
*/
public void compileSdkVersion(int apiLevel) {
compileSdkVersion("android-" + apiLevel);
}
public void setCompileSdkVersion(int apiLevel) {
compileSdkVersion(apiLevel);
}
public void setCompileSdkVersion(String target) {
compileSdkVersion(target);
}
/**
* Request the use a of Library. The library is then added to the classpath.
* @param name the name of the library.
*/
public void useLibrary(String name) {
useLibrary(name, true);
}
/**
* Request the use a of Library. The library is then added to the classpath.
* @param name the name of the library.
* @param required if using the library requires a manifest entry, the entry will
* indicate that the library is not required.
*/
public void useLibrary(String name, boolean required) {
libraryRequests.add(new LibraryRequest(name, required));
}
public void buildToolsVersion(String version) {
checkWritability();
buildToolsRevision = FullRevision.parseRevision(version);
}
/**
* <strong>Required.</strong> Version of the build tools to use.
*
* <p>Value assigned to this property is parsed and stored in a normalized form, so reading it
* back may give a slightly different string.
*/
@Override
public String getBuildToolsVersion() {
return buildToolsRevision.toString();
}
public void setBuildToolsVersion(String version) {
buildToolsVersion(version);
}
/**
* Configures the build types.
*/
public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
checkWritability();
action.execute(buildTypes);
}
/**
* Configures the product flavors.
*/
public void productFlavors(Action<? super NamedDomainObjectContainer<CoreProductFlavor>> action) {
checkWritability();
action.execute(productFlavors);
}
/**
* Configures the signing configs.
*/
public void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
checkWritability();
action.execute(signingConfigs);
}
public void flavorDimensions(String... dimensions) {
checkWritability();
flavorDimensionList = Arrays.asList(dimensions);
}
/**
* Configures the source sets. Note that the Android plugin uses its own implementation of
* source sets, {@link AndroidSourceSet}.
*/
public void sourceSets(Action<NamedDomainObjectContainer<AndroidSourceSet>> action) {
checkWritability();
action.execute(sourceSetsContainer);
}
/**
* All source sets. Note that the Android plugin uses its own implementation of
* source sets, {@link AndroidSourceSet}.
*/
@Override
public NamedDomainObjectContainer<AndroidSourceSet> getSourceSets() {
return sourceSetsContainer;
}
/**
* The default configuration, inherited by all build flavors (if any are defined).
*/
public void defaultConfig(Action<ProductFlavor> action) {
checkWritability();
action.execute(defaultConfig);
}
/**
* Configures aapt options.
*/
public void aaptOptions(Action<AaptOptions> action) {
checkWritability();
action.execute(aaptOptions);
}
/**
* Configures dex options.
*/
public void dexOptions(Action<DexOptions> action) {
checkWritability();
action.execute(dexOptions);
}
/**
* Configure lint options.
*/
public void lintOptions(Action<LintOptions> action) {
checkWritability();
action.execute(lintOptions);
}
/** Configures the test options. */
public void testOptions(Action<TestOptions> action) {
checkWritability();
action.execute(testOptions);
}
/**
* Configures compile options.
*/
public void compileOptions(Action<CompileOptions> action) {
checkWritability();
action.execute(compileOptions);
}
/**
* Configures packaging options.
*/
public void packagingOptions(Action<PackagingOptions> action) {
checkWritability();
action.execute(packagingOptions);
}
/**
* Configures preprocessing options.
*/
public void preprocessingOptions(Action<PreprocessingOptions> action) {
checkWritability();
action.execute(preprocessingOptions);
}
/**
* Configures JaCoCo options.
*/
public void jacoco(Action<JacocoExtension> action) {
checkWritability();
action.execute(jacoco);
}
/**
* Configures adb options.
*/
public void adbOptions(Action<AdbOptions> action) {
checkWritability();
action.execute(adbOptions);
}
/**
* Configures APK splits.
*/
public void splits(Action<Splits> action) {
checkWritability();
action.execute(splits);
}
public void deviceProvider(DeviceProvider deviceProvider) {
checkWritability();
deviceProviderList.add(deviceProvider);
}
@Override
@NonNull
public List<DeviceProvider> getDeviceProviders() {
return deviceProviderList;
}
public void testServer(TestServer testServer) {
checkWritability();
testServerList.add(testServer);
}
@Override
@NonNull
public List<TestServer> getTestServers() {
return testServerList;
}
/** {@inheritDoc} */
@Override
public Collection<? extends CoreProductFlavor> getProductFlavors() {
return productFlavors;
}
/** {@inheritDoc} */
@Override
public Collection<? extends CoreBuildType> getBuildTypes() {
return buildTypes;
}
/** {@inheritDoc} */
@Override
public Collection<? extends com.android.builder.model.SigningConfig> getSigningConfigs() {
return signingConfigs;
}
public void defaultPublishConfig(String value) {
setDefaultPublishConfig(value);
}
public void publishNonDefault(boolean value) {
publishNonDefault = value;
}
/**
* Name of the configuration used to build the default artifact of this project.
*
* <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Referencing-a-Library">
* Referencing a Library</a>
*/
@Override
public String getDefaultPublishConfig() {
return defaultPublishConfig;
}
public void setDefaultPublishConfig(String value) {
defaultPublishConfig = value;
}
/**
* Whether to publish artifacts for all configurations, not just the default one.
*
* <p>See <a href="http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Referencing-a-Library">
* Referencing a Library</a>
*/
@Override
public boolean getPublishNonDefault() {
return publishNonDefault;
}
public void variantFilter(Action<VariantFilter> filter) {
setVariantFilter(filter);
}
public void setVariantFilter(Action<VariantFilter> filter) {
variantFilter = filter;
}
/**
* A variant filter to control which variants are excluded.
* <p>The filter is an {@link Action} which is passed a single object of type
* {@link com.android.build.gradle.internal.api.VariantFilter}. It should set the
* {@link VariantFilter#setIgnore(boolean)} flag to filter out the given variant.
*/
@Override
public Action<VariantFilter> getVariantFilter() {
return variantFilter;
}
@Override
public AdbOptions getAdbOptions() {
return adbOptions;
}
/** {@inheritDoc} */
@Override
public String getResourcePrefix() {
return resourcePrefix;
}
@Override
public List<String> getFlavorDimensionList() {
return flavorDimensionList;
}
@Override
public boolean getGeneratePureSplits() {
return generatePureSplits;
}
/** {@inheritDoc} */
@Override
@Beta
public PreprocessingOptions getPreprocessingOptions() {
return preprocessingOptions;
}
public void resourcePrefix(String prefix) {
resourcePrefix = prefix;
}
public abstract void addVariant(BaseVariant variant);
public void registerArtifactType(@NonNull String name,
boolean isTest,
int artifactType) {
extraModelInfo.registerArtifactType(name, isTest, artifactType);
}
public void registerBuildTypeSourceProvider(
@NonNull String name,
@NonNull BuildType buildType,
@NonNull SourceProvider sourceProvider) {
extraModelInfo.registerBuildTypeSourceProvider(name, buildType, sourceProvider);
}
public void registerProductFlavorSourceProvider(
@NonNull String name,
@NonNull CoreProductFlavor productFlavor,
@NonNull SourceProvider sourceProvider) {
extraModelInfo.registerProductFlavorSourceProvider(name, productFlavor, sourceProvider);
}
public void registerJavaArtifact(
@NonNull String name,
@NonNull BaseVariant variant,
@NonNull String assembleTaskName,
@NonNull String javaCompileTaskName,
@NonNull Collection<File> generatedSourceFolders,
@NonNull Iterable<String> ideSetupTaskNames,
@NonNull Configuration configuration,
@NonNull File classesFolder,
@NonNull File javaResourceFolder,
@Nullable SourceProvider sourceProvider) {
extraModelInfo.registerJavaArtifact(name, variant, assembleTaskName,
javaCompileTaskName, generatedSourceFolders, ideSetupTaskNames,
configuration, classesFolder, javaResourceFolder, sourceProvider);
}
public void registerMultiFlavorSourceProvider(
@NonNull String name,
@NonNull String flavorName,
@NonNull SourceProvider sourceProvider) {
extraModelInfo.registerMultiFlavorSourceProvider(name, flavorName, sourceProvider);
}
@NonNull
public SourceProvider wrapJavaSourceSet(@NonNull SourceSet sourceSet) {
return new SourceSetSourceProviderWrapper(sourceSet);
}
/**
* <strong>Required.</strong> Compile SDK version.
*
* <p>Your code will be compiled against the android.jar from this API level. You should
* generally use the most up-to-date SDK version here. Use the Lint tool to make sure you don't
* use APIs not available in earlier platform version without checking.
*
* <p>Setter can be called with a string like "android-21" or a number.
*
* <p>Value assigned to this property is parsed and stored in a normalized form, so reading it
* back may give a slightly different string.
*/
@Override
public String getCompileSdkVersion() {
return target;
}
@Override
public FullRevision getBuildToolsRevision() {
return buildToolsRevision;
}
@Override
public Collection<LibraryRequest> getLibraryRequests() {
return libraryRequests;
}
public File getSdkDirectory() {
return sdkHandler.getSdkFolder();
}
public File getNdkDirectory() {
return sdkHandler.getNdkFolder();
}
public List<File> getBootClasspath() {
ensureTargetSetup();
return androidBuilder.getBootClasspath();
}
public File getAdbExe() {
return sdkHandler.getSdkInfo().getAdb();
}
public File getDefaultProguardFile(String name) {
File sdkDir = sdkHandler.getAndCheckSdkFolder();
return new File(sdkDir,
SdkConstants.FD_TOOLS + File.separatorChar
+ SdkConstants.FD_PROGUARD + File.separatorChar
+ name);
}
// ---------------
// TEMP for compatibility
// by default, we do not generate pure splits
boolean generatePureSplits = false;
public void generatePureSplits(boolean flag) {
if (flag) {
logger.warn("Pure splits are not supported by PlayStore yet.");
}
this.generatePureSplits = flag;
}
private boolean enforceUniquePackageName = true;
public void enforceUniquePackageName(boolean value) {
if (!value) {
LoggingUtil.displayDeprecationWarning(logger, project, "Support for libraries with same package name is deprecated and will be removed in a future release.");
}
enforceUniquePackageName = value;
}
public void setEnforceUniquePackageName(boolean value) {
enforceUniquePackageName(value);
}
@Override
public boolean getEnforceUniquePackageName() {
return enforceUniquePackageName;
}
/** {@inheritDoc} */
@Override
public CoreProductFlavor getDefaultConfig() {
return defaultConfig;
}
/** {@inheritDoc} */
@Override
public AaptOptions getAaptOptions() {
return aaptOptions;
}
/** {@inheritDoc} */
@Override
public CompileOptions getCompileOptions() {
return compileOptions;
}
/** {@inheritDoc} */
@Override
public DexOptions getDexOptions() {
return dexOptions;
}
/** {@inheritDoc} */
@Override
public JacocoExtension getJacoco() {
return jacoco;
}
/** {@inheritDoc} */
@Override
public LintOptions getLintOptions() {
return lintOptions;
}
/** {@inheritDoc} */
@Override
public PackagingOptions getPackagingOptions() {
return packagingOptions;
}
/** {@inheritDoc} */
@Override
public Splits getSplits() {
return splits;
}
/** {@inheritDoc} */
@Override
public TestOptions getTestOptions() {
return testOptions;
}
private void ensureTargetSetup() {
// check if the target has been set.
TargetInfo targetInfo = androidBuilder.getTargetInfo();
if (targetInfo == null) {
sdkHandler.initTarget(
getCompileSdkVersion(),
buildToolsRevision,
libraryRequests,
androidBuilder);
}
}
// For compatibility with LibraryExtension.
@Override
public Boolean getPackageBuildConfig() {
throw new GradleException("packageBuildConfig is not supported.");
}
}