14
14
15
15
package com.google.firebase.gradle.plugins
16
16
17
- import com.android.build.api.transform.Format
18
- import com.android.build.api.transform.QualifiedContent
19
- import com.android.build.api.transform.Transform
20
- import com.android.build.api.transform.TransformInvocation
21
- import com.android.build.gradle.LibraryExtension
17
+ import com.android.build.api.artifact.ScopedArtifact
18
+ import com.android.build.api.variant.LibraryAndroidComponentsExtension
19
+ import com.android.build.api.variant.ScopedArtifacts
22
20
import com.android.build.gradle.LibraryPlugin
23
21
import java.io.BufferedInputStream
24
22
import java.io.BufferedOutputStream
@@ -28,42 +26,43 @@ import java.io.FileOutputStream
28
26
import java.util.zip.ZipEntry
29
27
import java.util.zip.ZipFile
30
28
import java.util.zip.ZipOutputStream
29
+ import javax.inject.Inject
30
+ import org.gradle.api.DefaultTask
31
31
import org.gradle.api.GradleException
32
32
import org.gradle.api.Plugin
33
33
import org.gradle.api.Project
34
34
import org.gradle.api.artifacts.Configuration
35
- import org.gradle.api.logging.Logger
35
+ import org.gradle.api.file.Directory
36
+ import org.gradle.api.file.RegularFile
37
+ import org.gradle.api.file.RegularFileProperty
38
+ import org.gradle.api.provider.ListProperty
36
39
import org.gradle.api.provider.Property
37
- import org.gradle.api.provider.Provider
40
+ import org.gradle.api.tasks.Classpath
41
+ import org.gradle.api.tasks.Input
42
+ import org.gradle.api.tasks.InputFiles
43
+ import org.gradle.api.tasks.OutputFile
44
+ import org.gradle.api.tasks.TaskAction
38
45
import org.gradle.kotlin.dsl.apply
39
- import org.gradle.kotlin.dsl.create
40
-
41
- abstract class VendorExtension {
42
- /* * Controls dead code elimination, enabled if true. */
43
- abstract val optimize: Property <Boolean >
44
-
45
- init {
46
- optimize.convention(true )
47
- }
48
- }
46
+ import org.gradle.kotlin.dsl.getByType
47
+ import org.gradle.process.ExecOperations
49
48
50
49
class VendorPlugin : Plugin <Project > {
51
50
override fun apply (project : Project ) {
52
- val vendorConfig = project.extensions.create<VendorExtension >(" vendor" )
53
51
project.plugins.all {
54
52
when (this ) {
55
- is LibraryPlugin -> configureAndroid(project, vendorConfig )
53
+ is LibraryPlugin -> configureAndroid(project)
56
54
}
57
55
}
58
56
}
59
57
60
- fun configureAndroid (project : Project , vendorConfig : VendorExtension ) {
58
+ fun configureAndroid (project : Project ) {
61
59
project.apply (plugin = " LicenseResolverPlugin" )
62
60
63
61
val vendor = project.configurations.create(" vendor" )
64
62
project.configurations.all {
65
63
when (name) {
66
- " compileOnly" ,
64
+ " releaseCompileOnly" ,
65
+ " debugImplementation" ,
67
66
" testImplementation" ,
68
67
" androidTestImplementation" -> extendsFrom(vendor)
69
68
}
@@ -72,174 +71,63 @@ class VendorPlugin : Plugin<Project> {
72
71
val jarJar = project.configurations.create(" firebaseJarJarArtifact" )
73
72
project.dependencies.add(" firebaseJarJarArtifact" , " org.pantsbuild:jarjar:1.7.2" )
74
73
75
- val android = project.extensions.getByType(LibraryExtension ::class .java)
76
-
77
- android.registerTransform(
78
- VendorTransform (
79
- android,
80
- vendor,
81
- JarJarTransformer (
82
- parentPackageProvider = {
83
- android.libraryVariants.find { it.name == " release" }!! .applicationId
84
- },
85
- jarJarProvider = { jarJar.resolve() },
86
- project = project,
87
- logger = project.logger,
88
- optimize = vendorConfig.optimize
89
- ),
90
- logger = project.logger
91
- )
92
- )
93
- }
94
- }
74
+ val androidComponents = project.extensions.getByType<LibraryAndroidComponentsExtension >()
95
75
96
- interface JarTransformer {
97
- fun transform (
98
- inputJar : File ,
99
- outputJar : File ,
100
- ownPackages : Set <String >,
101
- packagesToVendor : Set <String >
102
- )
103
- }
104
-
105
- class JarJarTransformer (
106
- private val parentPackageProvider : () -> String ,
107
- private val jarJarProvider : () -> Collection <File >,
108
- private val project : Project ,
109
- private val logger : Logger ,
110
- private val optimize : Provider <Boolean >
111
- ) : JarTransformer {
112
- override fun transform (
113
- inputJar : File ,
114
- outputJar : File ,
115
- ownPackages : Set <String >,
116
- packagesToVendor : Set <String >
117
- ) {
118
- val parentPackage = parentPackageProvider()
119
- val rulesFile = File .createTempFile(parentPackage, " .jarjar" )
120
- rulesFile.printWriter().use {
121
- if (optimize.get()) {
122
- for (packageName in ownPackages) {
123
- it.println (" keep $packageName .**" )
76
+ androidComponents.onVariants(androidComponents.selector().withBuildType(" release" )) { variant ->
77
+ val vendorTask =
78
+ project.tasks.register(" ${variant.name} VendorTransform" , VendorTask ::class .java) {
79
+ vendorDependencies.set(vendor)
80
+ packageName.set(variant.namespace)
81
+ this .jarJar.set(jarJar)
124
82
}
125
- }
126
- for (externalPackageName in packagesToVendor) {
127
- it.println (" rule $externalPackageName .** $parentPackage .@0" )
128
- }
83
+ variant.artifacts
84
+ .forScope(ScopedArtifacts .Scope .PROJECT )
85
+ .use(vendorTask)
86
+ .toTransform(
87
+ ScopedArtifact .CLASSES ,
88
+ VendorTask ::inputJars,
89
+ VendorTask ::inputDirs,
90
+ VendorTask ::outputJar
91
+ )
129
92
}
130
- logger.info(" The following JarJar configuration will be used:\n ${rulesFile.readText()} " )
131
-
132
- project
133
- .javaexec {
134
- main = " org.pantsbuild.jarjar.Main"
135
- classpath = project.files(jarJarProvider())
136
- args =
137
- listOf (" process" , rulesFile.absolutePath, inputJar.absolutePath, outputJar.absolutePath)
138
- systemProperties = mapOf (" verbose" to " true" , " misplacedClassStrategy" to " FATAL" )
139
- }
140
- .assertNormalExitValue()
141
93
}
142
94
}
143
95
144
- class VendorTransform (
145
- private val android : LibraryExtension ,
146
- private val configuration : Configuration ,
147
- private val jarTransformer : JarTransformer ,
148
- private val logger : Logger
149
- ) : Transform() {
150
- override fun getName () = " firebaseVendorTransform"
96
+ abstract class VendorTask @Inject constructor(private val execOperations : ExecOperations ) :
97
+ DefaultTask () {
98
+ @get: [InputFiles Classpath ]
99
+ abstract val vendorDependencies: Property <Configuration >
151
100
152
- override fun getInputTypes (): MutableSet <QualifiedContent .ContentType > {
153
- return mutableSetOf (QualifiedContent .DefaultContentType .CLASSES )
154
- }
101
+ @get: [InputFiles Classpath ]
102
+ abstract val jarJar: Property <Configuration >
155
103
156
- override fun isIncremental () = false
104
+ @get:Input abstract val packageName : Property < String >
157
105
158
- override fun getScopes (): MutableSet <in QualifiedContent .Scope > {
159
- return mutableSetOf (QualifiedContent .Scope .PROJECT )
160
- }
106
+ @get:InputFiles abstract val inputJars: ListProperty <RegularFile >
161
107
162
- override fun getReferencedScopes (): MutableSet <in QualifiedContent .Scope > {
163
- return mutableSetOf (QualifiedContent .Scope .PROJECT )
164
- }
108
+ @get:InputFiles abstract val inputDirs: ListProperty <Directory >
165
109
166
- override fun transform (transformInvocation : TransformInvocation ) {
167
- if (configuration.resolve().isEmpty()) {
168
- logger.warn(
169
- " Nothing to vendor. " +
170
- " If you don't need vendor functionality please disable 'firebase-vendor' plugin. " +
171
- " Otherwise use the 'vendor' configuration to add dependencies you want vendored in."
172
- )
173
- for (input in transformInvocation.inputs) {
174
- for (directoryInput in input.directoryInputs) {
175
- val directoryOutput =
176
- transformInvocation.outputProvider.getContentLocation(
177
- directoryInput.name,
178
- setOf (QualifiedContent .DefaultContentType .CLASSES ),
179
- mutableSetOf (QualifiedContent .Scope .PROJECT ),
180
- Format .DIRECTORY
181
- )
182
- directoryInput.file.copyRecursively(directoryOutput, overwrite = true )
183
- }
184
- for (jarInput in input.jarInputs) {
185
- val jarOutput =
186
- transformInvocation.outputProvider.getContentLocation(
187
- jarInput.name,
188
- setOf (QualifiedContent .DefaultContentType .CLASSES ),
189
- mutableSetOf (QualifiedContent .Scope .PROJECT ),
190
- Format .JAR
191
- )
110
+ @get:OutputFile abstract val outputJar: RegularFileProperty
192
111
193
- jarInput.file.copyTo(jarOutput, overwrite = true )
194
- }
195
- }
196
- return
197
- }
198
-
199
- val contentLocation =
200
- transformInvocation.outputProvider.getContentLocation(
201
- " sourceAndVendoredLibraries" ,
202
- setOf (QualifiedContent .DefaultContentType .CLASSES ),
203
- mutableSetOf (QualifiedContent .Scope .PROJECT ),
204
- Format .DIRECTORY
205
- )
206
- contentLocation.deleteRecursively()
207
- contentLocation.mkdirs()
208
- val tmpDir = File (contentLocation, " tmp" )
209
- tmpDir.mkdirs()
210
- try {
211
- val fatJar = process(tmpDir, transformInvocation)
212
- unzipJar(fatJar, contentLocation)
213
- } finally {
214
- tmpDir.deleteRecursively()
215
- }
216
- }
217
-
218
- private fun isTest (transformInvocation : TransformInvocation ): Boolean {
219
- return android.testVariants.find { it.name == transformInvocation.context.variantName } != null
220
- }
112
+ @TaskAction
113
+ fun taskAction () {
114
+ val workDir = File .createTempFile(" vendorTmp" , null )
115
+ workDir.mkdirs()
116
+ workDir.deleteRecursively()
221
117
222
- private fun process (workDir : File , transformInvocation : TransformInvocation ): File {
223
- transformInvocation.context.variantName
224
118
val unzippedDir = File (workDir, " unzipped" )
225
- val unzippedExcludedDir = File (workDir, " unzipped-excluded" )
226
- unzippedDir.mkdirs()
227
- unzippedExcludedDir.mkdirs()
228
-
229
- val externalCodeDir = if (isTest(transformInvocation)) unzippedExcludedDir else unzippedDir
119
+ val externalCodeDir = unzippedDir
230
120
231
- for (input in transformInvocation.inputs) {
232
- for (directoryInput in input.directoryInputs) {
233
- directoryInput.file.copyRecursively(unzippedDir)
234
- }
235
- for (jarInput in input.jarInputs) {
236
- unzipJar(jarInput.file, unzippedDir)
237
- }
121
+ for (directory in inputDirs.get()) {
122
+ directory.asFile.copyRecursively(unzippedDir)
123
+ }
124
+ for (jar in inputJars.get()) {
125
+ unzipJar(jar.asFile, unzippedDir)
238
126
}
239
127
240
128
val ownPackageNames = inferPackages(unzippedDir)
241
129
242
- for (jar in configuration.resolve ()) {
130
+ for (jar in vendorDependencies.get ()) {
243
131
unzipJar(jar, externalCodeDir)
244
132
}
245
133
val externalPackageNames = inferPackages(externalCodeDir) subtract ownPackageNames
@@ -250,26 +138,57 @@ class VendorTransform(
250
138
throw GradleException (
251
139
" Vendoring java or javax packages is not supported. " +
252
140
" Please exclude one of the direct or transitive dependencies: \n " +
253
- configuration.resolvedConfiguration.resolvedArtifacts.joinToString(separator = " \n " )
141
+ vendorDependencies
142
+ .get()
143
+ .resolvedConfiguration
144
+ .resolvedArtifacts
145
+ .joinToString(separator = " \n " )
254
146
)
255
147
}
148
+
256
149
val jar = File (workDir, " intermediate.jar" )
257
150
zipAll(unzippedDir, jar)
258
- val outputJar = File (workDir, " output.jar" )
259
-
260
- jarTransformer.transform(jar, outputJar, ownPackageNames, externalPackageNames)
261
- return outputJar
151
+ transform(jar, ownPackageNames, externalPackageNames)
262
152
}
263
153
264
- private fun inferPackages (dir : File ): Set <String > {
265
- return dir
266
- .walk()
267
- .filter { it.name.endsWith(" .class" ) }
268
- .map { it.parentFile.toRelativeString(dir).replace(' /' , ' .' ) }
269
- .toSet()
154
+ fun transform (inputJar : File , ownPackages : Set <String >, packagesToVendor : Set <String >) {
155
+ val parentPackage = packageName.get()
156
+ val rulesFile = File .createTempFile(parentPackage, " .jarjar" )
157
+ rulesFile.printWriter().use {
158
+ for (packageName in ownPackages) {
159
+ it.println (" keep $packageName .**" )
160
+ }
161
+ for (externalPackageName in packagesToVendor) {
162
+ it.println (" rule $externalPackageName .** $parentPackage .@0" )
163
+ }
164
+ }
165
+ logger.info(" The following JarJar configuration will be used:\n ${rulesFile.readText()} " )
166
+
167
+ execOperations
168
+ .javaexec {
169
+ mainClass.set(" org.pantsbuild.jarjar.Main" )
170
+ classpath = project.files(jarJar.get())
171
+ args =
172
+ listOf (
173
+ " process" ,
174
+ rulesFile.absolutePath,
175
+ inputJar.absolutePath,
176
+ outputJar.asFile.get().absolutePath
177
+ )
178
+ systemProperties = mapOf (" verbose" to " true" , " misplacedClassStrategy" to " FATAL" )
179
+ }
180
+ .assertNormalExitValue()
270
181
}
271
182
}
272
183
184
+ fun inferPackages (dir : File ): Set <String > {
185
+ return dir
186
+ .walk()
187
+ .filter { it.name.endsWith(" .class" ) }
188
+ .map { it.parentFile.toRelativeString(dir).replace(' /' , ' .' ) }
189
+ .toSet()
190
+ }
191
+
273
192
fun unzipJar (jar : File , directory : File ) {
274
193
ZipFile (jar).use { zip ->
275
194
zip
0 commit comments