968e6551ba9ea40e98082b8ee876e13db9e1cb21
[android/platform/tools/build.git] / gradle / src / main / groovy / com / android / build / gradle / BasePlugin.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 import com.android.SdkConstants
18 import com.android.build.gradle.internal.ApplicationVariant
19 import com.android.build.gradle.internal.LoggerWrapper
20 import com.android.build.gradle.internal.ProductFlavorData
21 import com.android.build.gradle.internal.ProductionAppVariant
22 import com.android.build.gradle.internal.TestAppVariant
23 import com.android.build.gradle.internal.dependency.AndroidDependencyImpl
24 import com.android.build.gradle.internal.dependency.ConfigurationDependencies
25 import com.android.build.gradle.internal.dependency.DependencyChecker
26 import com.android.build.gradle.internal.dependency.ManifestDependencyImpl
27 import com.android.build.gradle.internal.dependency.SymbolFileProviderImpl
28 import com.android.build.gradle.internal.dsl.SigningConfigDsl
29 import com.android.build.gradle.internal.tasks.AndroidTestTask
30 import com.android.build.gradle.internal.tasks.DependencyReportTask
31 import com.android.build.gradle.internal.tasks.InstallTask
32 import com.android.build.gradle.internal.tasks.PrepareDependenciesTask
33 import com.android.build.gradle.internal.tasks.PrepareLibraryTask
34 import com.android.build.gradle.internal.tasks.SigningReportTask
35 import com.android.build.gradle.internal.tasks.TestFlavorTask
36 import com.android.build.gradle.internal.tasks.TestLibraryTask
37 import com.android.build.gradle.internal.tasks.UninstallTask
38 import com.android.build.gradle.internal.tasks.ValidateSigningTask
39 import com.android.build.gradle.tasks.AidlCompile
40 import com.android.build.gradle.tasks.Dex
41 import com.android.build.gradle.tasks.GenerateBuildConfig
42 import com.android.build.gradle.tasks.MergeAssets
43 import com.android.build.gradle.tasks.MergeResources
44 import com.android.build.gradle.tasks.PackageApplication
45 import com.android.build.gradle.tasks.ProcessAndroidResources
46 import com.android.build.gradle.tasks.ProcessAppManifest
47 import com.android.build.gradle.tasks.ProcessTestManifest
48 import com.android.build.gradle.tasks.RenderscriptCompile
49 import com.android.build.gradle.tasks.ZipAlign
50 import com.android.builder.AndroidBuilder
51 import com.android.builder.AndroidDependency
52 import com.android.builder.BuilderConstants
53 import com.android.builder.DefaultSdkParser
54 import com.android.builder.JarDependency
55 import com.android.builder.ManifestDependency
56 import com.android.builder.PlatformSdkParser
57 import com.android.builder.ProductFlavor
58 import com.android.builder.SdkParser
59 import com.android.builder.SourceProvider
60 import com.android.builder.VariantConfiguration
61 import com.android.builder.signing.SigningConfig
62 import com.android.utils.ILogger
63 import com.google.common.collect.ArrayListMultimap
64 import com.google.common.collect.Lists
65 import com.google.common.collect.Multimap
66 import org.gradle.api.GradleException
67 import org.gradle.api.NamedDomainObjectContainer
68 import org.gradle.api.Project
69 import org.gradle.api.Task
70 import org.gradle.api.artifacts.Configuration
71 import org.gradle.api.artifacts.ModuleVersionIdentifier
72 import org.gradle.api.artifacts.ProjectDependency
73 import org.gradle.api.artifacts.ResolvedArtifact
74 import org.gradle.api.artifacts.SelfResolvingDependency
75 import org.gradle.api.artifacts.result.ResolvedDependencyResult
76 import org.gradle.api.artifacts.result.ResolvedModuleVersionResult
77 import org.gradle.api.internal.plugins.ProcessResources
78 import org.gradle.api.logging.LogLevel
79 import org.gradle.api.plugins.JavaBasePlugin
80 import org.gradle.api.tasks.Copy
81 import org.gradle.api.tasks.compile.JavaCompile
82 import org.gradle.internal.reflect.Instantiator
83 import org.gradle.tooling.BuildException
84 import org.gradle.util.GUtil
85 /**
86  * Base class for all Android plugins
87  */
88 public abstract class BasePlugin {
89     public static final String[] GRADLE_SUPPORTED_VERSIONS = [ "1.3", "1.4" ]
90     public static final String GRADLE_MIN_VERSION = "1.3"
91
92     public static final String INSTALL_GROUP = "Install"
93
94     protected static File TEST_SDK_DIR;
95
96     protected Instantiator instantiator
97
98     private final Map<Object, AndroidBuilder> builders = [:]
99
100     final List<ApplicationVariant> variants = []
101     final Map<AndroidDependencyImpl, PrepareLibraryTask> prepareTaskMap = [:]
102     final Map<SigningConfig, ValidateSigningTask> validateSigningTaskMap = [:]
103
104     protected Project project
105     protected SdkParser androidSdkParser
106     private LoggerWrapper loggerWrapper
107
108     private boolean hasCreatedTasks = false
109
110     private ProductFlavorData<ProductFlavor> defaultConfigData
111     protected AndroidSourceSet mainSourceSet
112     protected AndroidSourceSet testSourceSet
113
114     protected Task uninstallAll
115     protected Task assembleTest
116     protected Task deviceCheck
117
118     protected abstract String getTarget()
119
120     protected BasePlugin(Instantiator instantiator) {
121         this.instantiator = instantiator
122     }
123
124     protected abstract BaseExtension getExtension()
125     protected abstract void doCreateAndroidTasks()
126
127     protected void apply(Project project) {
128         this.project = project
129
130         checkGradleVersion()
131
132         project.apply plugin: JavaBasePlugin
133
134         project.tasks.assemble.description =
135             "Assembles all variants of all applications and secondary packages."
136
137         uninstallAll = project.tasks.add("uninstallAll")
138         uninstallAll.description = "Uninstall all applications."
139         uninstallAll.group = INSTALL_GROUP
140
141         deviceCheck = project.tasks.add("deviceCheck")
142         deviceCheck.description = "Runs all checks that requires a connected device."
143         deviceCheck.group = JavaBasePlugin.VERIFICATION_GROUP
144
145         project.afterEvaluate {
146             createAndroidTasks()
147         }
148     }
149
150     private void checkGradleVersion() {
151         boolean foundMatch = false
152         for (String version : GRADLE_SUPPORTED_VERSIONS) {
153             if (project.getGradle().gradleVersion.startsWith(version)) {
154                 foundMatch = true
155                 break
156             }
157         }
158
159         if (!foundMatch) {
160             throw new BuildException(
161                     String.format(
162                             "Gradle version %s is required. Current version is %s",
163                             GRADLE_MIN_VERSION, project.getGradle().gradleVersion), null);
164
165         }
166     }
167
168     final void createAndroidTasks() {
169         findSdk(project)
170
171         if (hasCreatedTasks) {
172             return
173         }
174         hasCreatedTasks = true
175
176         doCreateAndroidTasks()
177         createReportTasks()
178     }
179
180     protected setDefaultConfig(ProductFlavor defaultConfig,
181                                NamedDomainObjectContainer<AndroidSourceSet> sourceSets) {
182         mainSourceSet = sourceSets.create(defaultConfig.name)
183         testSourceSet = sourceSets.create("test")
184
185         defaultConfigData = new ProductFlavorData<ProductFlavor>(defaultConfig, mainSourceSet,
186                 testSourceSet, project, ConfigurationDependencies.ConfigType.DEFAULT)
187     }
188
189     ProductFlavorData getDefaultConfigData() {
190         return defaultConfigData
191     }
192
193     SdkParser getSdkParser() {
194         return androidSdkParser;
195     }
196
197     ILogger getLogger() {
198         if (loggerWrapper == null) {
199             loggerWrapper = new LoggerWrapper(project.logger)
200         }
201
202         return loggerWrapper
203     }
204
205     boolean isVerbose() {
206         return project.logger.isEnabled(LogLevel.DEBUG)
207     }
208
209     AndroidBuilder getAndroidBuilder(ApplicationVariant variant) {
210         AndroidBuilder androidBuilder = builders.get(variant)
211
212         if (androidBuilder == null) {
213             androidBuilder = variant.createBuilder(this)
214             builders.put(variant, androidBuilder)
215         }
216
217         return androidBuilder
218     }
219
220     private void findSdk(Project project) {
221         // if already set through tests.
222         if (TEST_SDK_DIR != null) {
223             androidSdkParser = new DefaultSdkParser(TEST_SDK_DIR.absolutePath)
224             return
225         }
226
227         boolean defaultParser = true
228         File sdkDir = null
229
230         def rootDir = project.rootDir
231         def localProperties = new File(rootDir, SdkConstants.FN_LOCAL_PROPERTIES)
232         if (localProperties.exists()) {
233             Properties properties = new Properties()
234             localProperties.withInputStream { instr ->
235                 properties.load(instr)
236             }
237             def sdkDirProp = properties.getProperty('sdk.dir')
238
239             if (sdkDirProp != null) {
240                 sdkDir = new File(sdkDirProp)
241             } else {
242                 sdkDirProp = properties.getProperty('android.dir')
243                 if (sdkDirProp != null) {
244                     sdkDir = new File(rootDir, sdkDirProp)
245                     defaultParser = false
246                 } else {
247                     throw new RuntimeException(
248                             "No sdk.dir property defined in local.properties file.")
249                 }
250             }
251         } else {
252             def envVar = System.getenv("ANDROID_HOME")
253             if (envVar != null) {
254                 sdkDir = new File(envVar)
255             }
256         }
257
258         if (sdkDir == null) {
259             throw new RuntimeException(
260                     "SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.")
261         }
262
263         if (!sdkDir.directory) {
264             throw new RuntimeException(
265                     "The SDK directory '$sdkDir' specified in local.properties does not exist.")
266         }
267
268         if (defaultParser) {
269             androidSdkParser = new DefaultSdkParser(sdkDir.absolutePath)
270         } else {
271             androidSdkParser = new PlatformSdkParser(sdkDir.absolutePath)
272         }
273     }
274
275     protected String getRuntimeJars(ApplicationVariant variant) {
276         AndroidBuilder androidBuilder = getAndroidBuilder(variant)
277
278         return androidBuilder.runtimeClasspath.join(File.pathSeparator)
279     }
280
281     protected void createProcessManifestTask(ApplicationVariant variant,
282                                              String manifestOurDir) {
283         def processManifestTask = project.tasks.add("process${variant.name}Manifest",
284                 ProcessAppManifest)
285         variant.processManifestTask = processManifestTask
286         processManifestTask.dependsOn variant.prepareDependenciesTask
287
288         processManifestTask.plugin = this
289         processManifestTask.variant = variant
290
291         VariantConfiguration config = variant.config
292         ProductFlavor mergedFlavor = config.mergedFlavor
293
294         processManifestTask.conventionMapping.mainManifest = {
295             config.mainManifest
296         }
297         processManifestTask.conventionMapping.manifestOverlays = {
298             config.manifestOverlays
299         }
300         processManifestTask.conventionMapping.versionName = {
301             config.versionName
302         }
303         processManifestTask.conventionMapping.libraries = {
304             getManifestDependencies(config.directLibraries)
305         }
306         processManifestTask.conventionMapping.versionCode = {
307             mergedFlavor.versionCode
308         }
309         processManifestTask.conventionMapping.minSdkVersion = {
310             mergedFlavor.minSdkVersion
311         }
312         processManifestTask.conventionMapping.targetSdkVersion = {
313             mergedFlavor.targetSdkVersion
314         }
315         processManifestTask.conventionMapping.outManifest = {
316             project.file(
317                     "$project.buildDir/${manifestOurDir}/$variant.dirName/AndroidManifest.xml")
318         }
319     }
320
321     protected void createProcessTestManifestTask(ApplicationVariant variant,
322                                                  String manifestOurDir) {
323         def processTestManifestTask = project.tasks.add("process${variant.name}TestManifest",
324                 ProcessTestManifest)
325         variant.processManifestTask = processTestManifestTask
326         processTestManifestTask.dependsOn variant.prepareDependenciesTask
327
328         processTestManifestTask.plugin = this
329         processTestManifestTask.variant = variant
330
331         VariantConfiguration config = variant.config
332
333         processTestManifestTask.conventionMapping.testPackageName = {
334             config.packageName
335         }
336         processTestManifestTask.conventionMapping.minSdkVersion = {
337             config.minSdkVersion
338         }
339         processTestManifestTask.conventionMapping.testedPackageName = {
340             config.testedPackageName
341         }
342         processTestManifestTask.conventionMapping.instrumentationRunner = {
343             config.instrumentationRunner
344         }
345         processTestManifestTask.conventionMapping.libraries = {
346             getManifestDependencies(config.directLibraries)
347         }
348         processTestManifestTask.conventionMapping.outManifest = {
349             project.file(
350                     "$project.buildDir/${manifestOurDir}/$variant.dirName/AndroidManifest.xml")
351         }
352     }
353
354     protected void createRenderscriptTask(ApplicationVariant variant) {
355         VariantConfiguration config = variant.config
356
357         def renderscriptTask = project.tasks.add("compile${variant.name}Renderscript",
358                 RenderscriptCompile)
359         variant.renderscriptCompileTask = renderscriptTask
360
361         renderscriptTask.dependsOn variant.prepareDependenciesTask
362         renderscriptTask.plugin = this
363         renderscriptTask.variant = variant
364
365         renderscriptTask.targetApi = config.mergedFlavor.renderscriptTargetApi
366         renderscriptTask.debugBuild = config.buildType.renderscriptDebugBuild
367         renderscriptTask.optimLevel = config.buildType.renderscriptOptimLevel
368
369         renderscriptTask.conventionMapping.sourceDirs = { config.renderscriptSourceList }
370         renderscriptTask.conventionMapping.importDirs = { config.renderscriptImports }
371
372         renderscriptTask.conventionMapping.sourceOutputDir = {
373             project.file("$project.buildDir/source/rs/$variant.dirName")
374         }
375         renderscriptTask.conventionMapping.resOutputDir = {
376             project.file("$project.buildDir/res/rs/$variant.dirName")
377         }
378     }
379
380     protected void createMergeResourcesTask(ApplicationVariant variant, boolean process9Patch) {
381         createMergeResourcesTask(variant, "$project.buildDir/res/all/$variant.dirName",
382                 process9Patch)
383     }
384
385     protected void createMergeResourcesTask(ApplicationVariant variant, String location,
386                                             boolean process9Patch) {
387         def mergeResourcesTask = project.tasks.add("merge${variant.name}Resources", MergeResources)
388         variant.mergeResourcesTask = mergeResourcesTask
389
390         mergeResourcesTask.dependsOn variant.prepareDependenciesTask, variant.renderscriptCompileTask
391         mergeResourcesTask.plugin = this
392         mergeResourcesTask.variant = variant
393         mergeResourcesTask.incrementalFolder =
394                 project.file("$project.buildDir/incremental/mergeResources/$variant.dirName")
395
396         mergeResourcesTask.process9Patch = process9Patch
397
398         mergeResourcesTask.conventionMapping.inputResourceSets = {
399             variant.config.getResourceSets(variant.renderscriptCompileTask.getResOutputDir())
400         }
401
402         mergeResourcesTask.conventionMapping.outputDir = { project.file(location) }
403     }
404
405     protected void createMergeAssetsTask(ApplicationVariant variant, String location) {
406         if (location == null) {
407             location = "$project.buildDir/assets/$variant.dirName"
408         }
409
410         def mergeAssetsTask = project.tasks.add("merge${variant.name}Assets", MergeAssets)
411         variant.mergeAssetsTask = mergeAssetsTask
412
413         mergeAssetsTask.dependsOn variant.prepareDependenciesTask
414         mergeAssetsTask.plugin = this
415         mergeAssetsTask.variant = variant
416         mergeAssetsTask.incrementalFolder =
417             project.file("$project.buildDir/incremental/mergeAssets/$variant.dirName")
418
419         mergeAssetsTask.conventionMapping.inputAssetSets = { variant.config.assetSets }
420         mergeAssetsTask.conventionMapping.outputDir = { project.file(location) }
421     }
422
423     protected void createBuildConfigTask(ApplicationVariant variant) {
424         def generateBuildConfigTask = project.tasks.add(
425                 "generate${variant.name}BuildConfig", GenerateBuildConfig)
426         variant.generateBuildConfigTask = generateBuildConfigTask
427         if (variant.config.type == VariantConfiguration.Type.TEST) {
428             // in case of a test project, the manifest is generated so we need to depend
429             // on its creation.
430             generateBuildConfigTask.dependsOn variant.processManifestTask
431         }
432
433         generateBuildConfigTask.plugin = this
434         generateBuildConfigTask.variant = variant
435
436         generateBuildConfigTask.conventionMapping.packageName = {
437             variant.config.originalPackageName
438         }
439
440         generateBuildConfigTask.conventionMapping.debuggable = {
441             variant.config.buildType.isDebuggable()
442         }
443
444         generateBuildConfigTask.conventionMapping.javaLines = {
445             variant.config.buildConfigLines
446         }
447
448         generateBuildConfigTask.conventionMapping.sourceOutputDir = {
449             project.file("$project.buildDir/source/buildConfig/${variant.dirName}")
450         }
451     }
452
453     protected void createProcessResTask(ApplicationVariant variant) {
454         createProcessResTask(variant, "$project.buildDir/symbols/$variant.dirName")
455     }
456
457     protected void createProcessResTask(ApplicationVariant variant, final String symbolLocation) {
458         def processResources = project.tasks.add("process${variant.name}Resources",
459                 ProcessAndroidResources)
460         variant.processResourcesTask = processResources
461         processResources.dependsOn variant.processManifestTask, variant.mergeResourcesTask, variant.mergeAssetsTask
462
463         processResources.plugin = this
464         processResources.variant = variant
465
466         VariantConfiguration config = variant.config
467
468         processResources.conventionMapping.manifestFile = {
469             variant.processManifestTask.outManifest
470         }
471
472         processResources.conventionMapping.resFolder = {
473             variant.mergeResourcesTask.outputDir
474         }
475
476         processResources.conventionMapping.assetsDir =  {
477             variant.mergeAssetsTask.outputDir
478         }
479
480         processResources.conventionMapping.libraries = {
481             getTextSymbolDependencies(config.allLibraries)
482         }
483         processResources.conventionMapping.packageOverride = {
484             if (config.testedConfig != null) {
485                 return config.testedConfig.packageOverride
486             }
487             config.packageOverride
488         }
489
490         // TODO: unify with generateBuilderConfig, compileAidl, and library packaging somehow?
491         processResources.conventionMapping.sourceOutputDir = {
492             project.file("$project.buildDir/source/r/$variant.dirName")
493         }
494         processResources.conventionMapping.textSymbolDir = {
495             project.file(symbolLocation)
496         }
497         processResources.conventionMapping.packageFile = {
498             project.file(
499                     "$project.buildDir/libs/${project.archivesBaseName}-${variant.baseName}.ap_")
500         }
501         if (variant.runProguard) {
502             processResources.conventionMapping.proguardFile = {
503                 project.file("$project.buildDir/proguard/${variant.dirName}/rules.txt")
504             }
505         }
506
507         processResources.conventionMapping.type = { config.type }
508         processResources.conventionMapping.debuggable = { config.buildType.debuggable }
509         processResources.conventionMapping.aaptOptions = { extension.aaptOptions }
510     }
511
512     protected void createProcessJavaResTask(ApplicationVariant variant) {
513         VariantConfiguration config = variant.config
514
515         Copy processResources = project.tasks.add("process${variant.name}JavaRes",
516                 ProcessResources);
517         variant.processJavaResources = processResources
518
519         // set the input
520         processResources.from(((AndroidSourceSet) config.defaultSourceSet).resources)
521
522         if (config.getType() != VariantConfiguration.Type.TEST) {
523             processResources.from(((AndroidSourceSet) config.buildTypeSourceSet).resources)
524         }
525         if (config.hasFlavors()) {
526             for (SourceProvider flavorSourceSet : config.flavorSourceSets) {
527                 processResources.from(((AndroidSourceSet) flavorSourceSet).resources)
528             }
529         }
530
531         processResources.conventionMapping.destinationDir = {
532             project.file("$project.buildDir/javaResources/$variant.dirName")
533         }
534     }
535
536     protected void createAidlTask(ApplicationVariant variant) {
537         VariantConfiguration config = variant.config
538
539         def compileTask = project.tasks.add("compile${variant.name}Aidl", AidlCompile)
540         variant.aidlCompileTask = compileTask
541         variant.aidlCompileTask.dependsOn variant.prepareDependenciesTask
542
543         compileTask.plugin = this
544         compileTask.variant = variant
545         compileTask.incrementalFolder =
546             project.file("$project.buildDir/incremental/aidl/$variant.dirName")
547
548
549         compileTask.conventionMapping.sourceDirs = { config.aidlSourceList }
550         compileTask.conventionMapping.importDirs = { config.aidlImports }
551
552         compileTask.conventionMapping.sourceOutputDir = {
553             project.file("$project.buildDir/source/aidl/$variant.dirName")
554         }
555     }
556
557     protected void createCompileTask(ApplicationVariant variant,
558                                      ApplicationVariant testedVariant) {
559         def compileTask = project.tasks.add("compile${variant.name}", JavaCompile)
560         variant.javaCompileTask = compileTask
561         compileTask.dependsOn variant.processResourcesTask, variant.generateBuildConfigTask, variant.aidlCompileTask
562
563         VariantConfiguration config = variant.config
564
565         List<Object> sourceList = new ArrayList<Object>();
566         sourceList.add(((AndroidSourceSet) config.defaultSourceSet).java)
567         sourceList.add({ variant.processResourcesTask.sourceOutputDir })
568         sourceList.add({ variant.generateBuildConfigTask.sourceOutputDir })
569         sourceList.add({ variant.aidlCompileTask.sourceOutputDir })
570         sourceList.add({ variant.renderscriptCompileTask.sourceOutputDir })
571
572         if (config.getType() != VariantConfiguration.Type.TEST) {
573             sourceList.add(((AndroidSourceSet) config.buildTypeSourceSet).java)
574         }
575         if (config.hasFlavors()) {
576             for (SourceProvider flavorSourceSet : config.flavorSourceSets) {
577                 sourceList.add(((AndroidSourceSet) flavorSourceSet).java)
578             }
579         }
580         compileTask.source = sourceList.toArray()
581
582         if (testedVariant != null) {
583             compileTask.classpath = project.files({config.compileClasspath}) + testedVariant.javaCompileTask.classpath + testedVariant.javaCompileTask.outputs.files
584         } else {
585             compileTask.classpath = project.files({config.compileClasspath})
586         }
587
588         // TODO - dependency information for the compile classpath is being lost.
589         // Add a temporary approximation
590         compileTask.dependsOn project.configurations.compile.buildDependencies
591
592         compileTask.conventionMapping.destinationDir = {
593             project.file("$project.buildDir/classes/$variant.dirName")
594         }
595         compileTask.conventionMapping.dependencyCacheDir = {
596             project.file("$project.buildDir/dependency-cache/$variant.dirName")
597         }
598
599         // set source/target compatibility
600         compileTask.conventionMapping.sourceCompatibility = {
601             extension.compileOptions.sourceCompatibility.toString()
602         }
603         compileTask.conventionMapping.targetCompatibility = {
604             extension.compileOptions.targetCompatibility.toString()
605         }
606
607         // setup the boot classpath just before the task actually runs since this will
608         // force the sdk to be parsed.
609         compileTask.doFirst {
610             compileTask.options.bootClasspath = getRuntimeJars(variant)
611         }
612     }
613
614     /**
615      * Creates the test tasks, and return the main test[*] entry point.
616      *
617      * The main "test[*]" task can be created two different ways:
618      * mainTask is false: this creates the task for the given variant (with its variant name).
619      * mainTask is true: this creates the main "test" task, and makes check depend on it.
620      *
621      * @param variant the test variant
622      * @param testedVariant the tested variant
623      * @param configDependencies the list of config dependencies
624      * @param mainTestTask whether the main task is a main test task.
625      * @return the test task.
626      */
627     protected AndroidTestTask createTestTasks(TestAppVariant variant,
628                                               ProductionAppVariant testedVariant,
629                                               List<ConfigurationDependencies> configDependencies,
630                                               boolean mainTestTask) {
631         // The test app is signed with the same info as the tested app so there's no need
632         // to test both.
633         if (!testedVariant.isSigned()) {
634             throw new GradleException("Tested Variant '${testedVariant.name}' is not configured to create a signed APK.")
635         }
636
637         createPrepareDependenciesTask(variant, configDependencies)
638
639         // Add a task to process the manifest
640         createProcessTestManifestTask(variant, "manifests")
641
642         // Add a task to compile renderscript files.
643         createRenderscriptTask(variant)
644
645         // Add a task to merge the resource folders
646         createMergeResourcesTask(variant, true /*process9Patch*/)
647
648         // Add a task to merge the assets folders
649         createMergeAssetsTask(variant, null /*default location*/)
650
651         if (testedVariant.config.type == VariantConfiguration.Type.LIBRARY) {
652             // in this case the tested library must be fully built before test can be built!
653             if (testedVariant.assembleTask != null) {
654                 variant.processManifestTask.dependsOn testedVariant.assembleTask
655                 variant.mergeResourcesTask.dependsOn testedVariant.assembleTask
656             }
657         }
658
659         // Add a task to create the BuildConfig class
660         createBuildConfigTask(variant)
661
662         // Add a task to generate resource source files
663         createProcessResTask(variant)
664
665         // process java resources
666         createProcessJavaResTask(variant)
667
668         createAidlTask(variant)
669
670         // Add a task to compile the test application
671         createCompileTask(variant, testedVariant)
672
673         addPackageTasks(variant, null)
674
675         if (assembleTest != null) {
676             assembleTest.dependsOn variant.assembleTask
677         }
678
679         // create the check task for this test
680         def testFlavorTask = project.tasks.add(
681                 mainTestTask ? "instrumentationTest" : "instrumentationTest${testedVariant.name}",
682                 mainTestTask ? TestLibraryTask : TestFlavorTask)
683         testFlavorTask.description = "Installs and runs the tests for Build ${testedVariant.name}."
684         testFlavorTask.group = JavaBasePlugin.VERIFICATION_GROUP
685         testFlavorTask.dependsOn testedVariant.assembleTask, variant.assembleTask
686
687         if (mainTestTask) {
688             deviceCheck.dependsOn testFlavorTask
689         }
690
691         testFlavorTask.plugin = this
692         testFlavorTask.variant = variant
693         testFlavorTask.testedVariant = testedVariant
694         testFlavorTask.flavorName = variant.flavorName
695
696         testFlavorTask.conventionMapping.adbExe = { androidSdkParser.adb }
697
698         testFlavorTask.conventionMapping.testApp = { variant.outputFile }
699         if (testedVariant.config.type != VariantConfiguration.Type.LIBRARY) {
700             testFlavorTask.conventionMapping.testedApp = { testedVariant.outputFile }
701         }
702
703         testFlavorTask.conventionMapping.resultsDir = {
704             String rootLocation = extension.testOptions.resultsDir != null ?
705                 extension.testOptions.resultsDir : "$project.buildDir/test-results"
706
707             project.file("$rootLocation/flavors/$variant.flavorDirName")
708         }
709         testFlavorTask.conventionMapping.reportsDir = {
710             String rootLocation = extension.testOptions.reportDir != null ?
711                 extension.testOptions.reportDir : "$project.buildDir/reports/tests"
712
713             project.file("$rootLocation/flavors/$variant.flavorDirName")
714         }
715         variant.testFlavorTask = testFlavorTask
716
717         return testFlavorTask
718     }
719
720     /**
721      * Creates the packaging tasks for the given Variant.
722      * @param variant the variant.
723      * @param assembleTask an optional assembleTask to be used. If null a new one is created. The
724      *                assembleTask is always set in the Variant.
725      */
726     protected void addPackageTasks(ApplicationVariant variant, Task assembleTask) {
727         // Add a dex task
728         def dexTaskName = "dex${variant.name}"
729         def dexTask = project.tasks.add(dexTaskName, Dex)
730         variant.dexTask = dexTask
731         dexTask.dependsOn variant.javaCompileTask
732
733         dexTask.plugin = this
734         dexTask.variant = variant
735         dexTask.incrementalFolder =
736                 project.file("$project.buildDir/incremental/dex/$variant.dirName")
737
738         dexTask.conventionMapping.libraries = { project.files({ variant.config.packagedJars }) }
739         dexTask.conventionMapping.sourceFiles = { variant.javaCompileTask.outputs.files } // this creates a dependency
740         dexTask.conventionMapping.outputFile = {
741             project.file(
742                     "${project.buildDir}/libs/${project.archivesBaseName}-${variant.baseName}.dex")
743         }
744         dexTask.dexOptions = extension.dexOptions
745
746         // Add a task to generate application package
747         def packageApp = project.tasks.add("package${variant.name}", PackageApplication)
748         variant.packageApplicationTask = packageApp
749         packageApp.dependsOn variant.processResourcesTask, dexTask, variant.processJavaResources
750
751         packageApp.plugin = this
752         packageApp.variant = variant
753
754         VariantConfiguration config = variant.config
755
756         packageApp.conventionMapping.resourceFile = { variant.processResourcesTask.packageFile }
757         packageApp.conventionMapping.dexFile = { dexTask.outputFile }
758
759         packageApp.conventionMapping.packagedJars = { config.packagedJars }
760
761         packageApp.conventionMapping.javaResourceDir = {
762             getOptionalDir(variant.processJavaResources.destinationDir)
763         }
764
765         packageApp.conventionMapping.jniDebugBuild = { config.buildType.jniDebugBuild }
766
767         SigningConfigDsl sc = (SigningConfigDsl) config.signingConfig
768         packageApp.conventionMapping.signingConfig = { sc }
769         if (sc != null) {
770             ValidateSigningTask validateSigningTask = validateSigningTaskMap.get(sc)
771             if (validateSigningTask == null) {
772                 validateSigningTask = project.tasks.add("validate${sc.name.capitalize()}Signing",
773                     ValidateSigningTask)
774                 validateSigningTask.plugin = this
775                 validateSigningTask.signingConfig = sc
776
777                 validateSigningTaskMap.put(sc, validateSigningTask)
778             }
779
780             packageApp.dependsOn validateSigningTask
781         }
782
783         def signedApk = variant.isSigned()
784         def apkName = signedApk ?
785             "${project.archivesBaseName}-${variant.baseName}-unaligned.apk" :
786             "${project.archivesBaseName}-${variant.baseName}-unsigned.apk"
787
788         packageApp.conventionMapping.outputFile = {
789             project.file("$project.buildDir/apk/${apkName}")
790         }
791
792         def appTask = packageApp
793         variant.outputFile = project.file("$project.buildDir/apk/${apkName}")
794
795         if (signedApk) {
796             if (variant.zipAlign) {
797                 // Add a task to zip align application package
798                 def zipAlignTask = project.tasks.add("zipalign${variant.name}", ZipAlign)
799                 variant.zipAlignTask = zipAlignTask
800
801                 zipAlignTask.dependsOn packageApp
802                 zipAlignTask.conventionMapping.inputFile = { packageApp.outputFile }
803                 zipAlignTask.conventionMapping.outputFile = {
804                     project.file(
805                             "$project.buildDir/apk/${project.archivesBaseName}-${variant.baseName}.apk")
806                 }
807                 zipAlignTask.conventionMapping.zipAlignExe = { androidSdkParser.zipAlign }
808
809                 appTask = zipAlignTask
810                 variant.outputFile = project.file(
811                         "$project.buildDir/apk/${project.archivesBaseName}-${variant.baseName}.apk")
812             }
813
814             // Add a task to install the application package
815             def installTask = project.tasks.add("install${variant.name}", InstallTask)
816             installTask.description = "Installs the " + variant.description
817             installTask.group = INSTALL_GROUP
818             installTask.dependsOn appTask
819             installTask.conventionMapping.packageFile = { appTask.outputFile }
820             installTask.conventionMapping.adbExe = { androidSdkParser.adb }
821
822             variant.installTask = installTask
823         }
824
825         // Add an assemble task
826         if (assembleTask == null) {
827             assembleTask = project.tasks.add("assemble${variant.name}")
828             assembleTask.description = "Assembles the " + variant.description
829             assembleTask.group = org.gradle.api.plugins.BasePlugin.BUILD_GROUP
830         }
831         assembleTask.dependsOn appTask
832         variant.assembleTask = assembleTask
833
834         // add an uninstall task
835         def uninstallTask = project.tasks.add("uninstall${variant.name}", UninstallTask)
836         uninstallTask.description = "Uninstalls the " + variant.description
837         uninstallTask.group = INSTALL_GROUP
838         uninstallTask.variant = variant
839         uninstallTask.conventionMapping.adbExe = { androidSdkParser.adb }
840
841         variant.uninstallTask = uninstallTask
842         uninstallAll.dependsOn uninstallTask
843     }
844
845     private void createReportTasks() {
846         def dependencyReportTask = project.tasks.add("androidDependencies", DependencyReportTask)
847         dependencyReportTask.setDescription("Displays the Android dependencies of the project")
848         dependencyReportTask.setVariants(variants)
849         dependencyReportTask.setGroup("Android")
850
851         def signingReportTask = project.tasks.add("signingReport", SigningReportTask)
852         signingReportTask.setDescription("Displays the signing info for each variant")
853         signingReportTask.setVariants(variants)
854         signingReportTask.setGroup("Android")
855     }
856
857     protected void createPrepareDependenciesTask(ApplicationVariant variant,
858             List<ConfigurationDependencies> configDependenciesList) {
859         def prepareDependenciesTask = project.tasks.add("prepare${variant.name}Dependencies",
860                 PrepareDependenciesTask)
861         variant.prepareDependenciesTask = prepareDependenciesTask
862
863         prepareDependenciesTask.plugin = this
864         prepareDependenciesTask.variant = variant
865
866         // for all libraries required by the configurations of this variant, make this task
867         // depend on all the tasks preparing these libraries.
868         for (ConfigurationDependencies configDependencies : configDependenciesList) {
869             prepareDependenciesTask.addChecker(configDependencies.checker)
870
871             for (AndroidDependencyImpl lib : configDependencies.libraries) {
872                 addDependencyToPrepareTask(prepareDependenciesTask, lib)
873             }
874         }
875     }
876
877     def addDependencyToPrepareTask(PrepareDependenciesTask prepareDependenciesTask,
878                                    AndroidDependencyImpl lib) {
879         def prepareLibTask = prepareTaskMap.get(lib)
880         if (prepareLibTask != null) {
881             prepareDependenciesTask.dependsOn prepareLibTask
882         }
883
884         for (AndroidDependencyImpl childLib : lib.dependencies) {
885             addDependencyToPrepareTask(prepareDependenciesTask, childLib)
886         }
887     }
888
889     def resolveDependencies(List<ConfigurationDependencies> configs) {
890         Map<ModuleVersionIdentifier, List<AndroidDependency>> modules = [:]
891         Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts = [:]
892         Multimap<AndroidDependency, ConfigurationDependencies> reverseMap = ArrayListMultimap.create()
893
894         // start with the default config and its test
895         resolveDependencyForConfig(defaultConfigData, modules, artifacts, reverseMap)
896         resolveDependencyForConfig(defaultConfigData.testConfigDependencies, modules, artifacts,
897                 reverseMap)
898
899         // and then loop on all the other configs
900         for (ConfigurationDependencies config : configs) {
901             resolveDependencyForConfig(config, modules, artifacts, reverseMap)
902             if (config.testConfigDependencies != null) {
903                 resolveDependencyForConfig(config.testConfigDependencies, modules, artifacts,
904                         reverseMap)
905             }
906         }
907
908         modules.values().each { List list ->
909             if (!list.isEmpty()) {
910                 // get the first item only
911                 AndroidDependencyImpl androidDependency = (AndroidDependencyImpl) list.get(0)
912
913                 String bundleName = GUtil.toCamelCase(androidDependency.name.replaceAll("\\:", " "))
914
915                 def prepareLibraryTask = project.tasks.add("prepare${bundleName}Library",
916                         PrepareLibraryTask)
917                 prepareLibraryTask.description = "Prepare ${androidDependency.name}"
918                 prepareLibraryTask.bundle = androidDependency.bundle
919                 prepareLibraryTask.explodedDir = androidDependency.bundleFolder
920
921                 // Use the reverse map to find all the configurations that included this android
922                 // library so that we can make sure they are built.
923                 List<ConfigurationDependencies> configDepList = reverseMap.get(androidDependency)
924                 if (configDepList != null && !configDepList.isEmpty()) {
925                     for (ConfigurationDependencies configDependencies: configDepList) {
926                         prepareLibraryTask.dependsOn configDependencies.configuration.buildDependencies
927                     }
928                 }
929
930                 prepareTaskMap.put(androidDependency, prepareLibraryTask)
931             }
932         }
933     }
934
935     def resolveDependencyForConfig(
936             ConfigurationDependencies configDependencies,
937             Map<ModuleVersionIdentifier, List<AndroidDependency>> modules,
938             Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts,
939             Multimap<AndroidDependency, ConfigurationDependencies> reverseMap) {
940
941         // TODO support package configuration
942         def compileClasspath = configDependencies.configuration
943
944         // TODO - shouldn't need to do this - fix this in Gradle
945         ensureConfigured(compileClasspath)
946
947         configDependencies.checker = new DependencyChecker(configDependencies, logger)
948
949         // TODO - defer downloading until required -- This is hard to do as we need the info to build the variant config.
950         List<AndroidDependency> bundles = []
951         List<JarDependency> jars = []
952         collectArtifacts(compileClasspath, artifacts)
953         compileClasspath.incoming.resolutionResult.root.dependencies.each { ResolvedDependencyResult dep ->
954             addDependency(dep.selected, configDependencies, bundles, jars, modules,
955                     artifacts, reverseMap)
956         }
957         // also need to process local jar files, as they are not processed by the
958         // resolvedConfiguration result
959         compileClasspath.allDependencies.each { dep ->
960             if (dep instanceof SelfResolvingDependency &&
961                     !(dep instanceof ProjectDependency)) {
962                 Set<File> files = ((SelfResolvingDependency) dep).resolve()
963                 for (File f : files) {
964                     jars << new JarDependency(f.absolutePath, true, true, true)
965                 }
966             }
967         }
968
969         configDependencies.libraries = bundles
970         configDependencies.jars = jars
971
972         // TODO - filter bundles out of source set classpath
973
974         configureBuild(configDependencies)
975     }
976
977     def ensureConfigured(Configuration config) {
978         config.allDependencies.withType(ProjectDependency).each { dep ->
979             project.evaluationDependsOn(dep.dependencyProject.path)
980             ensureConfigured(dep.projectConfiguration)
981         }
982     }
983
984     static def collectArtifacts(Configuration configuration, Map<ModuleVersionIdentifier,
985                          List<ResolvedArtifact>> artifacts) {
986         configuration.resolvedConfiguration.resolvedArtifacts.each { ResolvedArtifact artifact ->
987             def id = artifact.moduleVersion.id
988             List<ResolvedArtifact> moduleArtifacts = artifacts[id]
989             if (moduleArtifacts == null) {
990                 moduleArtifacts = []
991                 artifacts[id] = moduleArtifacts
992             }
993             moduleArtifacts << artifact
994         }
995     }
996
997     def addDependency(ResolvedModuleVersionResult moduleVersion,
998                       ConfigurationDependencies configDependencies,
999                       Collection<AndroidDependency> bundles,
1000                       Collection<JarDependency> jars,
1001                       Map<ModuleVersionIdentifier, List<AndroidDependency>> modules,
1002                       Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts,
1003                       Multimap<AndroidDependency, ConfigurationDependencies> reverseMap) {
1004         def id = moduleVersion.id
1005         if (configDependencies.checker.excluded(id)) {
1006             return
1007         }
1008
1009         List<AndroidDependency> bundlesForThisModule = modules[id]
1010         if (bundlesForThisModule == null) {
1011             bundlesForThisModule = []
1012             modules[id] = bundlesForThisModule
1013
1014             def nestedBundles = []
1015             moduleVersion.dependencies.each { ResolvedDependencyResult dep ->
1016                 addDependency(dep.selected, configDependencies, nestedBundles,
1017                         jars, modules, artifacts, reverseMap)
1018             }
1019
1020             def moduleArtifacts = artifacts[id]
1021             moduleArtifacts?.each { artifact ->
1022                 if (artifact.type == BuilderConstants.EXT_LIB_ARCHIVE) {
1023                     def explodedDir = project.file(
1024                             "$project.buildDir/exploded-bundles/$artifact.file.name")
1025                     AndroidDependencyImpl adep = new AndroidDependencyImpl(
1026                             id.group + ":" + id.name + ":" + id.version,
1027                             explodedDir, nestedBundles, artifact.file)
1028                     bundlesForThisModule << adep
1029                     reverseMap.put(adep, configDependencies)
1030                 } else {
1031                     // TODO - need the correct values for the boolean flags
1032                     jars << new JarDependency(artifact.file.absolutePath, true, true, true)
1033                 }
1034             }
1035
1036             if (bundlesForThisModule.empty && !nestedBundles.empty) {
1037                 throw new GradleException("Module version $id depends on libraries but is not a library itself")
1038             }
1039         } else {
1040             for (AndroidDependency adep : bundlesForThisModule) {
1041                 reverseMap.put(adep, configDependencies)
1042             }
1043         }
1044
1045         bundles.addAll(bundlesForThisModule)
1046     }
1047
1048     private void configureBuild(ConfigurationDependencies configurationDependencies) {
1049         def configuration = configurationDependencies.configuration
1050
1051         addDependsOnTaskInOtherProjects(
1052                 project.getTasks().getByName(JavaBasePlugin.BUILD_NEEDED_TASK_NAME), true,
1053                 JavaBasePlugin.BUILD_NEEDED_TASK_NAME, "compile");
1054         addDependsOnTaskInOtherProjects(
1055                 project.getTasks().getByName(JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME), false,
1056                 JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME, "compile");
1057     }
1058
1059     /**
1060      * Adds a dependency on tasks with the specified name in other projects.  The other projects
1061      * are determined from project lib dependencies using the specified configuration name.
1062      * These may be projects this project depends on or projects that depend on this project
1063      * based on the useDependOn argument.
1064      *
1065      * @param task Task to add dependencies to
1066      * @param useDependedOn if true, add tasks from projects this project depends on, otherwise
1067      * use projects that depend on this one.
1068      * @param otherProjectTaskName name of task in other projects
1069      * @param configurationName name of configuration to use to find the other projects
1070      */
1071     private static void addDependsOnTaskInOtherProjects(final Task task, boolean useDependedOn,
1072                                                  String otherProjectTaskName,
1073                                                  String configurationName) {
1074         Project project = task.getProject();
1075         final Configuration configuration = project.getConfigurations().getByName(
1076                 configurationName);
1077         task.dependsOn(configuration.getTaskDependencyFromProjectDependency(
1078                 useDependedOn, otherProjectTaskName));
1079     }
1080
1081     protected static File getOptionalDir(File dir) {
1082         if (dir.isDirectory()) {
1083             return dir
1084         }
1085
1086         return null
1087     }
1088
1089     /**
1090      * TODO: remove once assets support more than one folder.
1091      * @param dirs
1092      * @return
1093      */
1094     protected static File getFirstOptionalDir(Set<File> dirs) {
1095         Iterator<File> iterator = dirs.iterator();
1096
1097         File dir = iterator.hasNext() ? iterator.next() : null;
1098         if (dir != null && dir.isDirectory()) {
1099             return dir
1100         }
1101
1102         return null
1103     }
1104
1105     protected List<ManifestDependencyImpl> getManifestDependencies(
1106             List<AndroidDependency> libraries) {
1107
1108         List<ManifestDependencyImpl> list = Lists.newArrayListWithCapacity(libraries.size())
1109
1110         for (AndroidDependency lib : libraries) {
1111             // get the dependencies
1112             List<ManifestDependency> children = getManifestDependencies(lib.dependencies)
1113             list.add(new ManifestDependencyImpl(lib.manifest, children))
1114         }
1115
1116         return list
1117     }
1118
1119     protected static List<SymbolFileProviderImpl> getTextSymbolDependencies(
1120             List<AndroidDependency> libraries) {
1121
1122         List<SymbolFileProviderImpl> list = Lists.newArrayListWithCapacity(libraries.size())
1123
1124         for (AndroidDependency lib : libraries) {
1125             list.add(new SymbolFileProviderImpl(lib.manifest, lib.symbolFile))
1126         }
1127
1128         return list
1129     }
1130 }
1131