a07766b4dbc2fbe559eeb9d3c72f92fc101e5132
[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
18 import com.android.SdkConstants
19 import com.android.build.gradle.internal.AndroidDependencyImpl
20 import com.android.build.gradle.internal.ApplicationVariant
21 import com.android.build.gradle.internal.ProductFlavorData
22 import com.android.build.gradle.internal.ProductionAppVariant
23 import com.android.build.gradle.internal.TestAppVariant
24 import com.android.builder.AndroidBuilder
25 import com.android.builder.AndroidDependency
26 import com.android.builder.DefaultSdkParser
27 import com.android.builder.JarDependency
28 import com.android.builder.ProductFlavor
29 import com.android.builder.SdkParser
30 import com.android.builder.SourceProvider
31 import com.android.builder.VariantConfiguration
32 import com.android.utils.ILogger
33 import org.gradle.api.DefaultTask
34 import org.gradle.api.GradleException
35 import org.gradle.api.NamedDomainObjectContainer
36 import org.gradle.api.Project
37 import org.gradle.api.Task
38 import org.gradle.api.artifacts.Configuration
39 import org.gradle.api.artifacts.ModuleVersionIdentifier
40 import org.gradle.api.artifacts.ProjectDependency
41 import org.gradle.api.artifacts.ResolvedArtifact
42 import org.gradle.api.artifacts.result.ResolvedDependencyResult
43 import org.gradle.api.artifacts.result.ResolvedModuleVersionResult
44 import org.gradle.api.internal.plugins.ProcessResources
45 import org.gradle.api.logging.LogLevel
46 import org.gradle.api.plugins.JavaBasePlugin
47 import org.gradle.api.plugins.JavaPluginConvention
48 import org.gradle.api.tasks.Copy
49 import org.gradle.api.tasks.compile.JavaCompile
50
51 /**
52  * Base class for all Android plugins
53  */
54 abstract class BasePlugin {
55
56     public final static String INSTALL_GROUP = "Install"
57
58     private final Map<Object, AndroidBuilder> builders = [:]
59
60     protected Project project
61     protected File sdkDir
62     private DefaultSdkParser androidSdkParser
63     private LoggerWrapper loggerWrapper
64
65     private ProductFlavorData defaultConfigData
66     protected AndroidSourceSet mainSourceSet
67     protected AndroidSourceSet testSourceSet
68
69     protected Task uninstallAll
70     protected Task assembleTest
71
72     abstract String getTarget()
73
74     protected void apply(Project project) {
75         this.project = project
76         project.apply plugin: JavaBasePlugin
77
78         project.tasks.assemble.description =
79             "Assembles all variants of all applications and secondary packages."
80
81         findSdk(project)
82
83         uninstallAll = project.tasks.add("uninstallAll")
84         uninstallAll.description = "Uninstall all applications."
85         uninstallAll.group = INSTALL_GROUP
86     }
87
88     protected setDefaultConfig(ProductFlavor defaultConfig,
89                                NamedDomainObjectContainer<AndroidSourceSet> sourceSets) {
90         mainSourceSet = sourceSets.create("main")
91         testSourceSet = sourceSets.create("test")
92
93         defaultConfigData = new ProductFlavorData(defaultConfig, mainSourceSet,
94                 testSourceSet, project)
95     }
96
97     ProductFlavorData getDefaultConfigData() {
98         return defaultConfigData
99     }
100
101     SdkParser getSdkParser() {
102         if (androidSdkParser == null) {
103             androidSdkParser = new DefaultSdkParser(sdkDir.absolutePath)
104         }
105
106         return androidSdkParser;
107     }
108
109     ILogger getLogger() {
110         if (loggerWrapper == null) {
111             loggerWrapper = new LoggerWrapper(project.logger)
112         }
113
114         return loggerWrapper
115     }
116
117     boolean isVerbose() {
118         return project.logger.isEnabled(LogLevel.DEBUG)
119     }
120
121     AndroidBuilder getAndroidBuilder(ApplicationVariant variant) {
122         AndroidBuilder androidBuilder = builders.get(variant)
123
124         if (androidBuilder == null) {
125             androidBuilder = variant.createBuilder(this)
126             builders.put(variant, androidBuilder)
127         }
128
129         return androidBuilder
130     }
131
132     private void findSdk(Project project) {
133         def rootDir = project.rootDir
134         def localProperties = new File(rootDir, SdkConstants.FN_LOCAL_PROPERTIES)
135         if (localProperties.exists()) {
136             Properties properties = new Properties()
137             localProperties.withInputStream { instr ->
138                 properties.load(instr)
139             }
140             def sdkDirProp = properties.getProperty('sdk.dir')
141             if (!sdkDirProp) {
142                 throw new RuntimeException("No sdk.dir property defined in local.properties file.")
143             }
144             sdkDir = new File(sdkDirProp)
145         } else {
146             def envVar = System.getenv("ANDROID_HOME")
147             if (envVar != null) {
148                 sdkDir = new File(envVar)
149             }
150         }
151
152         if (sdkDir == null) {
153             throw new RuntimeException(
154                     "SDK location not found. Define location with sdk.dir in the local.properties file or with an ANDROID_HOME environment variable.")
155         }
156
157         if (!sdkDir.directory) {
158             throw new RuntimeException(
159                     "The SDK directory '$sdkDir' specified in local.properties does not exist.")
160         }
161     }
162
163     protected String getRuntimeJars(ApplicationVariant variant) {
164         AndroidBuilder androidBuilder = getAndroidBuilder(variant)
165
166         return androidBuilder.runtimeClasspath.join(File.pathSeparator)
167     }
168
169     protected ProcessManifestTask createProcessManifestTask(ApplicationVariant variant,
170                                                         String manifestOurDir) {
171         def processManifestTask = project.tasks.add("process${variant.name}Manifest",
172                 ProcessManifestTask)
173         processManifestTask.plugin = this
174         processManifestTask.variant = variant
175         processManifestTask.configObjects = variant.configObjects
176         processManifestTask.conventionMapping.inputManifests = { variant.config.manifestInputs }
177         processManifestTask.conventionMapping.processedManifest = {
178             project.file(
179                     "$project.buildDir/${manifestOurDir}/$variant.dirName/AndroidManifest.xml")
180         }
181
182         return processManifestTask
183     }
184
185     protected CrunchResourcesTask createCrunchResTask(ApplicationVariant variant) {
186         def crunchTask = project.tasks.add("crunch${variant.name}Res", CrunchResourcesTask)
187         crunchTask.plugin = this
188         crunchTask.variant = variant
189         crunchTask.configObjects = variant.configObjects
190         crunchTask.conventionMapping.resDirectories = { variant.config.resourceInputs }
191         crunchTask.conventionMapping.outputDir = {
192             project.file("$project.buildDir/res/$variant.dirName")
193         }
194
195         return crunchTask
196     }
197
198     protected GenerateBuildConfigTask createBuildConfigTask(ApplicationVariant variant,
199                                                             ProcessManifestTask processManifestTask) {
200         def generateBuildConfigTask = project.tasks.add(
201                 "generate${variant.name}BuildConfig", GenerateBuildConfigTask)
202         if (processManifestTask != null) {
203             // This is in case the manifest is generated
204             generateBuildConfigTask.dependsOn processManifestTask
205         }
206         generateBuildConfigTask.plugin = this
207         generateBuildConfigTask.variant = variant
208         generateBuildConfigTask.configObjects = variant.configObjects
209         generateBuildConfigTask.optionalJavaLines = variant.buildConfigLines
210         generateBuildConfigTask.conventionMapping.sourceOutputDir = {
211             project.file("$project.buildDir/source/${variant.dirName}")
212         }
213         return generateBuildConfigTask
214     }
215
216     protected ProcessResourcesTask createProcessResTask(ApplicationVariant variant,
217                                                     ProcessManifestTask processManifestTask,
218                                                     CrunchResourcesTask crunchTask) {
219         def processResources = project.tasks.add("process${variant.name}Res", ProcessResourcesTask)
220         processResources.dependsOn processManifestTask
221         processResources.plugin = this
222         processResources.variant = variant
223         processResources.configObjects = variant.configObjects
224         processResources.conventionMapping.manifestFile = { processManifestTask.processedManifest }
225         // TODO: unify with generateBuilderConfig, compileAidl, and library packaging somehow?
226         processResources.conventionMapping.sourceOutputDir = {
227             project.file("$project.buildDir/source/$variant.dirName")
228         }
229         processResources.conventionMapping.textSymbolDir = {
230             project.file("$project.buildDir/symbols/$variant.dirName")
231         }
232         processResources.conventionMapping.packageFile = {
233             project.file(
234                     "$project.buildDir/libs/${project.archivesBaseName}-${variant.baseName}.ap_")
235         }
236         if (variant.runProguard) {
237             processResources.conventionMapping.proguardFile = {
238                 project.file("$project.buildDir/proguard/${variant.dirName}/rules.txt")
239             }
240         }
241
242         if (crunchTask != null) {
243             processResources.dependsOn crunchTask
244             processResources.conventionMapping.crunchDir = { crunchTask.outputDir }
245             processResources.conventionMapping.resDirectories = { crunchTask.resDirectories }
246         } else {
247             processResources.conventionMapping.resDirectories = { variant.config.resourceInputs }
248         }
249
250         processResources.aaptOptions = extension.aaptOptions
251         return processResources
252     }
253
254     protected void createProcessJavaResTask(ApplicationVariant variant) {
255         VariantConfiguration config = variant.config
256
257         Copy processResources = project.getTasks().add("process${variant.name}JavaRes",
258                 ProcessResources.class);
259
260         // set the input
261         processResources.from(((AndroidSourceSet) config.defaultSourceSet).javaResources)
262
263         if (config.getType() != VariantConfiguration.Type.TEST) {
264             processResources.from(((AndroidSourceSet) config.buildTypeSourceSet).javaResources)
265         }
266         if (config.hasFlavors()) {
267             for (SourceProvider flavorSourceSet : config.flavorSourceSets) {
268                 processResources.from(((AndroidSourceSet) flavorSourceSet).javaResources)
269             }
270         }
271
272         processResources.conventionMapping.destinationDir = {
273             project.file("$project.buildDir/javaResources/$variant.dirName")
274         }
275
276         variant.processJavaResources = processResources
277     }
278
279     protected CompileAidlTask createAidlTask(ApplicationVariant variant) {
280
281         VariantConfiguration config = variant.config
282
283         def compileTask = project.tasks.add("compile${variant.name}Aidl", CompileAidlTask)
284         compileTask.plugin = this
285         compileTask.variant = variant
286         compileTask.configObjects = variant.configObjects
287
288         List<Object> sourceList = new ArrayList<Object>();
289         sourceList.add(config.defaultSourceSet.aidlDir)
290         if (config.getType() != VariantConfiguration.Type.TEST) {
291             sourceList.add(config.buildTypeSourceSet.aidlDir)
292         }
293         if (config.hasFlavors()) {
294             for (SourceProvider flavorSourceSet : config.flavorSourceSets) {
295                 sourceList.add(flavorSourceSet.aidlDir)
296             }
297         }
298
299         compileTask.sourceDirs = sourceList
300         compileTask.importDirs = variant.config.aidlImports
301
302         compileTask.conventionMapping.sourceOutputDir = {
303             project.file("$project.buildDir/source/$variant.dirName")
304         }
305
306         return compileTask
307     }
308
309     protected void createCompileTask(ApplicationVariant variant,
310                                      ApplicationVariant testedVariant,
311                                      ProcessResourcesTask processResources,
312                                      GenerateBuildConfigTask generateBuildConfigTask,
313                                      CompileAidlTask aidlTask) {
314         def compileTask = project.tasks.add("compile${variant.name}", JavaCompile)
315         compileTask.dependsOn processResources, generateBuildConfigTask, aidlTask
316
317         VariantConfiguration config = variant.config
318
319         List<Object> sourceList = new ArrayList<Object>();
320         sourceList.add(((AndroidSourceSet) config.defaultSourceSet).java)
321         sourceList.add({ processResources.sourceOutputDir })
322         if (config.getType() != VariantConfiguration.Type.TEST) {
323             sourceList.add(((AndroidSourceSet) config.buildTypeSourceSet).java)
324         }
325         if (config.hasFlavors()) {
326             for (SourceProvider flavorSourceSet : config.flavorSourceSets) {
327                 sourceList.add(((AndroidSourceSet) flavorSourceSet).java)
328             }
329         }
330         compileTask.source = sourceList.toArray()
331
332         if (testedVariant != null) {
333             compileTask.classpath = project.files({config.compileClasspath}) + testedVariant.compileTask.classpath + testedVariant.compileTask.outputs.files
334         } else {
335             compileTask.classpath = project.files({config.compileClasspath})
336         }
337
338         // TODO - dependency information for the compile classpath is being lost.
339         // Add a temporary approximation
340         compileTask.dependsOn project.configurations.compile.buildDependencies
341
342         compileTask.conventionMapping.destinationDir = {
343             project.file("$project.buildDir/classes/$variant.dirName")
344         }
345         compileTask.conventionMapping.dependencyCacheDir = {
346             project.file("$project.buildDir/dependency-cache/$variant.dirName")
347         }
348
349         // set source/target compatibility
350         // TODO: fix?
351         JavaPluginConvention convention = project.convention.getPlugin(JavaPluginConvention.class);
352
353         compileTask.conventionMapping.sourceCompatibility = {
354             convention.sourceCompatibility.toString()
355         }
356         compileTask.conventionMapping.targetCompatibility = {
357             convention.targetCompatibility.toString()
358         }
359
360         // setup the bootclasspath just before the task actually runs since this will
361         // force the sdk to be parsed.
362         compileTask.doFirst {
363             compileTask.options.bootClasspath = getRuntimeJars(variant)
364         }
365
366         // Wire up the outputs
367         variant.resourcePackage = project.files({processResources.packageFile}) {
368             builtBy processResources
369         }
370         variant.compileTask = compileTask
371     }
372
373     protected void createTestTasks(TestAppVariant variant, ProductionAppVariant testedVariant) {
374         // Add a task to process the manifest
375         def processManifestTask = createProcessManifestTask(variant, "manifests")
376
377         // Add a task to crunch resource files
378         def crunchTask = createCrunchResTask(variant)
379
380         if (testedVariant.config.type == VariantConfiguration.Type.LIBRARY) {
381             // in this case the tested library must be fully built before test can be built!
382             if (testedVariant.assembleTask != null) {
383                 processManifestTask.dependsOn testedVariant.assembleTask
384                 crunchTask.dependsOn testedVariant.assembleTask
385             }
386         }
387
388         // Add a task to create the BuildConfig class
389         def generateBuildConfigTask = createBuildConfigTask(variant, processManifestTask)
390
391         // Add a task to generate resource source files
392         def processResources = createProcessResTask(variant, processManifestTask, crunchTask)
393
394         // process java resources
395         createProcessJavaResTask(variant)
396
397         def compileAidl = createAidlTask(variant)
398
399         // Add a task to compile the test application
400         createCompileTask(variant, testedVariant, processResources, generateBuildConfigTask,
401                 compileAidl)
402
403         addPackageTasks(variant, null)
404
405         if (assembleTest != null) {
406             assembleTest.dependsOn variant.assembleTask
407         }
408
409         // create the check task for this test
410         def checkTask = project.tasks.add("check${testedVariant.name}", DefaultTask)
411         checkTask.description = "Installs and runs the checks for Build ${testedVariant.name}."
412         checkTask.group = JavaBasePlugin.VERIFICATION_GROUP
413
414         checkTask.dependsOn testedVariant.assembleTask, variant.assembleTask
415         project.tasks.check.dependsOn checkTask
416
417         // now run the test.
418         def runTestsTask = project.tasks.add("run${testedVariant.name}Tests", RunTestsTask)
419         runTestsTask.description = "Runs the checks for Build ${testedVariant.name}. Must be installed on device."
420         runTestsTask.group = JavaBasePlugin.VERIFICATION_GROUP
421         runTestsTask.sdkDir = sdkDir
422         runTestsTask.variant = variant
423         checkTask.doLast { runTestsTask }
424
425         // TODO: don't rely on dependsOn which isn't reliable for execution order.
426         if (testedVariant.config.type == VariantConfiguration.Type.DEFAULT) {
427             checkTask.dependsOn testedVariant.installTask, variant.installTask, runTestsTask, testedVariant.uninstallTask, variant.uninstallTask
428         } else {
429             checkTask.dependsOn variant.installTask, runTestsTask, variant.uninstallTask
430         }
431     }
432
433     /**
434      * Creates the packaging tasks for the given Variant.
435      * @param variant the variant.
436      * @param assembleTask an optional assembleTask to be used. If null a new one is created. The
437      *                assembleTask is always set in the Variant.
438      */
439     protected void addPackageTasks(ApplicationVariant variant, Task assembleTask) {
440         // Add a dex task
441         def dexTaskName = "dex${variant.name}"
442         def dexTask = project.tasks.add(dexTaskName, DexTask)
443         dexTask.dependsOn variant.compileTask
444         dexTask.plugin = this
445         dexTask.variant = variant
446         dexTask.conventionMapping.libraries = { project.files({ variant.config.packagedJars }) }
447         dexTask.conventionMapping.sourceFiles = { variant.compileTask.outputs.files }
448         dexTask.conventionMapping.outputFile = {
449             project.file(
450                     "${project.buildDir}/libs/${project.archivesBaseName}-${variant.baseName}.dex")
451         }
452         dexTask.dexOptions = extension.dexOptions
453
454         // Add a task to generate application package
455         def packageApp = project.tasks.add("package${variant.name}", PackageApplicationTask)
456         packageApp.dependsOn variant.resourcePackage, dexTask, variant.processJavaResources
457         packageApp.plugin = this
458         packageApp.variant = variant
459         packageApp.configObjects = variant.configObjects
460
461         def signedApk = variant.isSigned()
462
463         def apkName = signedApk ?
464             "${project.archivesBaseName}-${variant.baseName}-unaligned.apk" :
465             "${project.archivesBaseName}-${variant.baseName}-unsigned.apk"
466
467         packageApp.conventionMapping.outputFile = {
468             project.file("$project.buildDir/apk/${apkName}")
469         }
470         packageApp.conventionMapping.resourceFile = { variant.resourcePackage.singleFile }
471         packageApp.conventionMapping.dexFile = { dexTask.outputFile }
472         packageApp.conventionMapping.javaResourceDir = {
473             variant.processJavaResources.destinationDir
474         }
475
476         def appTask = packageApp
477
478         if (signedApk) {
479             if (variant.zipAlign) {
480                 // Add a task to zip align application package
481                 def alignApp = project.tasks.add("zipalign${variant.name}", ZipAlignTask)
482                 alignApp.dependsOn packageApp
483                 alignApp.conventionMapping.inputFile = { packageApp.outputFile }
484                 alignApp.conventionMapping.outputFile = {
485                     project.file(
486                             "$project.buildDir/apk/${project.archivesBaseName}-${variant.baseName}.apk")
487                 }
488                 alignApp.sdkDir = sdkDir
489
490                 appTask = alignApp
491             }
492
493             // Add a task to install the application package
494             def installTask = project.tasks.add("install${variant.name}", InstallTask)
495             installTask.description = "Installs the " + variant.description
496             installTask.group = INSTALL_GROUP
497             installTask.dependsOn appTask
498             installTask.conventionMapping.packageFile = { appTask.outputFile }
499             installTask.sdkDir = sdkDir
500
501             variant.installTask = installTask
502         }
503
504         // Add an assemble task
505         if (assembleTask == null) {
506             assembleTask = project.tasks.add("assemble${variant.name}")
507             assembleTask.description = "Assembles the " + variant.description
508             assembleTask.group = org.gradle.api.plugins.BasePlugin.BUILD_GROUP
509         }
510         assembleTask.dependsOn appTask
511         variant.assembleTask = assembleTask
512
513         // add an uninstall task
514         def uninstallTask = project.tasks.add("uninstall${variant.name}", UninstallTask)
515         uninstallTask.description = "Uninstalls the " + variant.description
516         uninstallTask.group = INSTALL_GROUP
517         uninstallTask.variant = variant
518         uninstallTask.sdkDir = sdkDir
519
520         variant.uninstallTask = uninstallTask
521         uninstallAll.dependsOn uninstallTask
522     }
523
524     PrepareDependenciesTask createPrepareDependenciesTask(ProductionAppVariant variant) {
525         // TODO - include variant specific dependencies too
526         def compileClasspath = project.configurations.compile
527
528         // TODO - shouldn't need to do this - fix this in Gradle
529         ensureConfigured(compileClasspath)
530
531         def prepareDependenciesTask = project.tasks.add("prepare${variant.name}Dependencies",
532                 PrepareDependenciesTask)
533         prepareDependenciesTask.plugin = this
534         prepareDependenciesTask.variant = variant
535
536         // TODO - should be able to infer this
537         prepareDependenciesTask.dependsOn compileClasspath
538
539         def checker = new DependencyChecker(variant, logger)
540
541         // TODO - defer downloading until required
542         // TODO - build the library dependency graph
543         List<AndroidDependency> bundles = []
544         List<JarDependency> jars = []
545         Map<ModuleVersionIdentifier, List<AndroidDependency>> modules = [:]
546         Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts = [:]
547         collectArtifacts(compileClasspath, artifacts)
548         compileClasspath.resolvedConfiguration.resolutionResult.root.dependencies.each { ResolvedDependencyResult dep ->
549             addDependency(dep.selected, prepareDependenciesTask, checker,
550                     bundles, jars, modules, artifacts)
551         }
552
553         variant.config.androidDependencies = bundles
554         variant.config.jarDependencies = jars
555
556         // TODO - filter bundles out of source set classpath
557
558         return prepareDependenciesTask
559     }
560
561     def ensureConfigured(Configuration config) {
562         config.allDependencies.withType(ProjectDependency).each { dep ->
563             project.evaluationDependsOn(dep.dependencyProject.path)
564             ensureConfigured(dep.projectConfiguration)
565         }
566     }
567
568     def collectArtifacts(Configuration configuration, Map<ModuleVersionIdentifier,
569                          List<ResolvedArtifact>> artifacts) {
570         configuration.resolvedConfiguration.resolvedArtifacts.each { ResolvedArtifact artifact ->
571             def id = artifact.moduleVersion.id
572             List<ResolvedArtifact> moduleArtifacts = artifacts[id]
573             if (moduleArtifacts == null) {
574                 moduleArtifacts = []
575                 artifacts[id] = moduleArtifacts
576             }
577             moduleArtifacts << artifact
578         }
579     }
580
581     def addDependency(ResolvedModuleVersionResult moduleVersion,
582                       PrepareDependenciesTask prepareDependenciesTask,
583                       DependencyChecker checker,
584                       Collection<AndroidDependency> bundles,
585                       Collection<JarDependency> jars,
586                       Map<ModuleVersionIdentifier, List<AndroidDependency>> modules,
587                       Map<ModuleVersionIdentifier, List<ResolvedArtifact>> artifacts) {
588         def id = moduleVersion.id
589         if (checker.excluded(id)) {
590             return
591         }
592
593         List<AndroidDependency> bundlesForThisModule = modules[id]
594         if (bundlesForThisModule == null) {
595             bundlesForThisModule = []
596             modules[id] = bundlesForThisModule
597
598             def nestedBundles = []
599             moduleVersion.dependencies.each { ResolvedDependencyResult dep ->
600                 addDependency(dep.selected, prepareDependenciesTask, checker, nestedBundles,
601                         jars, modules, artifacts)
602             }
603
604             def moduleArtifacts = artifacts[id]
605             moduleArtifacts?.each { artifact ->
606                 if (artifact.type == 'alb') {
607                     def explodedDir = project.file(
608                             "$project.buildDir/exploded-bundles/$artifact.file.name")
609                     bundlesForThisModule << new AndroidDependencyImpl(explodedDir, nestedBundles)
610                     prepareDependenciesTask.add(artifact.file, explodedDir)
611                 } else {
612                     // TODO - need the correct values for the boolean flags
613                     jars << new JarDependency(artifact.file.absolutePath, true, true, true)
614                 }
615             }
616
617             if (bundlesForThisModule.empty && !nestedBundles.empty) {
618                 throw new GradleException("Module version $id depends on libraries but is not a library itself")
619             }
620         }
621
622         bundles.addAll(bundlesForThisModule)
623     }
624 }
625