aboutsummaryrefslogtreecommitdiff
path: root/gradle/src/main/groovy/com/android/build/gradle/LibraryPlugin.groovy
blob: ba86d356ceb54e81b01fa2dd2010596cbaa37167 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.build.gradle
import com.android.SdkConstants
import com.android.annotations.NonNull
import com.android.annotations.Nullable
import com.android.build.gradle.api.AndroidSourceSet
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.internal.BuildTypeData
import com.android.build.gradle.internal.ProductFlavorData
import com.android.build.gradle.internal.api.DefaultAndroidSourceSet
import com.android.build.gradle.internal.api.LibraryVariantImpl
import com.android.build.gradle.internal.api.TestVariantImpl
import com.android.build.gradle.internal.dependency.VariantDependencies
import com.android.build.gradle.internal.tasks.MergeFileTask
import com.android.build.gradle.internal.variant.BaseVariantData
import com.android.build.gradle.internal.variant.LibraryVariantData
import com.android.build.gradle.internal.variant.TestVariantData
import com.android.build.gradle.tasks.MergeResources
import com.android.builder.BuilderConstants
import com.android.builder.DefaultBuildType
import com.android.builder.VariantConfiguration
import com.android.builder.dependency.DependencyContainer
import com.android.builder.dependency.JarDependency
import com.android.builder.dependency.LibraryBundle
import com.android.builder.dependency.LibraryDependency
import com.android.builder.dependency.ManifestDependency
import com.android.builder.model.AndroidLibrary
import com.google.common.collect.Maps
import com.google.common.collect.Sets
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.plugins.MavenPlugin
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.Sync
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.bundling.Zip
import org.gradle.internal.reflect.Instantiator
import org.gradle.tooling.BuildException
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry

import javax.inject.Inject
/**
 * Gradle plugin class for 'library' projects.
 */
public class LibraryPlugin extends BasePlugin implements Plugin<Project> {

    LibraryExtension extension
    BuildTypeData debugBuildTypeData
    BuildTypeData releaseBuildTypeData

