blob: 586f54e95de45608aff3f538829944d88aeb95ca [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.build.gradle.internal.BuildTypeData
import com.android.build.gradle.internal.ProductFlavorData
import com.android.build.gradle.internal.ProductionAppVariant
import com.android.build.gradle.internal.TestAppVariant
import com.android.builder.BuildType
import com.android.builder.ProductFlavor
import com.android.builder.VariantConfiguration
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.plugins.BasePlugin
import org.gradle.internal.reflect.Instantiator
class AndroidPlugin extends AndroidBasePlugin implements Plugin<Project> {
private final Map<String, BuildTypeData> buildTypes = [:]
private final Map<String, ProductFlavorData> productFlavors = [:]
private AndroidExtension extension
@Override
void apply(Project project) {
super.apply(project)
def buildTypes = project.container(BuildType)
// TODO - do the decoration by default
def productFlavors = project.container(ProductFlavor) { name ->
project.services.get(Instantiator).newInstance(ProductFlavor, name)
}
extension = project.extensions.create('android', AndroidExtension,
buildTypes, productFlavors)
setDefaultConfig(extension.defaultConfig)
buildTypes.whenObjectAdded { BuildType buildType ->
addBuildType(buildType)
}
buildTypes.whenObjectRemoved {
throw new UnsupportedOperationException("Removing build types is not implemented yet.")
}
buildTypes.create(BuildType.DEBUG)
buildTypes.create(BuildType.RELEASE)
productFlavors.whenObjectAdded { ProductFlavor productFlavor ->
addProductFlavor(productFlavor)
}
productFlavors.whenObjectRemoved {
throw new UnsupportedOperationException(
"Removing product flavors is not implemented yet.")
}
project.afterEvaluate {
createAndroidTasks()
}
}
private void addBuildType(BuildType buildType) {
if (buildType.name.startsWith("test")) {
throw new RuntimeException("BuildType names cannot start with 'test'")
}
if (productFlavors.containsKey(buildType.name)) {
throw new RuntimeException("BuildType names cannot collide with ProductFlavor names")
}
def sourceSet = project.sourceSets.add(buildType.name)
BuildTypeData buildTypeData = new BuildTypeData(buildType, sourceSet, project)
project.tasks.assemble.dependsOn buildTypeData.assembleTask
buildTypes[buildType.name] = buildTypeData
}
private void addProductFlavor(ProductFlavor productFlavor) {
if (productFlavor.name.startsWith("test")) {
throw new RuntimeException("ProductFlavor names cannot start with 'test'")
}
if (productFlavors.containsKey(productFlavor.name)) {
throw new RuntimeException("ProductFlavor names cannot collide with BuildType names")
}
def mainSourceSet = project.sourceSets.add(productFlavor.name)
def testSourceSet = project.sourceSets.add("test${productFlavor.name.capitalize()}")
ProductFlavorData productFlavorData = new ProductFlavorData(
productFlavor, mainSourceSet, testSourceSet, project)
project.tasks.assemble.dependsOn productFlavorData.assembleTask
productFlavors[productFlavor.name] = productFlavorData
}
private void createAndroidTasks() {
if (productFlavors.isEmpty()) {
createTasksForDefaultBuild()
} else {
// there'll be more than one test app, so we need a top level assembleTest
assembleTest = project.tasks.add("assembleTest")
assembleTest.group = BasePlugin.BUILD_GROUP
assembleTest.description = "Assembles all the Test applications"
productFlavors.values().each { ProductFlavorData productFlavorData ->
createTasksForFlavoredBuild(productFlavorData)
}
}
}
/**
* Creates Tasks for non-flavored build. This means assembleDebug, assembleRelease, and other
* assemble<Type> are directly building the <type> build instead of all build of the given
* <type>.
*/
private createTasksForDefaultBuild() {
BuildTypeData testData = buildTypes[extension.testBuildType]
if (testData == null) {
throw new RuntimeException("Test Build Type '$extension.testBuildType' does not exist.")
}
ProductionAppVariant testedVariant = null
ProductFlavorData defaultConfigData = getDefaultConfigData();
for (BuildTypeData buildTypeData : buildTypes.values()) {
def variantConfig = new VariantConfiguration(
defaultConfigData.productFlavor, defaultConfigData.androidSourceSet,
buildTypeData.buildType, buildTypeData.androidSourceSet,
VariantConfiguration.Type.DEFAULT)
boolean isTestedVariant = (buildTypeData == testData)
ProductionAppVariant productionAppVariant = addVariant(variantConfig,
buildTypeData.assembleTask, isTestedVariant)
if (isTestedVariant) {
testedVariant = productionAppVariant
}
}
assert testedVariant != null
def testVariantConfig = new VariantConfiguration(
defaultConfigData.productFlavor, defaultConfigData.androidTestSourceSet,
testData.buildType, null,
VariantConfiguration.Type.TEST)
def testVariant = new TestAppVariant(testVariantConfig, testedVariant.config)
createTestTasks(testVariant, testedVariant)
}
/**
* Creates Task for a given flavor. This will create tasks for all build types for the given
* flavor.
* @param productFlavorData the flavor to build.
*/
private createTasksForFlavoredBuild(ProductFlavorData productFlavorData) {
BuildTypeData testData = buildTypes[extension.testBuildType]
if (testData == null) {
throw new RuntimeException("Test Build Type '$extension.testBuildType' does not exist.")
}
ProductionAppVariant testedVariant = null
for (BuildTypeData buildTypeData : buildTypes.values()) {
def variantConfig = new VariantConfiguration(
extension.defaultConfig, getDefaultConfigData().androidSourceSet,
buildTypeData.buildType, buildTypeData.androidSourceSet,
VariantConfiguration.Type.DEFAULT)
variantConfig.addProductFlavor(productFlavorData.productFlavor,
productFlavorData.androidSourceSet)
boolean isTestedVariant = (buildTypeData == testData)
ProductionAppVariant productionAppVariant = addVariant(variantConfig, null,
isTestedVariant)
buildTypeData.assembleTask.dependsOn productionAppVariant.assembleTask
productFlavorData.assembleTask.dependsOn productionAppVariant.assembleTask
if (isTestedVariant) {
testedVariant = productionAppVariant
}
}
assert testedVariant != null
def testVariantConfig = new VariantConfiguration(
extension.defaultConfig, getDefaultConfigData().androidTestSourceSet,
testData.buildType, null,
VariantConfiguration.Type.TEST)
testVariantConfig.addProductFlavor(productFlavorData.productFlavor,
productFlavorData.androidTestSourceSet)
def testVariant = new TestAppVariant(testVariantConfig, testedVariant.config)
createTestTasks(testVariant, testedVariant)
}
/**
* Creates build tasks for a given variant.
* @param variantConfig
* @param assembleTask an optional assembleTask to be used. If null, a new one is created in the
* returned ProductAppVariant instance.
* @param isTestApk whether this apk is needed for running tests
* @return
*/
private ProductionAppVariant addVariant(VariantConfiguration variantConfig, Task assembleTask,
boolean isTestApk) {
def variant = new ProductionAppVariant(variantConfig)
// Add a task to process the manifest(s)
ProcessManifest processManifestTask = createProcessManifestTask(variant)
// Add a task to crunch resource files
def crunchTask = createCrunchResTask(variant)
// Add a task to create the BuildConfig class
def generateBuildConfigTask = createBuildConfigTask(variant, null)
// Add a task to generate resource source files
def processResources = createProcessResTask(variant, processManifestTask, crunchTask)
// Add a compile task
createCompileTask(variant, null/*testedVariant*/, processResources, generateBuildConfigTask)
Task returnTask = addPackageTasks(variant, assembleTask, isTestApk)
if (returnTask != null) {
variant.assembleTask = returnTask
}
return variant;
}
@Override
String getTarget() {
return extension.target;
}
protected String getManifestOutDir() {
return "manifests"
}
}