From d68a220adfe0769de3a3c1cc14be19f2309da85b Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 21 Jan 2025 10:33:41 -0600 Subject: [PATCH 1/8] Update copyright header --- plugins/build.gradle.kts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts index 856ef9a9b08..8d7750724fc 100644 --- a/plugins/build.gradle.kts +++ b/plugins/build.gradle.kts @@ -1,16 +1,18 @@ -// Copyright 2018 Google LLC -// -// 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. +/* + * Copyright 2018 Google LLC + * + * 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. + */ plugins { alias(libs.plugins.kotlinx.serialization) From 577a44f731857ba84857db702bd2bff49d41e419 Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 21 Jan 2025 10:34:46 -0600 Subject: [PATCH 2/8] Add context receivers to plugins --- plugins/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts index 8d7750724fc..ac2b2653d4e 100644 --- a/plugins/build.gradle.kts +++ b/plugins/build.gradle.kts @@ -40,6 +40,8 @@ spotless { } } +kotlin { compilerOptions { freeCompilerArgs.add("-Xcontext-receivers") } } + // Refer latest "perf-plugin" released version on // https://maven.google.com/web/index.html?q=perf-plugin#com.google.firebase:perf-plugin // The System property allows us to integrate with an unreleased version from https://bityl.co/3oYt. From fc65f8ebadde4532506063c87e9d926f85d33268 Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 21 Jan 2025 10:34:48 -0600 Subject: [PATCH 3/8] Update build.gradle.kts --- plugins/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts index ac2b2653d4e..f1c08c25c14 100644 --- a/plugins/build.gradle.kts +++ b/plugins/build.gradle.kts @@ -28,6 +28,8 @@ repositories { maven(url = "https://plugins.gradle.org/m2/") } +group = "com.google.firebase" + spotless { java { target("src/**/*.java") From 35ffb8532b1746fc64a3f229c2cb5a9f9ab7dcde Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 21 Jan 2025 10:34:52 -0600 Subject: [PATCH 4/8] Delete ClosureUtil.java --- .../firebase/gradle/plugins/ClosureUtil.java | 34 ------------------- 1 file changed, 34 deletions(-) delete mode 100644 plugins/src/main/java/com/google/firebase/gradle/plugins/ClosureUtil.java diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/ClosureUtil.java b/plugins/src/main/java/com/google/firebase/gradle/plugins/ClosureUtil.java deleted file mode 100644 index 5a22a81520d..00000000000 --- a/plugins/src/main/java/com/google/firebase/gradle/plugins/ClosureUtil.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 Google LLC -// -// 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.google.firebase.gradle.plugins; - -import groovy.lang.Closure; -import java.util.function.Consumer; - -public final class ClosureUtil { - - private static final Object FAKE_THIS = new Object(); - - private ClosureUtil() {} - - /** Create a groovy closure backed by a lambda. */ - public static Closure closureOf(Consumer action) { - return new Closure(FAKE_THIS) { - void doCall(T t) { - action.accept(t); - } - }; - } -} From 7640b4d5e6968c3fcab0430ea80941076044e5f3 Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 21 Jan 2025 10:35:07 -0600 Subject: [PATCH 5/8] Migrate Utils to Extensions --- .../{GradleUtils.kt => GradleExtensions.kt} | 63 ++++++++++- .../{KotlinUtils.kt => KotlinExtensions.kt} | 106 ++++++++++++------ .../{ProjectUtils.kt => ProjectExtensions.kt} | 0 3 files changed, 130 insertions(+), 39 deletions(-) rename plugins/src/main/java/com/google/firebase/gradle/plugins/{GradleUtils.kt => GradleExtensions.kt} (73%) rename plugins/src/main/java/com/google/firebase/gradle/plugins/{KotlinUtils.kt => KotlinExtensions.kt} (81%) rename plugins/src/main/java/com/google/firebase/gradle/plugins/{ProjectUtils.kt => ProjectExtensions.kt} (100%) diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt similarity index 73% rename from plugins/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt index 9a071df3f69..9358c9fbf6a 100644 --- a/plugins/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt +++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt @@ -16,12 +16,19 @@ package com.google.firebase.gradle.plugins +import com.android.build.api.variant.LibraryAndroidComponentsExtension +import com.android.build.api.variant.LibraryVariant +import java.io.BufferedOutputStream import java.io.File +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream import org.gradle.api.DefaultTask import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.artifacts.Dependency import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.plugins.PluginManager import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.apply import org.gradle.workers.WorkAction @@ -165,7 +172,7 @@ inline fun attributeFrom(name: String) = Attribute.of(name, T::class * attribute(Attribute.of(name, T::class.java), value) * ``` */ -inline fun AttributeContainer.attribute(name: String, value: T) = +inline fun AttributeContainer.attribute(name: String, value: T) = attribute(attributeFrom(name), value) /** @@ -174,8 +181,7 @@ inline fun AttributeContainer.attribute(name: String, value: T) = * pluginManager.apply(T::class) * ``` */ -inline fun org.gradle.api.plugins.PluginManager.`apply`(): Unit = - `apply`(T::class) +inline fun PluginManager.apply(): Unit = apply(T::class) /** * The name provided to this artifact when published. @@ -186,4 +192,53 @@ inline fun org.gradle.api.plugins.PluginManager.`apply`(): Uni * ``` */ val Dependency.artifactName: String - get() = listOf(group, name, version).filterNotNull().joinToString(":") + get() = listOfNotNull(group, name, version).joinToString(":") + +/** + * Creates an archive of this directory at the [dest] file. + * + * Can only be ran within the context of a [Task], as outside of a [Task] so you should likely be + * using the `copy` or `sync` tasks instead. + */ +context(Task) +fun File.zipFilesTo(dest: File): File { + logger.info("Zipping '$absolutePath' to '${dest.absolutePath}'") + + logger.debug("Ensuring parent directories are present for zip file") + dest.parentFile?.mkdirs() + + logger.debug("Creating empty zip file to write to") + dest.createNewFile() + + logger.debug("Packing file contents into zip") + ZipOutputStream(BufferedOutputStream(dest.outputStream())).use { zipFile -> + for (file in walk().filter { it.isFile }) { + val relativePath = file.relativeTo(this).unixPath + logger.debug("Adding file to zip: $relativePath") + + zipFile.putNextEntry(ZipEntry(relativePath)) + file.inputStream().use { it.copyTo(zipFile) } + zipFile.closeEntry() + } + } + + return dest +} + +/** + * Bind a callback to run whenever there are release variants for this android build. + * + * Syntax sugar for: + * ``` + * components.onVariants(components.selector().withBuildType("release")) { + * // ... + * } + * ``` + * + * @see LibraryAndroidComponentsExtension.onVariants + */ +fun LibraryAndroidComponentsExtension.onReleaseVariants( + callback: (variant: LibraryVariant) -> Unit +) { + onVariants(selector().withBuildType("release"), callback) +} diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinUtils.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinExtensions.kt similarity index 81% rename from plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinUtils.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinExtensions.kt index c261d89d075..770cbfed6a7 100644 --- a/plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinUtils.kt +++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinExtensions.kt @@ -16,6 +16,7 @@ package com.google.firebase.gradle.plugins +import java.io.File import org.w3c.dom.Element import org.w3c.dom.NodeList @@ -26,16 +27,14 @@ fun String.remove(regex: Regex) = replace(regex, "") fun String.remove(str: String) = replace(str, "") /** - * Returns a sequence containing all elements. - * - * The operation is _terminal_. + * Joins a variable amount of [strings][Any.toString] to a single [String] split by newlines (`\n`). * - * Syntax sugar for: - * ``` - * take(count()) + * For example: + * ```kotlin + * println(multiLine("Hello", "World", "!")) // "Hello\nWorld\n!" * ``` */ -public fun Sequence.takeAll(): Sequence = take(count()) +fun multiLine(vararg strings: Any?) = strings.joinToString("\n") /** * Converts an [Element] to an Artifact string. @@ -77,19 +76,30 @@ fun Element.toArtifactString() = * ``` * * @throws NoSuchElementException if the [Element] does not have descendant [Element]s with tags - * that match the components of an Artifact string; groupId, artifactId, version. + * that match the components of an Artifact string; groupId and artifactId. */ fun Element.toMavenName() = "${textByTag("groupId")}:${textByTag("artifactId")}" /** - * Finds a descendant [Element] by a given [tag], and returns the [textContent] - * [Element.getTextContent] of it. + * Finds a descendant [Element] by a [tag], and returns the [textContent][Element.getTextContent] of + * it. * * @param tag the XML tag to filter for (the special value "*" matches all tags) * @throws NoSuchElementException if an [Element] with the given [tag] does not exist * @see findElementsByTag + * @see textByTagOrNull + */ +fun Element.textByTag(tag: String) = + textByTagOrNull(tag) ?: throw RuntimeException("Element tag was missing: $tag") + +/** + * Finds a descendant [Element] by a [tag], and returns the [textContent][Element.getTextContent] of + * it, or null if it couldn't be found. + * + * @param tag the XML tag to filter for (the special value "*" matches all tags) + * @see textByTag */ -fun Element.textByTag(tag: String) = findElementsByTag(tag).first().textContent +fun Element.textByTagOrNull(tag: String) = findElementsByTag(tag).firstOrNull()?.textContent /** * Finds a descendant [Element] by a given [tag], or creates a new one. @@ -99,7 +109,7 @@ fun Element.textByTag(tag: String) = findElementsByTag(tag).first().textContent * @param tag the XML tag to filter for (the special value "*" matches all tags) * @see findElementsByTag */ -fun Element.findOrCreate(tag: String) = +fun Element.findOrCreate(tag: String): Element = findElementsByTag(tag).firstOrNull() ?: ownerDocument.createElement(tag).also { appendChild(it) } /** @@ -118,27 +128,21 @@ fun Element.findElementsByTag(tag: String) = * Yields the items of this [NodeList] as a [Sequence]. * * [NodeList] does not typically offer an iterator. This extension method offers a means to loop - * through a NodeList's [item][NodeList.item] method, while also taking into account its [length] - * [NodeList.getLength] property to avoid an [IndexOutOfBoundsException]. + * through a NodeList's [item][NodeList.item] method, while also taking into account the element's + * [length][NodeList.getLength] property to avoid an [IndexOutOfBoundsException]. * * Additionally, this operation is _intermediate_ and _stateless_. */ -fun NodeList.children() = sequence { - for (index in 0..length) { - yield(item(index)) +fun NodeList.children(removeDOMSections: Boolean = true) = sequence { + for (index in 0 until length) { + val child = item(index) + + if (!removeDOMSections || !child.nodeName.startsWith("#")) { + yield(child) + } } } -/** - * Joins a variable amount of [strings][Any.toString] to a single [String] split by newlines (`\n`). - * - * For example: - * ```kotlin - * println(multiLine("Hello", "World", "!")) // "Hello\nWorld\n!" - * ``` - */ -fun multiLine(vararg strings: Any?) = strings.joinToString("\n") - /** * Returns the first match of a regular expression in the [input], beginning at the specified * [startIndex]. @@ -152,6 +156,26 @@ fun Regex.findOrThrow(input: CharSequence, startIndex: Int = 0) = find(input, startIndex) ?: throw RuntimeException(multiLine("No match found for the given input:", input.toString())) +/** + * Returns the value of the first capture group. + * + * Intended to be used in [MatchResult] that are only supposed to capture a single entry. + */ +val MatchResult.firstCapturedValue: String + get() = groupValues[1] + +/** + * Returns a sequence containing all elements. + * + * The operation is _terminal_. + * + * Syntax sugar for: + * ``` + * take(count()) + * ``` + */ +fun Sequence.takeAll(): Sequence = take(count()) + /** * Creates a [Pair] out of an [Iterable] with only two elements. * @@ -212,14 +236,6 @@ fun List.replaceMatches(regex: Regex, transform: (MatchResult) -> String } } -/** - * Returns the value of the first capture group. - * - * Intended to be used in [MatchResult] that are only supposed to capture a single entry. - */ -val MatchResult.firstCapturedValue: String - get() = groupValues[1] - /** * Creates a diff between two lists. * @@ -250,3 +266,23 @@ infix fun List.diff(other: List): List> { * ``` */ fun List.coerceToSize(targetSize: Int) = List(targetSize) { getOrNull(it) } + +/** + * The [path][File.path] represented as a qualified unix path. + * + * Useful when a system expects a unix path, but you need to be able to run it on non unix systems. + * + * @see absoluteUnixPath + */ +val File.unixPath: String + get() = path.replace("\\", "/") + +/** + * The [absolutePath][File.getAbsolutePath] represented as a qualified unix path. + * + * Useful when a system expects a unix path, but you need to be able to run it on non unix systems. + * + * @see unixPath + */ +val File.absoluteUnixPath: String + get() = absolutePath.replace("\\", "/") diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/ProjectUtils.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/ProjectExtensions.kt similarity index 100% rename from plugins/src/main/java/com/google/firebase/gradle/plugins/ProjectUtils.kt rename to plugins/src/main/java/com/google/firebase/gradle/plugins/ProjectExtensions.kt From ea89b6002010eebbe4cad93b0744b36290b1e798 Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 21 Jan 2025 10:35:10 -0600 Subject: [PATCH 6/8] Update VendorPlugin.kt --- .../firebase/gradle/plugins/VendorPlugin.kt | 241 +++++++++--------- 1 file changed, 119 insertions(+), 122 deletions(-) diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt index 11552396078..d5e2c10d00a 100644 --- a/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt +++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt @@ -20,21 +20,18 @@ import com.android.build.api.artifact.ScopedArtifact import com.android.build.api.variant.LibraryAndroidComponentsExtension import com.android.build.api.variant.ScopedArtifacts import com.android.build.gradle.LibraryPlugin -import java.io.BufferedInputStream -import java.io.BufferedOutputStream +import com.google.firebase.gradle.plugins.license.LicenseResolverPlugin import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.util.zip.ZipEntry -import java.util.zip.ZipFile -import java.util.zip.ZipOutputStream import javax.inject.Inject import org.gradle.api.DefaultTask import org.gradle.api.GradleException import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.ArchiveOperations import org.gradle.api.file.Directory +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.file.ProjectLayout import org.gradle.api.file.RegularFile import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.ListProperty @@ -46,43 +43,66 @@ import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.getByType +import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.withType import org.gradle.process.ExecOperations +import org.jetbrains.kotlin.gradle.utils.extendsFrom +/** + * Gradle plugin for vendoring dependencies in an android library. + * + * We vendor dependencies by moving the dependency into the published package, and renaming all + * imports to reference the vendored package. + * + * Registers the `vendor` configuration to be used for specifying vendored dependencies. + * + * Note that you should exclude any `java` or `javax` transitive dependencies, as `jarjar` (what we + * use to do the actual vendoring) unconditionally skips them. + * + * ``` + * vendor("com.google.dagger:dagger:2.27") { + * exclude(group = "javax.inject", module = "javax.inject") + * } + * ``` + * + * @see VendorTask + */ class VendorPlugin : Plugin { override fun apply(project: Project) { - project.plugins.all { - when (this) { - is LibraryPlugin -> configureAndroid(project) - } - } + project.apply() + project.plugins.withType().configureEach { configureAndroid(project) } } - fun configureAndroid(project: Project) { - project.apply(plugin = "LicenseResolverPlugin") - - val vendor = project.configurations.create("vendor") - project.configurations.all { - when (name) { + private fun configureAndroid(project: Project) { + val vendor = project.configurations.register("vendor") + val configurations = + listOf( "releaseCompileOnly", "debugImplementation", "testImplementation", - "androidTestImplementation" -> extendsFrom(vendor) - } + "androidTestImplementation", + ) + + for (configuration in configurations) { + project.configurations.named(configuration).extendsFrom(vendor) } - val jarJar = project.configurations.create("firebaseJarJarArtifact") - project.dependencies.add("firebaseJarJarArtifact", "org.pantsbuild:jarjar:1.7.2") + val jarJarArtifact = + project.configurations.register("firebaseJarJarArtifact") { + dependencies.add(project.dependencies.create("org.pantsbuild:jarjar:1.7.2")) + } val androidComponents = project.extensions.getByType() - androidComponents.onVariants(androidComponents.selector().withBuildType("release")) { variant -> + androidComponents.onReleaseVariants { val vendorTask = - project.tasks.register("${variant.name}VendorTransform", VendorTask::class.java) { + project.tasks.register("${it.name}VendorTransform") { vendorDependencies.set(vendor) - packageName.set(variant.namespace) - this.jarJar.set(jarJar) + packageName.set(it.namespace) + jarJar.set(jarJarArtifact) } - variant.artifacts + + it.artifacts .forScope(ScopedArtifacts.Scope.PROJECT) .use(vendorTask) .toTransform( @@ -95,67 +115,99 @@ class VendorPlugin : Plugin { } } -abstract class VendorTask @Inject constructor(private val execOperations: ExecOperations) : - DefaultTask() { +/** + * Executes the actual vendoring of a library. + * + * @see VendorPlugin + */ +abstract class VendorTask +@Inject +constructor( + private val exec: ExecOperations, + private val archive: ArchiveOperations, + private val fs: FileSystemOperations, + private val layout: ProjectLayout, +) : DefaultTask() { + /** Dependencies that should be vendored. */ @get:[InputFiles Classpath] abstract val vendorDependencies: Property + /** Configuration pointing to the `.jar` file for JarJar. */ @get:[InputFiles Classpath] abstract val jarJar: Property + /** + * The name of the package (or namespace) that we're vendoring for. + * + * We use this to rename the [vendorDependencies]. + */ @get:Input abstract val packageName: Property + /** The jars generated for this package during a release. */ @get:InputFiles abstract val inputJars: ListProperty + /** The directories generated for this package during a release. */ @get:InputFiles abstract val inputDirs: ListProperty + /** The jar file to save the vendored artifact. */ @get:OutputFile abstract val outputJar: RegularFileProperty @TaskAction fun taskAction() { - val workDir = File.createTempFile("vendorTmp", null) - workDir.mkdirs() - workDir.deleteRecursively() - - val unzippedDir = File(workDir, "unzipped") - val externalCodeDir = unzippedDir + val unzippedDir = temporaryDir.childFile("unzipped") - for (directory in inputDirs.get()) { - directory.asFile.copyRecursively(unzippedDir) + logger.info("Unpacking input directories") + fs.sync { + from(inputDirs) + into(unzippedDir) } - for (jar in inputJars.get()) { - unzipJar(jar.asFile, unzippedDir) + + logger.info("Unpacking input jars") + fs.copy { + for (jar in inputJars.get()) { + from(archive.zipTree(jar)) + } + into(unzippedDir) + exclude { it.path.contains("META-INF") } } - val ownPackageNames = inferPackages(unzippedDir) + val ownPackageNames = inferPackageNames(unzippedDir) - for (jar in vendorDependencies.get()) { - unzipJar(jar, externalCodeDir) + logger.info("Unpacking vendored files") + fs.copy { + for (jar in vendorDependencies.get()) { + from(archive.zipTree(jar)) + } + into(unzippedDir) + exclude { it.path.contains("META-INF") } } - val externalPackageNames = inferPackages(externalCodeDir) subtract ownPackageNames - val java = File(externalCodeDir, "java") - val javax = File(externalCodeDir, "javax") + + val externalPackageNames = inferPackageNames(unzippedDir) subtract ownPackageNames + val java = unzippedDir.childFile("java") + val javax = unzippedDir.childFile("javax") if (java.exists() || javax.exists()) { // JarJar unconditionally skips any classes whose package name starts with "java" or "javax". + val dependencies = vendorDependencies.get().resolvedConfiguration.resolvedArtifacts throw GradleException( - "Vendoring java or javax packages is not supported. " + - "Please exclude one of the direct or transitive dependencies: \n" + - vendorDependencies - .get() - .resolvedConfiguration - .resolvedArtifacts - .joinToString(separator = "\n") + """ + |Vendoring java or javax packages is not supported. + |Please exclude one of the direct or transitive dependencies: + |${dependencies.joinToString("\n")} + """ + .trimMargin() ) } - val jar = File(workDir, "intermediate.jar") - zipAll(unzippedDir, jar) - transform(jar, ownPackageNames, externalPackageNames) + val inputJar = temporaryDir.childFile("intermediate.jar") + unzippedDir.zipFilesTo(inputJar) + + transform(inputJar, ownPackageNames, externalPackageNames) } - fun transform(inputJar: File, ownPackages: Set, packagesToVendor: Set) { + private fun transform(inputJar: File, ownPackages: Set, packagesToVendor: Set) { val parentPackage = packageName.get() - val rulesFile = File.createTempFile(parentPackage, ".jarjar") + val rulesFile = temporaryDir.childFile("$parentPackage.jarjar") + rulesFile.printWriter().use { for (packageName in ownPackages) { it.println("keep $packageName.**") @@ -164,12 +216,13 @@ abstract class VendorTask @Inject constructor(private val execOperations: ExecOp it.println("rule $externalPackageName.** $parentPackage.@0") } } + logger.info("The following JarJar configuration will be used:\n ${rulesFile.readText()}") - execOperations + exec .javaexec { mainClass.set("org.pantsbuild.jarjar.Main") - classpath = project.files(jarJar.get()) + classpath = layout.files(jarJar) args = listOf( "process", @@ -181,69 +234,13 @@ abstract class VendorTask @Inject constructor(private val execOperations: ExecOp } .assertNormalExitValue() } -} - -fun inferPackages(dir: File): Set { - return dir - .walk() - .filter { it.name.endsWith(".class") } - .map { it.parentFile.toRelativeString(dir).replace('/', '.') } - .toSet() -} - -fun unzipJar(jar: File, directory: File) { - ZipFile(jar).use { zip -> - zip - .entries() - .asSequence() - .filter { !it.isDirectory && !it.name.startsWith("META-INF") } - .forEach { entry -> - zip.getInputStream(entry).use { input -> - val entryFile = File(directory, entry.name) - entryFile.parentFile.mkdirs() - entryFile.outputStream().use { output -> input.copyTo(output) } - } - } - } -} - -fun zipAll(directory: File, zipFile: File) { - - ZipOutputStream(BufferedOutputStream(FileOutputStream(zipFile))).use { - zipFiles(it, directory, "") - } -} -private fun zipFiles(zipOut: ZipOutputStream, sourceFile: File, parentDirPath: String) { - val data = ByteArray(2048) - sourceFile.listFiles()?.forEach { f -> - if (f.isDirectory) { - val path = - if (parentDirPath == "") { - f.name - } else { - parentDirPath + File.separator + f.name - } - // Call recursively to add files within this directory - zipFiles(zipOut, f, path) - } else { - FileInputStream(f).use { fi -> - BufferedInputStream(fi).use { origin -> - val path = parentDirPath + File.separator + f.name - val entry = ZipEntry(path) - entry.time = f.lastModified() - entry.isDirectory - entry.size = f.length() - zipOut.putNextEntry(entry) - while (true) { - val readBytes = origin.read(data) - if (readBytes == -1) { - break - } - zipOut.write(data, 0, readBytes) - } - } - } - } + /** Given a directory of class files, constructs a list of all the class files. */ + private fun inferPackageNames(dir: File): Set { + return dir + .walk() + .filter { it.name.endsWith(".class") } + .map { it.parentFile.toRelativeString(dir).replace(File.separator, ".") } + .toSet() } } From 99e59a84d58c0f3eba32c4622c7539b5b34e9570 Mon Sep 17 00:00:00 2001 From: Daymon Date: Wed, 22 Jan 2025 11:51:45 -0600 Subject: [PATCH 7/8] Update ci_tests.yml --- .github/workflows/ci_tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 52c45abc18b..c706aa614bd 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -119,6 +119,7 @@ jobs: module: ${{ fromJSON(needs.determine_changed.outputs.modules) }} exclude: - module: :firebase-firestore + - module: :firebase-functions:ktx steps: - uses: actions/checkout@v4.1.1 From 1d5e443a8ff6c238bdbe54e63bfa0c4ec684ef22 Mon Sep 17 00:00:00 2001 From: Daymon Date: Thu, 23 Jan 2025 13:02:09 -0600 Subject: [PATCH 8/8] Remove context receivers usage --- plugins/build.gradle.kts | 2 -- .../google/firebase/gradle/plugins/GradleExtensions.kt | 8 +++++--- .../com/google/firebase/gradle/plugins/VendorPlugin.kt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts index f1c08c25c14..e5095195cf4 100644 --- a/plugins/build.gradle.kts +++ b/plugins/build.gradle.kts @@ -42,8 +42,6 @@ spotless { } } -kotlin { compilerOptions { freeCompilerArgs.add("-Xcontext-receivers") } } - // Refer latest "perf-plugin" released version on // https://maven.google.com/web/index.html?q=perf-plugin#com.google.firebase:perf-plugin // The System property allows us to integrate with an unreleased version from https://bityl.co/3oYt. diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt index 9358c9fbf6a..4dabc30a0c6 100644 --- a/plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt +++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt @@ -31,6 +31,7 @@ import org.gradle.api.attributes.AttributeContainer import org.gradle.api.plugins.PluginManager import org.gradle.api.provider.Provider import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.provideDelegate import org.gradle.workers.WorkAction import org.gradle.workers.WorkParameters import org.gradle.workers.WorkQueue @@ -197,11 +198,12 @@ val Dependency.artifactName: String /** * Creates an archive of this directory at the [dest] file. * - * Can only be ran within the context of a [Task], as outside of a [Task] so you should likely be + * Should only be ran within the context of a [Task], as outside of a [Task] so you should likely be * using the `copy` or `sync` tasks instead. */ -context(Task) -fun File.zipFilesTo(dest: File): File { +fun File.zipFilesTo(task: Task, dest: File): File { + val logger = task.logger + logger.info("Zipping '$absolutePath' to '${dest.absolutePath}'") logger.debug("Ensuring parent directories are present for zip file") diff --git a/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt b/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt index d5e2c10d00a..dfd22e2c50b 100644 --- a/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt +++ b/plugins/src/main/java/com/google/firebase/gradle/plugins/VendorPlugin.kt @@ -199,7 +199,7 @@ constructor( } val inputJar = temporaryDir.childFile("intermediate.jar") - unzippedDir.zipFilesTo(inputJar) + unzippedDir.zipFilesTo(this, inputJar) transform(inputJar, ownPackageNames, externalPackageNames) }