Support compile config for flavors and build types.
[android/platform/tools/build.git] / gradle / src / main / groovy / com / android / build / gradle / AppPlugin.groovy
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.build.gradle
17
18 import com.android.build.gradle.internal.BuildTypeData
19 import com.android.build.gradle.internal.BuildTypeDsl
20 import com.android.build.gradle.internal.ConfigurationDependencies
21 import com.android.build.gradle.internal.ProductFlavorData
22 import com.android.build.gradle.internal.ProductFlavorDsl
23 import com.android.build.gradle.internal.ProductionAppVariant
24 import com.android.build.gradle.internal.TestAppVariant
25 import com.android.builder.AndroidDependency
26 import com.android.builder.BuildType
27 import com.android.builder.JarDependency
28 import com.android.builder.VariantConfiguration
29 import com.google.common.collect.ArrayListMultimap
30 import com.google.common.collect.ListMultimap
31 import org.gradle.api.Project
32 import org.gradle.api.Task
33 import org.gradle.api.internal.project.ProjectInternal
34 import org.gradle.api.plugins.BasePlugin
35
36 class AppPlugin extends com.android.build.gradle.BasePlugin implements org.gradle.api.Plugin<Project> {
37     private final Map<String, BuildTypeData> buildTypes = [:]
38     private final Map<String, ProductFlavorData> productFlavors = [:]
39
40     private AppExtension extension
41
42     @Override
43     void apply(Project project) {
44         super.apply(project)
45
46         def buildTypeContainer = project.container(BuildTypeDsl)
47         def productFlavorContainer = project.container(ProductFlavorDsl)
48
49         extension = project.extensions.create('android', AppExtension,
50                 (ProjectInternal) project, buildTypeContainer, productFlavorContainer)
51         setDefaultConfig(extension.defaultConfig, extension.sourceSetsContainer)
52
53         buildTypeContainer.whenObjectAdded { BuildType buildType ->
54             addBuildType(buildType)
55         }
56         buildTypeContainer.whenObjectRemoved {
57             throw new UnsupportedOperationException("Removing build types is not implemented yet.")
58         }
59
60         buildTypeContainer.create(BuildType.DEBUG)
61         buildTypeContainer.create(BuildType.RELEASE)
62
63         productFlavorContainer.whenObjectAdded { ProductFlavorDsl productFlavor ->
64             addProductFlavor(productFlavor)
65         }
66
67         productFlavorContainer.whenObjectRemoved {
68             throw new UnsupportedOperationException(
69                     "Removing product flavors is not implemented yet.")
70         }
71
72         project.afterEvaluate {
73             createAndroidTasks()
74         }
75     }
76
77     private void addBuildType(BuildType buildType) {
78         if (buildType.name.startsWith("test")) {
79             throw new RuntimeException("BuildType names cannot start with 'test'")
80         }
81         if (productFlavors.containsKey(buildType.name)) {
82             throw new RuntimeException("BuildType names cannot collide with ProductFlavor names")
83         }
84
85         def sourceSet = extension.sourceSetsContainer.create(buildType.name)
86
87         BuildTypeData buildTypeData = new BuildTypeData(buildType, sourceSet, project)
88         project.tasks.assemble.dependsOn buildTypeData.assembleTask
89
90         buildTypes[buildType.name] = buildTypeData
91     }
92
93     private void addProductFlavor(ProductFlavorDsl productFlavor) {
94         if (productFlavor.name.startsWith("test")) {
95             throw new RuntimeException("ProductFlavor names cannot start with 'test'")
96         }
97         if (buildTypes.containsKey(productFlavor.name)) {
98             throw new RuntimeException("ProductFlavor names cannot collide with BuildType names")
99         }
100
101         def mainSourceSet = extension.sourceSetsContainer.create(productFlavor.name)
102         String testName = "test${productFlavor.name.capitalize()}"
103         def testSourceSet = extension.sourceSetsContainer.create(testName)
104
105         ProductFlavorData productFlavorData = new ProductFlavorData(
106                 productFlavor, mainSourceSet, testSourceSet, project)
107
108         productFlavors[productFlavor.name] = productFlavorData
109     }
110
111     private void createAndroidTasks() {
112         // resolve dependencies for all config
113         List<ConfigurationDependencies> dependencies = []
114         dependencies.addAll(buildTypes.values())
115         dependencies.addAll(productFlavors.values())
116         resolveDependencies(dependencies)
117
118         // now create the tasks.
119         if (productFlavors.isEmpty()) {
120             createTasksForDefaultBuild()
121         } else {
122             // there'll be more than one test app, so we need a top level assembleTest
123             assembleTest = project.tasks.add("assembleTest")
124             assembleTest.group = BasePlugin.BUILD_GROUP
125             assembleTest.description = "Assembles all the Test applications"
126
127             // check whether we have multi flavor builds
128             if (extension.flavorGroupList == null || extension.flavorGroupList.size() < 2) {
129                 productFlavors.values().each { ProductFlavorData productFlavorData ->
130                     createTasksForFlavoredBuild(productFlavorData)
131                 }
132             } else {
133                 // need to group the flavor per group.
134                 // First a map of group -> list(ProductFlavor)
135                 ArrayListMultimap<String, ProductFlavorData> map = ArrayListMultimap.create();
136                 productFlavors.values().each { ProductFlavorData productFlavorData ->
137                     def flavor = productFlavorData.productFlavor
138                     if (flavor.flavorGroup == null) {
139                         throw new RuntimeException(
140                                 "Flavor ${flavor.name} has no flavor group.")
141                     }
142                     if (!extension.flavorGroupList.contains(flavor.flavorGroup)) {
143                         throw new RuntimeException(
144                                 "Flavor ${flavor.name} has unknown group ${flavor.flavorGroup}.")
145                     }
146
147                     map.put(flavor.flavorGroup, productFlavorData)
148                 }
149
150                 // now we use the flavor groups to generate an ordered array of flavor to use
151                 ProductFlavorData[] array = new ProductFlavorData[extension.flavorGroupList.size()]
152                 createTasksForMultiFlavoredBuilds(array, 0, map)
153             }
154         }
155
156         createDependencyReportTask()
157     }
158
159     private createTasksForMultiFlavoredBuilds(ProductFlavorData[] datas, int i,
160                                               ListMultimap<String, ProductFlavorData> map) {
161         if (i == datas.length) {
162             createTasksForFlavoredBuild(datas)
163             return
164         }
165
166         // fill the array at the current index
167         def group = extension.flavorGroupList.get(i)
168         def flavorList = map.get(group)
169         for (ProductFlavorData flavor : flavorList) {
170            datas[i] = flavor
171             createTasksForMultiFlavoredBuilds(datas, i+1, map)
172         }
173     }
174
175     /**
176      * Creates Tasks for non-flavored build. This means assembleDebug, assembleRelease, and other
177      * assemble<Type> are directly building the <type> build instead of all build of the given
178      * <type>.
179      */
180     private createTasksForDefaultBuild() {
181
182         BuildTypeData testData = buildTypes[extension.testBuildType]
183         if (testData == null) {
184             throw new RuntimeException("Test Build Type '$extension.testBuildType' does not exist.")
185         }
186
187         ProductionAppVariant testedVariant = null
188
189         ProductFlavorData defaultConfigData = getDefaultConfigData();
190
191         for (BuildTypeData buildTypeData : buildTypes.values()) {
192             List<ConfigurationDependencies> configDependencies = []
193             configDependencies.add(defaultConfigData)
194             configDependencies.add(buildTypeData)
195
196             // list of dependency to set on the variantConfig
197             List<JarDependency> jars = []
198             jars.addAll(defaultConfigData.jars)
199             jars.addAll(buildTypeData.jars)
200
201             // the order of the libraries is important. In descending order:
202             // build types, flavors, defaultConfig.
203             List<AndroidDependency> libs = []
204             libs.addAll(buildTypeData.libraries)
205             // no flavors, just add the default config
206             libs.addAll(defaultConfigData.libraries)
207
208             def variantConfig = new VariantConfiguration(
209                     defaultConfigData.productFlavor, defaultConfigData.sourceSet,
210                     buildTypeData.buildType, buildTypeData.sourceSet)
211
212             variantConfig.setJarDependencies(jars)
213             variantConfig.setAndroidDependencies(libs)
214
215             ProductionAppVariant productionAppVariant = addVariant(variantConfig,
216                     buildTypeData.assembleTask, configDependencies)
217             variants.add(productionAppVariant)
218
219             if (buildTypeData == testData) {
220                 testedVariant = productionAppVariant
221             }
222         }
223
224         assert testedVariant != null
225
226         def testVariantConfig = new VariantConfiguration(
227                 defaultConfigData.productFlavor, defaultConfigData.testSourceSet,
228                 testData.buildType, null,
229                 VariantConfiguration.Type.TEST, testedVariant.config)
230
231         List<ConfigurationDependencies> testConfigDependencies = []
232         testConfigDependencies.add(defaultConfigData.testConfigDependencies)
233
234         // list of dependency to set on the variantConfig
235         List<JarDependency> testJars = []
236         testJars.addAll(defaultConfigData.testConfigDependencies.jars)
237         List<AndroidDependency> testLibs = []
238         testLibs.addAll(defaultConfigData.testConfigDependencies.libraries)
239
240         testVariantConfig.setJarDependencies(testJars)
241         testVariantConfig.setAndroidDependencies(testLibs)
242
243         def testVariant = new TestAppVariant(testVariantConfig)
244         variants.add(testVariant)
245         createTestTasks(testVariant, testedVariant, testConfigDependencies)
246     }
247
248     /**
249      * Creates Task for a given flavor. This will create tasks for all build types for the given
250      * flavor.
251      * @param flavorDataList the flavor(s) to build.
252      */
253     private createTasksForFlavoredBuild(ProductFlavorData... flavorDataList) {
254
255         BuildTypeData testData = buildTypes[extension.testBuildType]
256         if (testData == null) {
257             throw new RuntimeException("Test Build Type '$extension.testBuildType' does not exist.")
258         }
259
260         ProductionAppVariant testedVariant = null
261
262         // assembleTask for this flavor(group)
263         def assembleTask
264
265         for (BuildTypeData buildTypeData : buildTypes.values()) {
266             List<ConfigurationDependencies> configDependencies = []
267             configDependencies.add(defaultConfigData)
268             configDependencies.add(buildTypeData)
269
270             // list of dependency to set on the variantConfig
271             List<JarDependency> jars = []
272             jars.addAll(defaultConfigData.jars)
273             jars.addAll(buildTypeData.jars)
274
275             // the order of the libraries is important. In descending order:
276             // build types, flavors, defaultConfig.
277             List<AndroidDependency> libs = []
278             libs.addAll(buildTypeData.libraries)
279
280             def variantConfig = new VariantConfiguration(
281                     extension.defaultConfig, getDefaultConfigData().sourceSet,
282                     buildTypeData.buildType, buildTypeData.sourceSet)
283
284             for (ProductFlavorData data : flavorDataList) {
285                 variantConfig.addProductFlavor(data.productFlavor, data.sourceSet)
286                 jars.addAll(data.jars)
287                 libs.addAll(data.libraries)
288                 configDependencies.add(data)
289             }
290
291             // now add the defaultConfig
292             libs.addAll(defaultConfigData.libraries)
293
294             variantConfig.setJarDependencies(jars)
295             variantConfig.setAndroidDependencies(libs)
296
297             ProductionAppVariant productionAppVariant = addVariant(variantConfig, null,
298                     configDependencies)
299             variants.add(productionAppVariant)
300
301             buildTypeData.assembleTask.dependsOn productionAppVariant.assembleTask
302
303             if (assembleTask == null) {
304                 // create the task based on the name of the flavors.
305                 assembleTask = createAssembleTask(flavorDataList)
306                 project.tasks.assemble.dependsOn assembleTask
307             }
308             assembleTask.dependsOn productionAppVariant.assembleTask
309
310             if (buildTypeData == testData) {
311                 testedVariant = productionAppVariant
312             }
313         }
314
315         assert testedVariant != null
316
317         def testVariantConfig = new VariantConfiguration(
318                 extension.defaultConfig, getDefaultConfigData().testSourceSet,
319                 testData.buildType, null,
320                 VariantConfiguration.Type.TEST, testedVariant.config)
321
322         List<ConfigurationDependencies> testConfigDependencies = []
323         testConfigDependencies.add(defaultConfigData.testConfigDependencies)
324
325         // list of dependency to set on the variantConfig
326         List<JarDependency> testJars = []
327         testJars.addAll(defaultConfigData.testConfigDependencies.jars)
328
329         // the order of the libraries is important. In descending order:
330         // flavors, defaultConfig.
331         List<AndroidDependency> testLibs = []
332
333         for (ProductFlavorData data : flavorDataList) {
334             testVariantConfig.addProductFlavor(data.productFlavor, data.testSourceSet)
335             testJars.addAll(data.testConfigDependencies.jars)
336             testLibs.addAll(data.testConfigDependencies.libraries)
337         }
338
339         // now add the default config
340         testLibs.addAll(defaultConfigData.testConfigDependencies.libraries)
341
342         testVariantConfig.setJarDependencies(testJars)
343         testVariantConfig.setAndroidDependencies(testLibs)
344
345         def testVariant = new TestAppVariant(testVariantConfig)
346         variants.add(testVariant)
347         createTestTasks(testVariant, testedVariant, testConfigDependencies)
348     }
349
350     private Task createAssembleTask(ProductFlavorData[] flavorDataList) {
351         def name = ProductFlavorData.getFlavoredName(flavorDataList, true)
352
353         def assembleTask = project.tasks.add("assemble${name}")
354         assembleTask.description = "Assembles all builds for flavor ${name}"
355         assembleTask.group = "Build"
356
357         return assembleTask
358     }
359
360     /**
361      * Creates build tasks for a given variant.
362      * @param variantConfig
363      * @param assembleTask an optional assembleTask to be used. If null, a new one is created.
364      * @return
365      */
366     private ProductionAppVariant addVariant(VariantConfiguration variantConfig, Task assembleTask,
367                                             List<ConfigurationDependencies> configDependencies) {
368
369         def variant = new ProductionAppVariant(variantConfig)
370
371         def prepareDependenciesTask = createPrepareDependenciesTask(variant, configDependencies)
372
373         // Add a task to process the manifest(s)
374         def processManifestTask = createProcessManifestTask(variant, "manifests")
375         // TODO - move this
376         processManifestTask.dependsOn prepareDependenciesTask
377
378         // Add a task to crunch resource files
379         def crunchTask = createCrunchResTask(variant)
380
381         // Add a task to create the BuildConfig class
382         def generateBuildConfigTask = createBuildConfigTask(variant, null)
383
384         // Add a task to generate resource source files
385         def processResources = createProcessResTask(variant, processManifestTask, crunchTask)
386
387         // Add a task to process the java resources
388         createProcessJavaResTask(variant)
389
390         def compileAidl = createAidlTask(variant)
391         // TODO - move this
392         compileAidl.dependsOn prepareDependenciesTask
393
394         // Add a compile task
395         createCompileTask(variant, null/*testedVariant*/, processResources, generateBuildConfigTask,
396                 compileAidl)
397
398         addPackageTasks(variant, assembleTask)
399
400         return variant;
401     }
402
403     @Override
404     String getTarget() {
405         return extension.target;
406     }
407 }