Skip to content

Commit ea89b60

Browse files
committed
Update VendorPlugin.kt
1 parent 7640b4d commit ea89b60

File tree

1 file changed

+119
-122
lines changed

1 file changed

+119
-122
lines changed

plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt

Lines changed: 119 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,18 @@ import com.android.build.api.artifact.ScopedArtifact
2020
import com.android.build.api.variant.LibraryAndroidComponentsExtension
2121
import com.android.build.api.variant.ScopedArtifacts
2222
import com.android.build.gradle.LibraryPlugin
23-
import java.io.BufferedInputStream
24-
import java.io.BufferedOutputStream
23+
import com.google.firebase.gradle.plugins.license.LicenseResolverPlugin
2524
import java.io.File
26-
import java.io.FileInputStream
27-
import java.io.FileOutputStream
28-
import java.util.zip.ZipEntry
29-
import java.util.zip.ZipFile
30-
import java.util.zip.ZipOutputStream
3125
import javax.inject.Inject
3226
import org.gradle.api.DefaultTask
3327
import org.gradle.api.GradleException
3428
import org.gradle.api.Plugin
3529
import org.gradle.api.Project
3630
import org.gradle.api.artifacts.Configuration
31+
import org.gradle.api.file.ArchiveOperations
3732
import org.gradle.api.file.Directory
33+
import org.gradle.api.file.FileSystemOperations
34+
import org.gradle.api.file.ProjectLayout
3835
import org.gradle.api.file.RegularFile
3936
import org.gradle.api.file.RegularFileProperty
4037
import org.gradle.api.provider.ListProperty
@@ -46,43 +43,66 @@ import org.gradle.api.tasks.OutputFile
4643
import org.gradle.api.tasks.TaskAction
4744
import org.gradle.kotlin.dsl.apply
4845
import org.gradle.kotlin.dsl.getByType
46+
import org.gradle.kotlin.dsl.register
47+
import org.gradle.kotlin.dsl.withType
4948
import org.gradle.process.ExecOperations
49+
import org.jetbrains.kotlin.gradle.utils.extendsFrom
5050

