97b74bf90891e1b7cb2000d85f1f854bbfc185d2
[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
17 package com.android.build.gradle
18
19 import com.android.build.gradle.internal.BuildTypeData
20 import com.android.build.gradle.internal.DefaultBuildVariant
21 import com.android.build.gradle.internal.ProductFlavorData
22 import com.android.build.gradle.internal.ProductionAppVariant
23 import com.android.build.gradle.internal.TestAppVariant
24 import com.android.build.gradle.internal.dependency.ConfigurationDependencies
25 import com.android.build.gradle.internal.dsl.BuildTypeDsl
26 import com.android.build.gradle.internal.dsl.BuildTypeFactory
27 import com.android.build.gradle.internal.dsl.GroupableProductFlavor
28 import com.android.build.gradle.internal.dsl.GroupableProductFlavorFactory
29 import com.android.build.gradle.internal.dsl.SigningConfigDsl
30 import com.android.build.gradle.internal.dsl.SigningConfigFactory
31 import com.android.build.gradle.internal.tasks.AndroidReportTask
32 import com.android.build.gradle.internal.tasks.AndroidTestTask
33 import com.android.build.gradle.internal.test.PluginHolder
34 import com.android.build.gradle.internal.test.report.ReportType
35 import com.android.builder.AndroidDependency
36 import com.android.builder.BuildType
37 import com.android.builder.BuilderConstants
38 import com.android.builder.JarDependency
39 import com.android.builder.VariantConfiguration
40 import com.android.builder.signing.SigningConfig
41 import com.google.common.collect.ArrayListMultimap
42 import com.google.common.collect.ListMultimap
43 import org.gradle.api.Project
44 import org.gradle.api.Task
45 import org.gradle.api.internal.project.ProjectInternal
46 import org.gradle.api.plugins.BasePlugin
47 import org.gradle.api.plugins.JavaBasePlugin
48 import org.gradle.internal.reflect.Instantiator
49
50 import javax.inject.Inject
51
52 /**
53  * Gradle plugin class for 'application' projects.
54  */
55 class AppPlugin extends com.android.build.gradle.BasePlugin implements org.gradle.api.Plugin<Project> {
56     static PluginHolder pluginHolder;
57
58     final Map<String, BuildTypeData> buildTypes = [:]
59     final Map<String, ProductFlavorData<GroupableProductFlavor>> productFlavors = [:]
60     final Map<String, SigningConfig> signingConfigs = [:]
61
62     AppExtension extension
63     AndroidReportTask testTask
64
65     @Inject
66     public AppPlugin(Instantiator instantiator) {
67         super(instantiator)
68     }
69
70     @Override
71     void apply(Project project) {
72         super.apply(project)
73
74         // This is for testing.
75         if (pluginHolder != null) {
76             pluginHolder.plugin = this;
77         }
78
79         def buildTypeContainer = project.container(BuildType, new BuildTypeFactory(instantiator))
80         def productFlavorContainer = project.container(GroupableProductFlavor,
81                 new GroupableProductFlavorFactory(instantiator))
82         def signingConfigContainer = project.container(SigningConfig,
83                 new SigningConfigFactory(instantiator))
84
85         extension = project.extensions.create('android', AppExtension,
86                 this, (ProjectInternal) project, instantiator,
87                 buildTypeContainer, productFlavorContainer, signingConfigContainer)
88         setDefaultConfig(extension.defaultConfig, extension.sourceSetsContainer)
89
90         // map the whenObjectAdded callbacks on the containers.
91         signingConfigContainer.whenObjectAdded { SigningConfig signingConfig ->
92             SigningConfigDsl signingConfigDsl = (SigningConfigDsl) signingConfig
93             signingConfigs[signingConfigDsl.name] = signingConfig
94         }
95
96         buildTypeContainer.whenObjectAdded { BuildType buildType ->
97             ((BuildTypeDsl)buildType).init(signingConfigContainer.getByName(BuilderConstants.DEBUG))
98             addBuildType(buildType)
99         }
100
101         productFlavorContainer.whenObjectAdded { GroupableProductFlavor productFlavor ->
102             addProductFlavor(productFlavor)
103         }
104
105         // create default Objects, signingConfig first as its used by the BuildTypes.
106         signingConfigContainer.create(BuilderConstants.DEBUG)
107         buildTypeContainer.create(BuilderConstants.DEBUG)
108         buildTypeContainer.create(BuilderConstants.RELEASE)
109
110         // map whenObjectRemoved on the containers to throw an exception.
111         signingConfigContainer.whenObjectRemoved {
112             throw new UnsupportedOperationException("Removing signingConfigs is not supported.")
113         }
114         buildTypeContainer.whenObjectRemoved {
115             throw new UnsupportedOperationException("Removing build types is not supported.")
116         }
117         productFlavorContainer.whenObjectRemoved {
118             throw new UnsupportedOperationException("Removing product flavors is not supported.")
119         }
120     }
121
122     /**
123      * Adds new BuildType, creating a BuildTypeData, and the associated source set,
124      * and adding it to the map.
125      * @param buildType the build type.
126      */
127     private void addBuildType(BuildType buildType) {
128         String name = buildType.name
129         if (name.startsWith(BuilderConstants.TEST)) {
130             throw new RuntimeException("BuildType names cannot start with 'test'")
131         }
132         if (productFlavors.containsKey(name)) {
133             throw new RuntimeException("BuildType names cannot collide with ProductFlavor names")
134         }
135
136         def sourceSet = extension.sourceSetsContainer.create(name)
137
138         BuildTypeData buildTypeData = new BuildTypeData(buildType, sourceSet, project)
139         project.tasks.assemble.dependsOn buildTypeData.assembleTask
140
141         buildTypes[name] = buildTypeData
142     }
143
144     /**
145      * Adds a new ProductFlavor, creating a ProductFlavorData and associated source sets,
146      * and adding it to the map.
147      *
148      * @param productFlavor the product flavor
149      */
150     private void addProductFlavor(GroupableProductFlavor productFlavor) {
151         if (productFlavor.name.startsWith(BuilderConstants.TEST)) {
152             throw new RuntimeException("ProductFlavor names cannot start with 'test'")
153         }
154         if (buildTypes.containsKey(productFlavor.name)) {
155             throw new RuntimeException("ProductFlavor names cannot collide with BuildType names")
156         }
157
158         def mainSourceSet = extension.sourceSetsContainer.create(productFlavor.name)
159         String testName = "${BuilderConstants.TEST}${productFlavor.name.capitalize()}"
160         def testSourceSet = extension.sourceSetsContainer.create(testName)
161
162         ProductFlavorData<GroupableProductFlavor> productFlavorData =
163                 new ProductFlavorData<GroupableProductFlavor>(
164                         productFlavor, mainSourceSet, testSourceSet, project)
165
166         productFlavors[productFlavor.name] = productFlavorData
167     }
168
169     /**
170      * Task creation entry point.
171      */
172     @Override
173     protected void doCreateAndroidTasks() {
174         // resolve dependencies for all config
175         List<ConfigurationDependencies> dependencies = []
176         dependencies.addAll(buildTypes.values())
177         dependencies.addAll(productFlavors.values())
178         resolveDependencies(dependencies)
179
180         // now create the tasks.
181         if (productFlavors.isEmpty()) {
182             createTasksForDefaultBuild()
183         } else {
184             // there'll be more than one test app, so we need a top level assembleTest
185             assembleTest = project.tasks.add("assembleTest")
186             assembleTest.group = BasePlugin.BUILD_GROUP
187             assembleTest.description = "Assembles all the Test applications"
188
189             // same for the test task
190             testTask = project.tasks.add("instrumentationTest", AndroidReportTask)
191             testTask.group = JavaBasePlugin.VERIFICATION_GROUP
192             testTask.description = "Installs and runs instrumentation tests for all flavors"
193             testTask.reportType = ReportType.MULTI_FLAVOR
194             deviceCheck.dependsOn testTask
195
196             testTask.conventionMapping.resultsDir = {
197                 String rootLocation = extension.testOptions.resultsDir != null ?
198                     extension.testOptions.resultsDir : "$project.buildDir/test-results"
199
200                 project.file("$rootLocation/all")
201             }
202             testTask.conventionMapping.reportsDir = {
203                 String rootLocation = extension.testOptions.reportDir != null ?
204                     extension.testOptions.reportDir : "$project.buildDir/reports/tests"
205
206                 project.file("$rootLocation/all")
207             }
208
209             // check whether we have multi flavor builds
210             if (extension.flavorGroupList == null || extension.flavorGroupList.size() < 2) {
211                 productFlavors.values().each { ProductFlavorData productFlavorData ->
212                     createTasksForFlavoredBuild(productFlavorData)
213                 }
214             } else {
215                 // need to group the flavor per group.
216                 // First a map of group -> list(ProductFlavor)
217                 ArrayListMultimap<String, ProductFlavorData<GroupableProductFlavor>> map = ArrayListMultimap.create();
218                 productFlavors.values().each { ProductFlavorData<GroupableProductFlavor> productFlavorData ->
219                     def flavor = productFlavorData.productFlavor
220                     if (flavor.flavorGroup == null) {
221                         throw new RuntimeException(
222                                 "Flavor ${flavor.name} has no flavor group.")
223                     }
224                     if (!extension.flavorGroupList.contains(flavor.flavorGroup)) {
225                         throw new RuntimeException(
226                                 "Flavor ${flavor.name} has unknown group ${flavor.flavorGroup}.")
227                     }
228
229                     map.put(flavor.flavorGroup, productFlavorData)
230                 }
231
232                 // now we use the flavor groups to generate an ordered array of flavor to use
233                 ProductFlavorData[] array = new ProductFlavorData[extension.flavorGroupList.size()]
234                 createTasksForMultiFlavoredBuilds(array, 0, map)
235             }
236         }
237
238         // If gradle is launched with --continue, we want to run all tests and generate an
239         // aggregate report (to help with the fact that we may have several build variants).
240         // To do that, the "test" task (which does the aggregation) must always run even if
241         // one of its dependent task (all the testFlavor tasks) fails, so we make them ignore their
242         // error.
243         // We cannot do that always: in case the test task is not going to run, we do want the
244         // individual testFlavor tasks to fail.
245         if (testTask != null && project.gradle.startParameter.continueOnFailure) {
246             project.gradle.taskGraph.whenReady { taskGraph ->
247                 if (taskGraph.hasTask(testTask)) {
248                     testTask.setWillRun()
249                 }
250             }
251         }
252     }
253
254     /**
255      * Creates the tasks for multi-flavor builds.
256      *
257      * This recursively fills the array of ProductFlavorData (in the order defined
258      * in extension.flavorGroupList), creating all possible combination.
259      *
260      * @param datas the arrays to fill
261      * @param i the current index to fill
262      * @param map the map of group -> list(ProductFlavor)
263      * @return
264      */
265     private createTasksForMultiFlavoredBuilds(ProductFlavorData[] datas, int i,
266                                               ListMultimap<String, ? extends ProductFlavorData> map) {
267         if (i == datas.length) {
268             createTasksForFlavoredBuild(datas)
269             return
270         }
271
272         // fill the array at the current index.
273         // get the group name that matches the index we are filling.
274         def group = extension.flavorGroupList.get(i)
275
276         // from our map, get all the possible flavors in that group.
277         def flavorList = map.get(group)
278
279         // loop on all the flavors to add them to the current index and recursively fill the next
280         // indices.
281         for (ProductFlavorData flavor : flavorList) {
282             datas[i] = flavor
283             createTasksForMultiFlavoredBuilds(datas, (int) i + 1, map)
284         }
285     }
286
287     /**
288      * Creates Tasks for non-flavored build. This means assembleDebug, assembleRelease, and other
289      * assemble<Type> are directly building the <type> build instead of all build of the given
290      * <type>.
291      */
292     private createTasksForDefaultBuild() {
293
294         BuildTypeData testData = buildTypes[extension.testBuildType]
295         if (testData == null) {
296             throw new RuntimeException("Test Build Type '$extension.testBuildType' does not exist.")
297         }
298
299         ProductionAppVariant testedVariant = null
300
301         ProductFlavorData defaultConfigData = getDefaultConfigData();
302
303         for (BuildTypeData buildTypeData : buildTypes.values()) {
304             List<ConfigurationDependencies> configDependencies = []
305             configDependencies.add(defaultConfigData)
306             configDependencies.add(buildTypeData)
307
308             // list of dependency to set on the variantConfig
309             List<JarDependency> jars = []
310             jars.addAll(defaultConfigData.jars)
311             jars.addAll(buildTypeData.jars)
312
313             // the order of the libraries is important. In descending order:
314             // build types, flavors, defaultConfig.
315             List<AndroidDependency> libs = []
316             libs.addAll(buildTypeData.libraries)
317             // no flavors, just add the default config
318             libs.addAll(defaultConfigData.libraries)
319
320             def variantConfig = new VariantConfiguration(
321                     defaultConfigData.productFlavor, defaultConfigData.sourceSet,
322                     buildTypeData.buildType, buildTypeData.sourceSet)
323
324             variantConfig.setJarDependencies(jars)
325             variantConfig.setAndroidDependencies(libs)
326
327             ProductionAppVariant productionAppVariant = addVariant(variantConfig,
328                     buildTypeData.assembleTask, configDependencies)
329             variants.add(productionAppVariant)
330
331             if (buildTypeData == testData) {
332                 testedVariant = productionAppVariant
333             } else {
334                 // add this non-tested variant to the list
335                 DefaultBuildVariant buildVariant = instantiator.newInstance(
336                         DefaultBuildVariant.class, productionAppVariant)
337                 extension.buildVariants.add(buildVariant)
338             }
339         }
340
341         assert testedVariant != null
342
343         def testVariantConfig = new VariantConfiguration(
344                 defaultConfigData.productFlavor, defaultConfigData.testSourceSet,
345                 testData.buildType, null,
346                 VariantConfiguration.Type.TEST, testedVariant.config)
347
348         List<ConfigurationDependencies> testConfigDependencies = []
349         testConfigDependencies.add(defaultConfigData.testConfigDependencies)
350
351         // list of dependency to set on the variantConfig
352         List<JarDependency> testJars = []
353         testJars.addAll(defaultConfigData.testConfigDependencies.jars)
354         List<AndroidDependency> testLibs = []
355         testLibs.addAll(defaultConfigData.testConfigDependencies.libraries)
356
357         testVariantConfig.setJarDependencies(testJars)
358         testVariantConfig.setAndroidDependencies(testLibs)
359
360         def testVariant = new TestAppVariant(testVariantConfig)
361         variants.add(testVariant)
362         createTestTasks(testVariant, testedVariant, testConfigDependencies, true /*mainTestTask*/)
363
364         // add the test and tested variants to the list
365         DefaultBuildVariant testedBuildVariant = instantiator.newInstance(
366                 DefaultBuildVariant.class, testVariant)
367         extension.testBuildVariants.add(testedBuildVariant)
368         DefaultBuildVariant buildVariant = instantiator.newInstance(
369                 DefaultBuildVariant.class, testedVariant, testedBuildVariant)
370         extension.buildVariants.add(buildVariant)
371     }
372
373     /**
374      * Creates Task for a given flavor. This will create tasks for all build types for the given
375      * flavor.
376      * @param flavorDataList the flavor(s) to build.
377      */
378     private createTasksForFlavoredBuild(ProductFlavorData... flavorDataList) {
379
380         BuildTypeData testData = buildTypes[extension.testBuildType]
381         if (testData == null) {
382             throw new RuntimeException("Test Build Type '$extension.testBuildType' does not exist.")
383         }
384
385         ProductionAppVariant testedVariant = null
386
387         // assembleTask for this flavor(group)
388         def assembleTask
389
390         for (BuildTypeData buildTypeData : buildTypes.values()) {
391             List<ConfigurationDependencies> configDependencies = []
392             configDependencies.add(defaultConfigData)
393             configDependencies.add(buildTypeData)
394
395             // list of dependency to set on the variantConfig
396             List<JarDependency> jars = []
397             jars.addAll(defaultConfigData.jars)
398             jars.addAll(buildTypeData.jars)
399
400             // the order of the libraries is important. In descending order:
401             // build types, flavors, defaultConfig.
402             List<AndroidDependency> libs = []
403             libs.addAll(buildTypeData.libraries)
404
405             def variantConfig = new VariantConfiguration(
406                     extension.defaultConfig, getDefaultConfigData().sourceSet,
407                     buildTypeData.buildType, buildTypeData.sourceSet)
408
409             for (ProductFlavorData data : flavorDataList) {
410                 variantConfig.addProductFlavor(data.productFlavor, data.sourceSet)
411                 jars.addAll(data.jars)
412                 libs.addAll(data.libraries)
413                 configDependencies.add(data)
414             }
415
416             // now add the defaultConfig
417             libs.addAll(defaultConfigData.libraries)
418
419             variantConfig.setJarDependencies(jars)
420             variantConfig.setAndroidDependencies(libs)
421
422             ProductionAppVariant productionAppVariant = addVariant(variantConfig, null,
423                     configDependencies)
424             variants.add(productionAppVariant)
425
426             buildTypeData.assembleTask.dependsOn productionAppVariant.assembleTask
427
428             if (assembleTask == null) {
429                 // create the task based on the name of the flavors.
430                 assembleTask = createAssembleTask(flavorDataList)
431                 project.tasks.assemble.dependsOn assembleTask
432             }
433             assembleTask.dependsOn productionAppVariant.assembleTask
434
435             if (buildTypeData == testData) {
436                 testedVariant = productionAppVariant
437             } else {
438                 // add this non-tested variant to the list
439                 DefaultBuildVariant buildVariant = instantiator.newInstance(
440                         DefaultBuildVariant.class, productionAppVariant)
441                 extension.buildVariants.add(buildVariant)
442             }
443         }
444
445         assert testedVariant != null
446
447         def testVariantConfig = new VariantConfiguration(
448                 extension.defaultConfig, getDefaultConfigData().testSourceSet,
449                 testData.buildType, null,
450                 VariantConfiguration.Type.TEST, testedVariant.config)
451
452         List<ConfigurationDependencies> testConfigDependencies = []
453         testConfigDependencies.add(defaultConfigData.testConfigDependencies)
454
455         // list of dependency to set on the variantConfig
456         List<JarDependency> testJars = []
457         testJars.addAll(defaultConfigData.testConfigDependencies.jars)
458
459         // the order of the libraries is important. In descending order:
460         // flavors, defaultConfig.
461         List<AndroidDependency> testLibs = []
462
463         for (ProductFlavorData data : flavorDataList) {
464             testVariantConfig.addProductFlavor(data.productFlavor, data.testSourceSet)
465             testJars.addAll(data.testConfigDependencies.jars)
466             testLibs.addAll(data.testConfigDependencies.libraries)
467         }
468
469         // now add the default config
470         testLibs.addAll(defaultConfigData.testConfigDependencies.libraries)
471
472         testVariantConfig.setJarDependencies(testJars)
473         testVariantConfig.setAndroidDependencies(testLibs)
474
475         def testVariant = new TestAppVariant(testVariantConfig)
476         variants.add(testVariant)
477         AndroidTestTask testFlavorTask = createTestTasks(testVariant, testedVariant,
478                 testConfigDependencies, false /*mainTestTask*/)
479
480         testTask.addTask(testFlavorTask)
481
482         // add the test and tested variants to the list
483         DefaultBuildVariant testedBuildVariant = instantiator.newInstance(
484                 DefaultBuildVariant.class, testVariant)
485         extension.testBuildVariants.add(testedBuildVariant)
486         DefaultBuildVariant buildVariant = instantiator.newInstance(
487                 DefaultBuildVariant.class, testedVariant, testedBuildVariant)
488         extension.buildVariants.add(buildVariant)
489
490     }
491
492     private Task createAssembleTask(ProductFlavorData[] flavorDataList) {
493         def name = ProductFlavorData.getFlavoredName(flavorDataList, true)
494
495         def assembleTask = project.tasks.add("assemble${name}")
496         assembleTask.description = "Assembles all builds for flavor ${name}"
497         assembleTask.group = "Build"
498
499         return assembleTask
500     }
501
502     /**
503      * Creates build tasks for a given variant.
504      * @param variantConfig
505      * @param assembleTask an optional assembleTask to be used. If null, a new one is created.
506      * @return
507      */
508     private ProductionAppVariant addVariant(VariantConfiguration variantConfig, Task assembleTask,
509                                             List<ConfigurationDependencies> configDependencies) {
510
511         def variant = new ProductionAppVariant(variantConfig)
512
513         createPrepareDependenciesTask(variant, configDependencies)
514
515         // Add a task to process the manifest(s)
516         createProcessManifestTask(variant, "manifests")
517
518         // Add a task to compile renderscript files.
519         createRenderscriptTask(variant)
520
521         // Add a task to merge the resource folders
522         createMergeResourcesTask(variant, true /*process9Patch*/)
523
524         // Add a task to merge the asset folders
525         createMergeAssetsTask(variant, null /*default location*/)
526
527         // Add a task to create the BuildConfig class
528         createBuildConfigTask(variant)
529
530         // Add a task to generate resource source files
531         createProcessResTask(variant)
532
533         // Add a task to process the java resources
534         createProcessJavaResTask(variant)
535
536         createAidlTask(variant)
537
538         // Add a compile task
539         createCompileTask(variant, null/*testedVariant*/)
540
541         addPackageTasks(variant, assembleTask)
542
543         return variant;
544     }
545
546     @Override
547     protected String getTarget() {
548         return extension.compileSdkVersion;
549     }
550 }