6ad3271f9027304f3cd3811be7671f3be65e7d88
[android/platform/tools/build.git] / gradle / src / main / groovy / com / android / build / gradle / AndroidBasePlugin.groovy
1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.build.gradle
17
18 import com.android.build.gradle.internal.AndroidSourceSet
19 import com.android.build.gradle.internal.ApplicationVariant
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.builder.AndroidBuilder
24 import com.android.builder.DefaultSdkParser
25 import com.android.builder.ProductFlavor
26 import com.android.builder.SdkParser
27 import com.android.builder.VariantConfiguration
28 import com.android.utils.ILogger
29 import org.gradle.api.Project
30 import org.gradle.api.Task
31 import org.gradle.api.logging.LogLevel
32 import org.gradle.api.plugins.BasePlugin
33 import org.gradle.api.plugins.JavaBasePlugin
34 import org.gradle.api.tasks.SourceSet
35 import org.gradle.api.tasks.compile.Compile
36
37 /**
38  * Base class for all Android plugins
39  */
40 abstract class AndroidBasePlugin {
41
42     public final static String INSTALL_GROUP = "Install"
43
44     private final Map<Object, AndroidBuilder> builders = [:]
45
46     protected Project project
47     protected File sdkDir
48     private DefaultSdkParser androidSdkParser
49     private AndroidLogger androidLogger
50
51     private ProductFlavorData defaultConfigData
52     protected SourceSet mainSourceSet
53     protected SourceSet testSourceSet
54
55     protected Task installAllForTests
56     protected Task runAllTests
57     protected Task uninstallAll
58     protected Task assembleTest
59
60     abstract String getTarget()
61
62     protected void apply(Project project) {
63         this.project = project
64         project.apply plugin: JavaBasePlugin
65
66         mainSourceSet = project.sourceSets.add("main")
67         testSourceSet = project.sourceSets.add("test")
68
69         project.tasks.assemble.description =
70             "Assembles all variants of all applications and secondary packages."
71
72         findSdk(project)
73
74         installAllForTests = project.tasks.add("installAllForTests")
75         installAllForTests.group = "Verification"
76         installAllForTests.description = "Installs all applications needed to run tests."
77
78         runAllTests = project.tasks.add("runAllTests")
79         runAllTests.group = "Verification"
80         runAllTests.description = "Runs all tests."
81
82         uninstallAll = project.tasks.add("uninstallAll")
83         uninstallAll.description = "Uninstall all applications."
84         uninstallAll.group = "Install"
85
86         project.tasks.check.dependsOn installAllForTests, runAllTests
87     }
88
89     protected setDefaultConfig(ProductFlavor defaultConfig) {
90         defaultConfigData = new ProductFlavorData(defaultConfig, mainSourceSet,
91                 testSourceSet, project)
92     }
93
94     ProductFlavorData getDefaultConfigData() {
95         return defaultConfigData
96     }
97
98     SdkParser getSdkParser() {
99         if (androidSdkParser == null) {
100             androidSdkParser = new DefaultSdkParser(sdkDir.absolutePath)
101         }
102
103         return androidSdkParser;
104     }
105
106     ILogger getLogger() {
107         if (androidLogger == null) {
108             androidLogger = new AndroidLogger(project.logger)
109         }
110
111         return androidLogger
112     }
113
114     boolean isVerbose() {
115         return project.logger.isEnabled(LogLevel.DEBUG)
116     }
117
118     AndroidBuilder getAndroidBuilder(Object key) {
119         return builders.get(key)
120     }
121
122     void setAndroidBuilder(Object key, AndroidBuilder androidBuilder) {
123         builders.put(key, androidBuilder)
124     }
125
126     private void findSdk(Project project) {
127         def localProperties = project.file("local.properties")
128         if (localProperties.exists()) {
129             Properties properties = new Properties()
130             localProperties.withInputStream { instr ->
131                 properties.load(instr)
132             }
133             def sdkDirProp = properties.getProperty('sdk.dir')
134             if (!sdkDirProp) {
135                 throw new RuntimeException("No sdk.dir property defined in local.properties file.")
136             }
137             sdkDir = new File(sdkDirProp)
138         } else {
139             def envVar = System.getenv("ANDROID_HOME")
140             if (envVar != null) {
141                 sdkDir = new File(envVar)
142             }
143         }
144
145         if (sdkDir == null) {
146             throw new RuntimeException(
147                     "SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.")
148         }
149
150         if (!sdkDir.directory) {
151             throw new RuntimeException(
152                     "The SDK directory '$sdkDir' specified in local.properties does not exist.")
153         }
154     }
155
156     protected String getRuntimeJars(ApplicationVariant variant) {
157         AndroidBuilder androidBuilder = getAndroidBuilder(variant)
158
159         return androidBuilder.runtimeClasspath.join(":")
160     }
161
162     protected ProcessManifestTask createProcessManifestTask(ApplicationVariant variant,
163                                                         String manifestOurDir) {
164         def processManifestTask = project.tasks.add("process${variant.name}Manifest",
165                 ProcessManifestTask)
166         processManifestTask.plugin = this
167         processManifestTask.variant = variant
168         processManifestTask.configObjects = variant.configObjects
169         processManifestTask.conventionMapping.processedManifest = {
170             project.file(
171                     "$project.buildDir/${manifestOurDir}/$variant.dirName/AndroidManifest.xml")
172         }
173
174         return processManifestTask
175     }
176
177     protected CrunchResourcesTask createCrunchResTask(ApplicationVariant variant) {
178         def crunchTask = project.tasks.add("crunch${variant.name}Res", CrunchResourcesTask)
179         crunchTask.plugin = this
180         crunchTask.variant = variant
181         crunchTask.configObjects = variant.configObjects
182         crunchTask.conventionMapping.resDirectories = { variant.config.resourceInputs }
183         crunchTask.conventionMapping.outputDir = {
184             project.file("$project.buildDir/res/$variant.dirName")
185         }
186
187         return crunchTask
188     }
189
190     protected GenerateBuildConfigTask createBuildConfigTask(ApplicationVariant variant,
191                                                             ProcessManifestTask processManifestTask) {
192         def generateBuildConfigTask = project.tasks.add(
193                 "generate${variant.name}BuildConfig", GenerateBuildConfigTask)
194         if (processManifestTask != null) {
195             // This is in case the manifest is generated
196             generateBuildConfigTask.dependsOn processManifestTask
197         }
198         generateBuildConfigTask.plugin = this
199         generateBuildConfigTask.variant = variant
200         generateBuildConfigTask.configObjects = variant.configObjects
201         generateBuildConfigTask.optionalJavaLines = variant.buildConfigLines
202         generateBuildConfigTask.conventionMapping.sourceOutputDir = {
203             project.file("$project.buildDir/source/${variant.dirName}")
204         }
205         return generateBuildConfigTask
206     }
207
208     protected ProcessResourcesTask createProcessResTask(ApplicationVariant variant,
209                                                     ProcessManifestTask processManifestTask,
210                                                     CrunchResourcesTask crunchTask) {
211         def processResources = project.tasks.add("process${variant.name}Res", ProcessResourcesTask)
212         processResources.dependsOn processManifestTask
213         processResources.plugin = this
214         processResources.variant = variant
215         processResources.configObjects = variant.configObjects
216         processResources.conventionMapping.manifestFile = { processManifestTask.processedManifest }
217         // TODO: unify with generateBuilderConfig, and compileAidl somehow?
218         processResources.conventionMapping.sourceOutputDir = {
219             project.file("$project.buildDir/source/$variant.dirName")
220         }
221         processResources.conventionMapping.packageFile = {
222             project.file(
223                     "$project.buildDir/libs/${project.archivesBaseName}-${variant.baseName}.ap_")
224         }
225         if (variant.runProguard) {
226             processResources.conventionMapping.proguardFile = {
227                 project.file("$project.buildDir/proguard/${variant.dirName}/rules.txt")
228             }
229         }
230
231         if (crunchTask != null) {
232             processResources.dependsOn crunchTask
233             processResources.conventionMapping.crunchDir = { crunchTask.outputDir }
234             processResources.conventionMapping.resDirectories = { crunchTask.resDirectories }
235         } else {
236             processResources.conventionMapping.resDirectories = { variant.config.resourceInputs }
237         }
238
239         processResources.aaptOptions = extension.aaptOptions
240         return processResources
241     }
242
243     protected CompileAidlTask createAidlTask(ApplicationVariant variant) {
244
245         VariantConfiguration config = variant.config
246
247         def compileTask = project.tasks.add("compile${variant.name}Aidl", CompileAidlTask)
248         compileTask.plugin = this
249         compileTask.variant = variant
250         compileTask.configObjects = variant.configObjects
251
252         List<Object> sourceList = new ArrayList<Object>();
253         sourceList.add(config.defaultSourceSet.aidlSource)
254         if (config.getType() != VariantConfiguration.Type.TEST) {
255             sourceList.add(config.buildTypeSourceSet.aidlSource)
256         }
257         if (config.hasFlavors()) {
258             for (com.android.builder.SourceSet flavorSourceSet : config.flavorSourceSets) {
259                 sourceList.add(flavorSourceSet.aidlSource)
260             }
261         }
262
263         compileTask.sourceDirs = sourceList
264         compileTask.importDirs = variant.config.aidlImports
265
266         compileTask.conventionMapping.sourceOutputDir = {
267             project.file("$project.buildDir/source/$variant.dirName")
268         }
269
270         return compileTask
271     }
272
273     protected void createCompileTask(ApplicationVariant variant,
274                                      ApplicationVariant testedVariant,
275                                      ProcessResourcesTask processResources,
276                                      GenerateBuildConfigTask generateBuildConfigTask,
277                                      CompileAidlTask aidlTask) {
278         def compileTask = project.tasks.add("compile${variant.name}", Compile)
279         compileTask.dependsOn processResources, generateBuildConfigTask, aidlTask
280
281         VariantConfiguration config = variant.config
282
283         List<Object> sourceList = new ArrayList<Object>();
284         sourceList.add(((AndroidSourceSet) config.defaultSourceSet).sourceSet.java)
285         sourceList.add({ processResources.sourceOutputDir })
286         if (config.getType() != VariantConfiguration.Type.TEST) {
287             sourceList.add(((AndroidSourceSet) config.buildTypeSourceSet).sourceSet.java)
288         }
289         if (config.hasFlavors()) {
290             for (com.android.builder.SourceSet flavorSourceSet : config.flavorSourceSets) {
291                 sourceList.add(((AndroidSourceSet) flavorSourceSet).sourceSet.java)
292             }
293         }
294         compileTask.source = sourceList.toArray()
295
296         // if the test is for a full app, the tested runtimeClasspath is added to the classpath for
297         // compilation only, not for packaging
298         variant.packagedClasspath = project.files({config.compileClasspath})
299         if (testedVariant != null &&
300                 testedVariant.config.type != VariantConfiguration.Type.LIBRARY) {
301             compileTask.classpath = variant.packagedClasspath + testedVariant.runtimeClasspath
302         } else {
303             compileTask.classpath = variant.packagedClasspath
304         }
305         // TODO - dependency information for the compile classpath is being lost.
306         // Add a temporary approximation
307         compileTask.dependsOn project.configurations.compile.buildDependencies
308
309         compileTask.conventionMapping.destinationDir = {
310             project.file("$project.buildDir/classes/$variant.dirName")
311         }
312         compileTask.doFirst {
313             compileTask.options.bootClasspath = getRuntimeJars(variant)
314         }
315
316         // Wire up the outputs
317         variant.runtimeClasspath = compileTask.outputs.files + compileTask.classpath
318         variant.resourcePackage = project.files({processResources.packageFile}) {
319             builtBy processResources
320         }
321         variant.compileTask = compileTask
322     }
323
324     protected void createTestTasks(TestAppVariant variant, ProductionAppVariant testedVariant) {
325         // Add a task to process the manifest
326         def processManifestTask = createProcessManifestTask(variant, "manifests")
327
328         // Add a task to crunch resource files
329         def crunchTask = createCrunchResTask(variant)
330
331         if (testedVariant.config.type == VariantConfiguration.Type.LIBRARY) {
332             // in this case the tested library must be fully built before test can be built!
333             if (testedVariant.assembleTask != null) {
334                 processManifestTask.dependsOn testedVariant.assembleTask
335                 crunchTask.dependsOn testedVariant.assembleTask
336             }
337         }
338
339         // Add a task to create the BuildConfig class
340         def generateBuildConfigTask = createBuildConfigTask(variant, processManifestTask)
341
342         // Add a task to generate resource source files
343         def processResources = createProcessResTask(variant, processManifestTask, crunchTask)
344
345         def compileAidl = createAidlTask(variant)
346
347         // Add a task to compile the test application
348         createCompileTask(variant, testedVariant, processResources, generateBuildConfigTask,
349                 compileAidl)
350
351         Task assembleTask = addPackageTasks(variant, null, true /*isTestApk*/)
352
353         if (assembleTest != null) {
354             assembleTest.dependsOn assembleTask
355         }
356
357         def runTestsTask = project.tasks.add("Run${variant.name}Tests", RunTestsTask)
358         runTestsTask.sdkDir = sdkDir
359         runTestsTask.variant = variant
360
361         runAllTests.dependsOn runTestsTask
362     }
363
364     protected Task addPackageTasks(ApplicationVariant variant, Task assembleTask,
365                                    boolean isTestApk) {
366         // Add a dex task
367         def dexTaskName = "dex${variant.name}"
368         def dexTask = project.tasks.add(dexTaskName, DexTask)
369         dexTask.dependsOn variant.compileTask
370         dexTask.plugin = this
371         dexTask.variant = variant
372         dexTask.conventionMapping.libraries = { variant.packagedClasspath }
373         dexTask.conventionMapping.sourceFiles = { variant.compileTask.outputs.files }
374         dexTask.conventionMapping.outputFile = {
375             project.file(
376                     "${project.buildDir}/libs/${project.archivesBaseName}-${variant.baseName}.dex")
377         }
378         dexTask.dexOptions = extension.dexOptions
379
380         // Add a task to generate application package
381         def packageApp = project.tasks.add("package${variant.name}", PackageApplicationTask)
382         packageApp.dependsOn variant.resourcePackage, dexTask
383         packageApp.plugin = this
384         packageApp.variant = variant
385         packageApp.configObjects = variant.configObjects
386
387         def signedApk = variant.isSigned()
388
389         def apkName = signedApk ?
390             "${project.archivesBaseName}-${variant.baseName}-unaligned.apk" :
391             "${project.archivesBaseName}-${variant.baseName}-unsigned.apk"
392
393         packageApp.conventionMapping.outputFile = {
394             project.file("$project.buildDir/apk/${apkName}")
395         }
396         packageApp.conventionMapping.resourceFile = { variant.resourcePackage.singleFile }
397         packageApp.conventionMapping.dexFile = { dexTask.outputFile }
398
399         def appTask = packageApp
400
401         if (signedApk) {
402             if (variant.zipAlign) {
403                 // Add a task to zip align application package
404                 def alignApp = project.tasks.add("zipalign${variant.name}", ZipAlignTask)
405                 alignApp.dependsOn packageApp
406                 alignApp.conventionMapping.inputFile = { packageApp.outputFile }
407                 alignApp.conventionMapping.outputFile = {
408                     project.file(
409                             "$project.buildDir/apk/${project.archivesBaseName}-${variant.baseName}.apk")
410                 }
411                 alignApp.sdkDir = sdkDir
412
413                 appTask = alignApp
414             }
415
416             // Add a task to install the application package
417             def installApp = project.tasks.add("install${variant.name}", InstallTask)
418             installApp.description = "Installs the " + variant.description
419             installApp.group = "Install"
420             installApp.dependsOn appTask
421             installApp.conventionMapping.packageFile = { appTask.outputFile }
422             installApp.sdkDir = sdkDir
423
424             if (isTestApk) {
425                 installAllForTests.dependsOn installApp
426             }
427         }
428
429         // Add an assemble task
430         Task returnTask = null
431         if (assembleTask == null) {
432             assembleTask = project.tasks.add("assemble${variant.name}")
433             assembleTask.description = "Assembles the " + variant.description
434             assembleTask.group = BasePlugin.BUILD_GROUP
435             returnTask = assembleTask
436         }
437         assembleTask.dependsOn appTask
438
439         // add an uninstall task
440         def uninstallApp = project.tasks.add("uninstall${variant.name}", UninstallTask)
441         uninstallApp.description = "Uninstalls the " + variant.description
442         uninstallApp.group = AndroidBasePlugin.INSTALL_GROUP
443         uninstallApp.variant = variant
444         uninstallApp.sdkDir = sdkDir
445
446         uninstallAll.dependsOn uninstallApp
447
448         return returnTask
449     }
450
451 }