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