    @Inject
    public LibraryPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
        super(instantiator, registry)
    }

    @Override
    void apply(Project project) {
        super.apply(project)

        extension = project.extensions.create('android', LibraryExtension,
                this, (ProjectInternal) project, instantiator)
        setBaseExtension(extension);

        // create the source sets for the build type.
        // the ones for the main product flavors are handled by the base plugin.
        DefaultAndroidSourceSet debugSourceSet =
            (DefaultAndroidSourceSet) extension.sourceSetsContainer.create(BuilderConstants.DEBUG)
        DefaultAndroidSourceSet releaseSourceSet =
            (DefaultAndroidSourceSet) extension.sourceSetsContainer.create(BuilderConstants.RELEASE)

        debugBuildTypeData = new BuildTypeData(extension.debug, debugSourceSet, project)
        releaseBuildTypeData = new BuildTypeData(extension.release, releaseSourceSet, project)
        project.tasks.assemble.dependsOn debugBuildTypeData.assembleTask
        project.tasks.assemble.dependsOn releaseBuildTypeData.assembleTask

        createConfigurations(releaseSourceSet)
    }

    void createConfigurations(AndroidSourceSet releaseSourceSet) {
        // The library artifact is published for the "default" configuration so we make
        // sure "default" extends from the actual configuration used for building.
        project.configurations["default"].extendsFrom(
                project.configurations[mainSourceSet.getPackageConfigurationName()])
        project.configurations["default"].extendsFrom(
                project.configurations[releaseSourceSet.getPackageConfigurationName()])

        project.plugins.withType(MavenPlugin) {
            project.conf2ScopeMappings.addMapping(300,
                    project.configurations[mainSourceSet.getCompileConfigurationName()],
                    "compile")
            project.conf2ScopeMappings.addMapping(300,
                    project.configurations[releaseSourceSet.getCompileConfigurationName()],
                    "compile")
            // TODO -- figure out the package configuration
//            project.conf2ScopeMappings.addMapping(300,
//                    project.configurations[mainSourceSet.getPackageConfigurationName()],
//                    "runtime")
//            project.conf2ScopeMappings.addMapping(300,
//                    project.configurations[releaseSourceSet.getPackageConfigurationName()],
//                    "runtime")
        }
    }

    @Override
    protected void doCreateAndroidTasks() {
        ProductFlavorData defaultConfigData = getDefaultConfigData()
        VariantDependencies variantDep

        LibraryVariantData debugVariantData = createLibVariant(defaultConfigData,
                debugBuildTypeData)
        LibraryVariantData releaseVariantData = createLibVariant(defaultConfigData,
                releaseBuildTypeData)

        // Add a compile lint task before library is bundled
        createLintCompileTask()

        // Need to create the tasks for these before doing the test variant as it
        // references the debug variant and its output
        createLibraryVariant(debugVariantData, false)
        createLibraryVariant(releaseVariantData, true)

        VariantConfiguration testVariantConfig = new VariantConfiguration(
                defaultConfigData.productFlavor,
                defaultConfigData.testSourceSet,
                debugBuildTypeData.buildType,
                null,
                VariantConfiguration.Type.TEST,
                debugVariantData.variantConfiguration,
                project.name)

        TestVariantData testVariantData = new TestVariantData(testVariantConfig, debugVariantData)
        // link the testVariant to the tested variant in the other direction
        debugVariantData.setTestVariantData(testVariantData);

        // dependencies for the test variant
        variantDep = VariantDependencies.compute(project, testVariantData.name,
                defaultConfigData.testProvider, debugVariantData.variantDependency)
        testVariantData.setVariantDependency(variantDep)

        variantDataList.add(testVariantData)

        createTestVariant(testVariantData, debugVariantData)

        // create the lint tasks.
        createLintTasks()

        // create the test tasks.
        createCheckTasks(false /*hasFlavors*/, true /*isLibrary*/)

        // Create the variant API objects after the tasks have been created!
        createApiObjects()
    }

    protected LibraryVariantData createLibVariant(@NonNull ProductFlavorData configData,
                                                  @NonNull BuildTypeData buildTypeData) {
        VariantConfiguration variantConfig = new VariantConfiguration(
                configData.productFlavor,
                configData.sourceSet,
                buildTypeData.buildType,
                buildTypeData.sourceSet,
                VariantConfiguration.Type.LIBRARY,
                project.name)

        LibraryVariantData variantData = new LibraryVariantData(variantConfig)

        VariantDependencies debugVariantDep = VariantDependencies.compute(
                project, variantData.name,
                buildTypeData, configData.mainProvider)
        variantData.setVariantDependency(debugVariantDep)

        variantDataList.add(variantData)

        return variantData
    }

    private void createLibraryVariant(@NonNull LibraryVariantData variantData,
                                      boolean publishArtifact) {
        resolveDependencies(variantData.variantDependency)
        variantData.variantConfiguration.setDependencies(variantData.variantDependency)

        VariantConfiguration variantConfig = variantData.variantConfiguration
        DefaultBuildType buildType = variantConfig.buildType

        createAnchorTasks(variantData)

        // Add a task to process the manifest(s)
        createProcessManifestTask(variantData, DIR_BUNDLES)

        // Add a task to compile renderscript files.
        createRenderscriptTask(variantData)

        // Add a task to merge the resource folders, including the libraries, in order to
        // generate the R.txt file with all the symbols, including the ones from the dependencies.
        createMergeResourcesTask(variantData, false /*process9Patch*/)

        // Create another merge task to only merge the resources from this libraries and not
        // the dependencies. This is what gets packaged in the aar.
        MergeResources packageRes = basicCreateMergeResourcesTask(variantData,
                "package",
                "$project.buildDir/$DIR_BUNDLES/${variantData.dirName}/res",
                false /*includeDependencies*/,
                false /*process9Patch*/)

        // Add a task to merge the assets folders
        createMergeAssetsTask(variantData,
                "$project.buildDir/$DIR_BUNDLES/${variantData.dirName}/assets",
                false /*includeDependencies*/)

        // Add a task to create the BuildConfig class
        createBuildConfigTask(variantData)

        // Add a task to generate resource source files, directing the location
        // of the r.txt file to be directly in the bundle.
        createProcessResTask(variantData, "$project.buildDir/$DIR_BUNDLES/${variantData.dirName}")

        // process java resources
        createProcessJavaResTask(variantData)

        createAidlTask(variantData)

        // Add a compile task
        createCompileTask(variantData, null/*testedVariant*/)

        // Add NDK tasks
        createNdkTasks(
                variantData,
                { project.file("$project.buildDir/$DIR_BUNDLES/${variantData.dirName}/jni") });

        // package the aidl files into the bundle folder
        Sync packageAidl = project.tasks.create("package${variantData.name}Aidl", Sync)
        // packageAidl from 3 sources. the order is important to make sure the override works well.
        packageAidl.from(variantConfig.aidlSourceList).include("**/*.aidl")
        packageAidl.into(project.file(
                "$project.buildDir/$DIR_BUNDLES/${variantData.dirName}/$SdkConstants.FD_AIDL"))

        // package the renderscript header files files into the bundle folder
        Sync packageRenderscript = project.tasks.create("package${variantData.name}Renderscript",
                Sync)
        // package from 3 sources. the order is important to make sure the override works well.
        packageRenderscript.from(variantConfig.renderscriptSourceList).include("**/*.rsh")
        packageRenderscript.into(project.file(
                "$project.buildDir/$DIR_BUNDLES/${variantData.dirName}/$SdkConstants.FD_RENDERSCRIPT"))

        // merge consumer proguard files from different build types and flavors
        MergeFileTask mergeProGuardFileTask = project.tasks.create("merge${variantData.name}ProguardFiles",
                MergeFileTask)
        mergeProGuardFileTask.conventionMapping.inputFiles = {
            project.files(variantConfig.getConsumerProguardFiles()).files }
        mergeProGuardFileTask.conventionMapping.outputFile = {
            project.file(
                    "$project.buildDir/$DIR_BUNDLES/${variantData.dirName}/$LibraryBundle.FN_PROGUARD_TXT")
        }

        // copy lint.jar into the bundle folder
        Copy lintCopy = project.tasks.create("copy${variantData.name}Lint", Copy)
        lintCopy.dependsOn lintCompile
        lintCopy.from("$project.buildDir/lint/lint.jar")
        lintCopy.into("$project.buildDir/$DIR_BUNDLES/$variantData.dirName")

        Zip bundle = project.tasks.create("bundle${variantData.name}", Zip)

        if (variantConfig.buildType.runProguard) {
            // run proguard on output of compile task
            createProguardTasks(variantData, null)

            // hack since bundle can't depend on variantData.proguardTask
            mergeProGuardFileTask.dependsOn variantData.proguardTask

            bundle.dependsOn packageRes, packageAidl, packageRenderscript, mergeProGuardFileTask, lintCopy, variantData.ndkCompileTask
        } else {
            Sync packageLocalJar = project.tasks.create("package${variantData.name}LocalJar", Sync)
            packageLocalJar.from(getLocalJarFileList(variantData.variantDependency))
            packageLocalJar.into(project.file(
                    "$project.buildDir/$DIR_BUNDLES/${variantData.dirName}/$SdkConstants.LIBS_FOLDER"))

            // jar the classes.
            Jar jar = project.tasks.create("package${buildType.name.capitalize()}Jar", Jar);
            jar.dependsOn variantData.javaCompileTask, variantData.processJavaResourcesTask
            jar.from(variantData.javaCompileTask.outputs);
            jar.from(variantData.processJavaResourcesTask.destinationDir)

            jar.destinationDir = project.file("$project.buildDir/$DIR_BUNDLES/${variantData.dirName}")
            jar.archiveName = "classes.jar"

            String packageName = variantConfig.getPackageFromManifest()
            if (packageName == null) {
                throw new BuildException("Failed to read manifest", null)
            }
            packageName = packageName.replace('.', '/');

            jar.exclude(packageName + "/R.class")
            jar.exclude(packageName + "/R\$*.class")
            jar.exclude(packageName + "/Manifest.class")
            jar.exclude(packageName + "/Manifest\$*.class")
            jar.exclude(packageName + "/BuildConfig.class")

            bundle.dependsOn packageRes, jar, packageAidl, packageRenderscript, packageLocalJar,
                    mergeProGuardFileTask, lintCopy, variantData.ndkCompileTask
        }

        bundle.setDescription("Assembles a bundle containing the library in ${variantData.name}.");
        bundle.destinationDir = project.file("$project.buildDir/libs")
        bundle.extension = BuilderConstants.EXT_LIB_ARCHIVE
        if (variantData.baseName != BuilderConstants.RELEASE) {
            bundle.classifier = variantData.baseName
        }
        bundle.from(project.file("$project.buildDir/$DIR_BUNDLES/${variantData.dirName}"))

        variantData.packageLibTask = bundle
        variantData.outputFile = bundle.archivePath

        if (publishArtifact) {
            // add the artifact that will be published
            project.artifacts.add("default", bundle)
            releaseBuildTypeData.assembleTask.dependsOn bundle
        } else {
            debugBuildTypeData.assembleTask.dependsOn bundle
        }

        variantData.assembleTask = bundle

        // configure the variant to be testable.
        variantConfig.output = new LibraryBundle(
                bundle.archivePath,
                project.file("$project.buildDir/$DIR_BUNDLES/${variantData.dirName}"),
                variantData.getName()) {

            @Nullable
            @Override
            String getProject() {
                return LibraryPlugin.this.project.path
            }

            @NonNull
            @Override
            List<LibraryDependency> getDependencies() {
                return variantConfig.directLibraries
            }

            @NonNull
            @Override
            List<? extends AndroidLibrary> getLibraryDependencies() {
                return variantConfig.directLibraries
            }

            @NonNull
            @Override
            List<ManifestDependency> getManifestDependencies() {
                return variantConfig.directLibraries
            }
        };
    }

    static Object[] getLocalJarFileList(DependencyContainer dependencyContainer) {
        Set<File> files = Sets.newHashSet()
        for (JarDependency jarDependency : dependencyContainer.localDependencies) {
            files.add(jarDependency.jarFile)
        }

        return files.toArray()
    }

    private void createTestVariant(@NonNull TestVariantData testVariantData,
                                   @NonNull LibraryVariantData testedVariantData) {

        resolveDependencies(testVariantData.variantDependency)
        testVariantData.variantConfiguration.setDependencies(testVariantData.variantDependency)

        createTestApkTasks(testVariantData, testedVariantData)
    }

    protected void createApiObjects() {
        // we always want to have the test/tested objects created at the same time
        // so that dynamic closure call on add can have referenced objects created.
        // This means some objects are created before they are processed from the loop,
        // so we store whether we have processed them or not.
        Map<BaseVariantData, BaseVariant> map = Maps.newHashMap()
        for (BaseVariantData variantData : variantDataList) {
            if (map.get(variantData) != null) {
                continue
            }

            if (variantData instanceof LibraryVariantData) {
                LibraryVariantData libVariantData = (LibraryVariantData) variantData
                createVariantApiObjects(map, libVariantData, libVariantData.testVariantData)

            } else if (variantData instanceof TestVariantData) {
                TestVariantData testVariantData = (TestVariantData) variantData
                createVariantApiObjects(map,
                        (LibraryVariantData) testVariantData.testedVariantData,
                        testVariantData)
            }
        }
    }

    private void createVariantApiObjects(@NonNull Map<BaseVariantData, BaseVariant> map,
                                         @NonNull LibraryVariantData libVariantData,
                                         @Nullable TestVariantData testVariantData) {
        LibraryVariantImpl libVariant = instantiator.newInstance(
                LibraryVariantImpl.class, libVariantData)

        TestVariantImpl testVariant = null;
        if (testVariantData != null) {
            testVariant = instantiator.newInstance(TestVariantImpl.class, testVariantData)
        }

        if (libVariant != null && testVariant != null) {
            libVariant.setTestVariant(testVariant)
            testVariant.setTestedVariant(libVariant)
        }

        extension.addLibraryVariant(libVariant)
        map.put(libVariantData, libVariant)

        if (testVariant != null) {
            extension.addTestVariant(testVariant)
            map.put(testVariantData, testVariant)
        }
    }
}