1 /*
<lambda>null2  * Copyright 2020 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 
17 package androidx.build
18 
19 import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
20 import com.github.jengelman.gradle.plugins.shadow.transformers.Transformer
21 import com.github.jengelman.gradle.plugins.shadow.transformers.TransformerContext
22 import org.apache.tools.zip.ZipOutputStream
23 import org.gradle.api.Project
24 import org.gradle.api.artifacts.Configuration
25 import org.gradle.api.attributes.Usage
26 import org.gradle.api.file.FileTreeElement
27 import org.gradle.api.tasks.Input
28 import org.gradle.api.tasks.Optional
29 import org.gradle.api.tasks.SourceSetContainer
30 import org.gradle.api.tasks.TaskProvider
31 import org.gradle.kotlin.dsl.named
32 
33 /** Allow java and Android libraries to bundle other projects inside the project jar/aar. */
34 object BundleInsideHelper {
35     val CONFIGURATION_NAME = "bundleInside"
36     val REPACKAGE_TASK_NAME = "repackageBundledJars"
37 
38     /**
39      * Creates a configuration for the users to use that will be used to bundle these dependency
40      * jars inside of libs/ directory inside of the aar.
41      *
42      * ```
43      * dependencies {
44      *   bundleInside(project(":foo"))
45      * }
46      * ```
47      *
48      * Used project are expected
49      *
50      * @param relocations a list of package relocations to apply
51      * @param dropResourcesWithSuffix used to drop Java resources if they match this suffix, null
52      *   means no filtering
53      * @receiver the project that should bundle jars specified by this configuration
54      * @see forInsideAar(String, String)
55      */
56     @JvmStatic
57     fun Project.forInsideAar(relocations: List<Relocation>?, dropResourcesWithSuffix: String?) {
58         val bundle = createBundleConfiguration()
59         val repackage = configureRepackageTaskForType(relocations, bundle, dropResourcesWithSuffix)
60         // Add to AGP's configuration so this jar get packaged inside of the aar.
61         dependencies.add("implementation", files(repackage.flatMap { it.archiveFile }))
62     }
63 
64     /**
65      * Creates 3 configurations for the users to use that will be used bundle these dependency jars
66      * inside of libs/ directory inside of the aar.
67      *
68      * ```
69      * dependencies {
70      *   bundleInside(project(":foo"))
71      * }
72      * ```
73      *
74      * Used project are expected
75      *
76      * @param from specifies from which package the rename should happen
77      * @param to specifies to which package to put the renamed classes
78      * @param dropResourcesWithSuffix used to drop Java resources if they match this suffix, null
79      *   means no filtering
80      * @receiver the project that should bundle jars specified by these configurations
81      */
82     @JvmStatic
83     fun Project.forInsideAar(from: String, to: String, dropResourcesWithSuffix: String?) {
84         forInsideAar(listOf(Relocation(from, to)), dropResourcesWithSuffix)
85     }
86 
87     /**
88      * Creates a configuration for users to use that will bundle the dependency jars inside of this
89      * lint check's jar. This is required because lintPublish does not currently support
90      * dependencies, so instead we need to bundle any dependencies with the lint jar manually.
91      * (b/182319899)
92      *
93      * ```
94      * dependencies {
95      *     if (rootProject.hasProperty("android.injected.invoked.from.ide")) {
96      *         compileOnly(LINT_API_LATEST)
97      *     } else {
98      *         compileOnly(LINT_API_MIN)
99      *     }
100      *     compileOnly(KOTLIN_STDLIB)
101      *     // Include this library inside the resulting lint jar
102      *     bundleInside(project(":foo-lint-utils"))
103      * }
104      * ```
105      *
106      * @receiver the project that should bundle jars specified by these configurations
107      */
108     @JvmStatic
109     fun Project.forInsideLintJar() {
110         val bundle = createBundleConfiguration()
111         val compileOnly = configurations.getByName("compileOnly")
112         val testImplementation = configurations.getByName("testImplementation")
113 
114         compileOnly.extendsFrom(bundle)
115         testImplementation.extendsFrom(bundle)
116 
117         // Relocation needed to avoid classpath conflicts with Android Studio (b/337980250)
118         // Can be removed if we migrate from using kotlin-metadata-jvm inside of lint checks
119         val relocations = listOf(Relocation("kotlin.metadata", "androidx.lint.kotlin.metadata"))
120         val repackage = configureRepackageTaskForType(relocations, bundle, null)
121         val sourceSets = extensions.getByType(SourceSetContainer::class.java)
122         repackage.configure { task ->
123             task.from(sourceSets.findByName("main")?.output)
124             // kotlin-metadata-jvm has a service descriptor that needs transformation
125             task.mergeServiceFiles()
126             // Exclude Kotlin metadata files from kotlin-metadata-jvm
127             task.exclude(
128                 "META-INF/kotlin-metadata-jvm.kotlin_module",
129                 "META-INF/kotlin-metadata.kotlin_module",
130                 "META-INF/metadata.jvm.kotlin_module",
131                 "META-INF/metadata.kotlin_module"
132             )
133         }
134 
135         listOf("apiElements", "runtimeElements").forEach { config ->
136             configurations.getByName(config).apply {
137                 outgoing.artifacts.clear()
138                 outgoing.artifact(repackage)
139             }
140         }
141     }
142 
143     data class Relocation(val from: String, val to: String)
144 
145     private fun Project.configureRepackageTaskForType(
146         relocations: List<Relocation>?,
147         configuration: Configuration,
148         dropResourcesWithSuffix: String?
149     ): TaskProvider<ShadowJar> {
150         return tasks.register(REPACKAGE_TASK_NAME, ShadowJar::class.java) { task ->
151             task.apply {
152                 configurations = listOf(configuration)
153                 if (relocations != null) {
154                     for (relocation in relocations) {
155                         relocate(relocation.from, relocation.to)
156                     }
157                 }
158                 val dontIncludeResourceTransformer = DontIncludeResourceTransformer()
159                 dontIncludeResourceTransformer.dropResourcesWithSuffix = dropResourcesWithSuffix
160                 transformers.add(dontIncludeResourceTransformer)
161                 archiveBaseName.set("repackaged")
162                 archiveVersion.set("")
163                 destinationDirectory.set(layout.buildDirectory.dir("repackaged"))
164             }
165         }
166     }
167 
168     private fun Project.createBundleConfiguration(): Configuration {
169         val bundle =
170             configurations.create(CONFIGURATION_NAME) {
171                 it.attributes {
172                     it.attribute(Usage.USAGE_ATTRIBUTE, objects.named<Usage>(Usage.JAVA_RUNTIME))
173                 }
174                 it.isCanBeConsumed = false
175             }
176         return bundle
177     }
178 
179     class DontIncludeResourceTransformer : Transformer {
180         @Optional @Input var dropResourcesWithSuffix: String? = null
181 
182         override fun getName(): String {
183             return "DontIncludeResourceTransformer"
184         }
185 
186         override fun canTransformResource(element: FileTreeElement?): Boolean {
187             val path = element?.relativePath?.pathString
188             return dropResourcesWithSuffix != null &&
189                 (path?.endsWith(dropResourcesWithSuffix!!) == true)
190         }
191 
192         override fun transform(context: TransformerContext?) {
193             // no op
194         }
195 
196         override fun hasTransformedResource(): Boolean {
197             return true
198         }
199 
200         override fun modifyOutputStream(zipOutputStream: ZipOutputStream?, b: Boolean) {
201             // no op
202         }
203     }
204 }
205