51+
/**
52+
* Gradle plugin for vendoring dependencies in an android library.
53+
*
54+
* We vendor dependencies by moving the dependency into the published package, and renaming all
55+
* imports to reference the vendored package.
56+
*
57+
* Registers the `vendor` configuration to be used for specifying vendored dependencies.
58+
*
59+
* Note that you should exclude any `java` or `javax` transitive dependencies, as `jarjar` (what we
60+
* use to do the actual vendoring) unconditionally skips them.
61+
*
62+
* ```
63+
* vendor("com.google.dagger:dagger:2.27") {
64+
* exclude(group = "javax.inject", module = "javax.inject")
65+
* }
66+
* ```
67+
*
68+
* @see VendorTask
69+
*/
5170
class VendorPlugin : Plugin<Project> {
5271
override fun apply(project: Project) {
53-
project.plugins.all {
54-
when (this) {
55-
is LibraryPlugin -> configureAndroid(project)
56-
}
57-
}
72+
project.apply<LicenseResolverPlugin>()
73+
project.plugins.withType<LibraryPlugin>().configureEach { configureAndroid(project) }
5874
}
5975

60-
fun configureAndroid(project: Project) {
61-
project.apply(plugin = "LicenseResolverPlugin")
62-
63-
val vendor = project.configurations.create("vendor")
64-
project.configurations.all {
65-
when (name) {
76+
private fun configureAndroid(project: Project) {
77+
val vendor = project.configurations.register("vendor")
78+
val configurations =
79+
listOf(
6680
"releaseCompileOnly",
6781
"debugImplementation",
6882
"testImplementation",
69-
"androidTestImplementation" -> extendsFrom(vendor)
70-
}
83+
"androidTestImplementation",
84+
)
85+
86+
for (configuration in configurations) {
87+
project.configurations.named(configuration).extendsFrom(vendor)
7188
}
7289

73-
val jarJar = project.configurations.create("firebaseJarJarArtifact")
74-
project.dependencies.add("firebaseJarJarArtifact", "org.pantsbuild:jarjar:1.7.2")
90+
val jarJarArtifact =
91+
project.configurations.register("firebaseJarJarArtifact") {
92+
dependencies.add(project.dependencies.create("org.pantsbuild:jarjar:1.7.2"))
93+
}
7594

7695
val androidComponents = project.extensions.getByType<LibraryAndroidComponentsExtension>()
7796

78-
androidComponents.onVariants(androidComponents.selector().withBuildType("release")) { variant ->
97+
androidComponents.onReleaseVariants {
7998
val vendorTask =
80-
project.tasks.register("${variant.name}VendorTransform", VendorTask::class.java) {
99+
project.tasks.register<VendorTask>("${it.name}VendorTransform") {
81100
vendorDependencies.set(vendor)
82-
packageName.set(variant.namespace)
83-
this.jarJar.set(jarJar)
101+
packageName.set(it.namespace)
102+
jarJar.set(jarJarArtifact)
84103
}
85-
variant.artifacts
104+
105+
it.artifacts
86106
.forScope(ScopedArtifacts.Scope.PROJECT)
87107
.use(vendorTask)
88108
.toTransform(
@@ -95,67 +115,99 @@ class VendorPlugin : Plugin<Project> {
95115
}
96116
}
97117

98-
abstract class VendorTask @Inject constructor(private val execOperations: ExecOperations) :
99-
DefaultTask() {
118+
/**
119+
* Executes the actual vendoring of a library.
120+
*
121+
* @see VendorPlugin
122+
*/
123+
abstract class VendorTask
124+
@Inject
125+
constructor(
126+
private val exec: ExecOperations,
127+
private val archive: ArchiveOperations,
128+
private val fs: FileSystemOperations,
129+
private val layout: ProjectLayout,
130+
) : DefaultTask() {
131+
/** Dependencies that should be vendored. */
100132
@get:[InputFiles Classpath]
101133
abstract val vendorDependencies: Property<Configuration>
102134

135+
/** Configuration pointing to the `.jar` file for JarJar. */
103136
@get:[InputFiles Classpath]
104137
abstract val jarJar: Property<Configuration>
105138

139+
/**
140+
* The name of the package (or namespace) that we're vendoring for.
141+
*
142+
* We use this to rename the [vendorDependencies].
143+
*/
106144
@get:Input abstract val packageName: Property<String>
107145

146+
/** The jars generated for this package during a release. */
108147
@get:InputFiles abstract val inputJars: ListProperty<RegularFile>
109148

149+
/** The directories generated for this package during a release. */
110150
@get:InputFiles abstract val inputDirs: ListProperty<Directory>
111151

152+
/** The jar file to save the vendored artifact. */
112153
@get:OutputFile abstract val outputJar: RegularFileProperty
113154

114155
@TaskAction
115156
fun taskAction() {
116-
val workDir = File.createTempFile("vendorTmp", null)
117-
workDir.mkdirs()
118-
workDir.deleteRecursively()
119-
120-
val unzippedDir = File(workDir, "unzipped")
121-
val externalCodeDir = unzippedDir
157+
val unzippedDir = temporaryDir.childFile("unzipped")
122158

123-
for (directory in inputDirs.get()) {
124-
directory.asFile.copyRecursively(unzippedDir)
159+
logger.info("Unpacking input directories")
160+
fs.sync {
161+
from(inputDirs)
162+
into(unzippedDir)
125163
}
126-
for (jar in inputJars.get()) {
127-
unzipJar(jar.asFile, unzippedDir)
164+
165+
logger.info("Unpacking input jars")
166+
fs.copy {
167+
for (jar in inputJars.get()) {
168+
from(archive.zipTree(jar))
169+
}
170+
into(unzippedDir)
171+
exclude { it.path.contains("META-INF") }
128172
}
129173

130-
val ownPackageNames = inferPackages(unzippedDir)
174+
val ownPackageNames = inferPackageNames(unzippedDir)
131175

132-
for (jar in vendorDependencies.get()) {
133-
unzipJar(jar, externalCodeDir)
176+
logger.info("Unpacking vendored files")
177+
fs.copy {
178+
for (jar in vendorDependencies.get()) {
179+
from(archive.zipTree(jar))
180+
}
181+
into(unzippedDir)
182+
exclude { it.path.contains("META-INF") }
134183
}
135-
val externalPackageNames = inferPackages(externalCodeDir) subtract ownPackageNames
136-
val java = File(externalCodeDir, "java")
137-
val javax = File(externalCodeDir, "javax")
184+
185+
val externalPackageNames = inferPackageNames(unzippedDir) subtract ownPackageNames
186+
val java = unzippedDir.childFile("java")
187+
val javax = unzippedDir.childFile("javax")
138188
if (java.exists() || javax.exists()) {
139189
// JarJar unconditionally skips any classes whose package name starts with "java" or "javax".
190+
val dependencies = vendorDependencies.get().resolvedConfiguration.resolvedArtifacts
140191
throw GradleException(
141-
"Vendoring java or javax packages is not supported. " +
142-
"Please exclude one of the direct or transitive dependencies: \n" +
143-
vendorDependencies
144-
.get()
145-
.resolvedConfiguration
146-
.resolvedArtifacts
147-
.joinToString(separator = "\n")
192+
"""
193+
|Vendoring java or javax packages is not supported.
194+
|Please exclude one of the direct or transitive dependencies:
195+
|${dependencies.joinToString("\n")}
196+
"""
197+
.trimMargin()
148198
)
149199
}
150200

151-
val jar = File(workDir, "intermediate.jar")
152-
zipAll(unzippedDir, jar)
153-
transform(jar, ownPackageNames, externalPackageNames)
201+
val inputJar = temporaryDir.childFile("intermediate.jar")
202+
unzippedDir.zipFilesTo(inputJar)
203+
204+
transform(inputJar, ownPackageNames, externalPackageNames)
154205
}
155206

156-
fun transform(inputJar: File, ownPackages: Set<String>, packagesToVendor: Set<String>) {
207+
private fun transform(inputJar: File, ownPackages: Set<String>, packagesToVendor: Set<String>) {
157208
val parentPackage = packageName.get()
158-
val rulesFile = File.createTempFile(parentPackage, ".jarjar")
209+
val rulesFile = temporaryDir.childFile("$parentPackage.jarjar")
210+
159211
rulesFile.printWriter().use {
160212
for (packageName in ownPackages) {
161213
it.println("keep $packageName.**")
@@ -164,12 +216,13 @@ abstract class VendorTask @Inject constructor(private val execOperations: ExecOp
164216
it.println("rule $externalPackageName.** $parentPackage.@0")
165217
}
166218
}
219+
167220
logger.info("The following JarJar configuration will be used:\n ${rulesFile.readText()}")
168221

169-
execOperations
222+
exec
170223
.javaexec {
171224
mainClass.set("org.pantsbuild.jarjar.Main")
172-
classpath = project.files(jarJar.get())
225+
classpath = layout.files(jarJar)
173226
args =
174227
listOf(
175228
"process",
@@ -181,69 +234,13 @@ abstract class VendorTask @Inject constructor(private val execOperations: ExecOp
181234
}
182235
.assertNormalExitValue()
183236
}
184-
}
185-
186-
fun inferPackages(dir: File): Set<String> {
187-
return dir
188-
.walk()
189-
.filter { it.name.endsWith(".class") }
190-
.map { it.parentFile.toRelativeString(dir).replace('/', '.') }
191-
.toSet()
192-
}
193-
194-
fun unzipJar(jar: File, directory: File) {
195-
ZipFile(jar).use { zip ->
196-
zip
197-
.entries()
198-
.asSequence()
199-
.filter { !it.isDirectory && !it.name.startsWith("META-INF") }
200-
.forEach { entry ->
201-
zip.getInputStream(entry).use { input ->
202-
val entryFile = File(directory, entry.name)
203-
entryFile.parentFile.mkdirs()
204-
entryFile.outputStream().use { output -> input.copyTo(output) }
205-
}
206-
}
207-
}
208-
}
209-
210-
fun zipAll(directory: File, zipFile: File) {
211-
212-
ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use {
213-
zipFiles(it, directory, "")
214-
}
215-
}
216237

217-
private fun zipFiles(zipOut: ZipOutputStream, sourceFile: File, parentDirPath: String) {
218-
val data = ByteArray(2048)
219-
sourceFile.listFiles()?.forEach { f ->
220-
if (f.isDirectory) {
221-
val path =
222-
if (parentDirPath == "") {
223-
f.name
224-
} else {
225-
parentDirPath + File.separator + f.name
226-
}
227-
// Call recursively to add files within this directory
228-
zipFiles(zipOut, f, path)
229-
} else {
230-
FileInputStream(f).use { fi ->
231-
BufferedInputStream(fi).use { origin ->
232-
val path = parentDirPath + File.separator + f.name
233-
val entry = ZipEntry(path)
234-
entry.time = f.lastModified()
235-
entry.isDirectory
236-
entry.size = f.length()
237-
zipOut.putNextEntry(entry)
238-
while (true) {
239-
val readBytes = origin.read(data)
240-
if (readBytes == -1) {
241-
break
242-
}
243-
zipOut.write(data, 0, readBytes)
244-
}
245-
}
246-
}
247-
}
238+
/** Given a directory of class files, constructs a list of all the class files. */
239+
private fun inferPackageNames(dir: File): Set<String> {
240+
return dir
241+
.walk()
242+
.filter { it.name.endsWith(".class") }
243+
.map { it.parentFile.toRelativeString(dir).replace(File.separator, ".") }
244+
.toSet()
248245
}
249246
}

0 commit comments

Comments
 (0)