Skip to content

Commit 0ce193f

Browse files
authored
Refactor Vendor Plugin (#6640)
Per [b/391351857](https://b.corp.google.com/issues/391351857), This refactors the `VendorPlugin` and `VendorTask` to be more compliant with the gradle cache and configuration avoidance. It also adds proper task logging and documentation to both the task and the plugin. This PR also fixes the following: - [b/391352175](https://b.corp.google.com/issues/391352175) -> Rename plugin Util files to Extensions
1 parent 377edcc commit 0ce193f

File tree

7 files changed

+269
-208
lines changed

7 files changed

+269
-208
lines changed

.github/workflows/ci_tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ jobs:
119119
module: ${{ fromJSON(needs.determine_changed.outputs.modules) }}
120120
exclude:
121121
- module: :firebase-firestore
122+
- module: :firebase-functions:ktx
122123

123124
steps:
124125
- uses: actions/checkout@v4.1.1

plugins/build.gradle.kts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1-
// Copyright 2018 Google LLC
2-
//
3-
// Licensed under the Apache License, Version 2.0 (the "License");
4-
// you may not use this file except in compliance with the License.
5-
// You may obtain a copy of the License at
6-
//
7-
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
9-
// Unless required by applicable law or agreed to in writing, software
10-
// distributed under the License is distributed on an "AS IS" BASIS,
11-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12-
// See the License for the specific language governing permissions and
13-
// limitations under the License.
1+
/*
2+
* Copyright 2018 Google LLC
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+
*/
1416

1517
plugins {
1618
alias(libs.plugins.kotlinx.serialization)
@@ -26,6 +28,8 @@ repositories {
2628
maven(url = "https://plugins.gradle.org/m2/")
2729
}
2830

31+
group = "com.google.firebase"
32+
2933
spotless {
3034
java {
3135
target("src/**/*.java")

plugins/src/main/java/com/google/firebase/gradle/plugins/ClosureUtil.java

Lines changed: 0 additions & 34 deletions
This file was deleted.

plugins/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt renamed to plugins/src/main/java/com/google/firebase/gradle/plugins/GradleExtensions.kt

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@
1616

1717
package com.google.firebase.gradle.plugins
1818

19+
import com.android.build.api.variant.LibraryAndroidComponentsExtension
20+
import com.android.build.api.variant.LibraryVariant
21+
import java.io.BufferedOutputStream
1922
import java.io.File
23+
import java.util.zip.ZipEntry
24+
import java.util.zip.ZipOutputStream
2025
import org.gradle.api.DefaultTask
2126
import org.gradle.api.Project
27+
import org.gradle.api.Task
2228
import org.gradle.api.artifacts.Dependency
2329
import org.gradle.api.attributes.Attribute
2430
import org.gradle.api.attributes.AttributeContainer
31+
import org.gradle.api.plugins.PluginManager
2532
import org.gradle.api.provider.Provider
2633
import org.gradle.kotlin.dsl.apply
34+
import org.gradle.kotlin.dsl.provideDelegate
2735
import org.gradle.workers.WorkAction
2836
import org.gradle.workers.WorkParameters
2937
import org.gradle.workers.WorkQueue
@@ -165,7 +173,7 @@ inline fun <reified T> attributeFrom(name: String) = Attribute.of(name, T::class
165173
* attribute(Attribute.of(name, T::class.java), value)
166174
* ```
167175
*/
168-
inline fun <reified T> AttributeContainer.attribute(name: String, value: T) =
176+
inline fun <reified T : Any> AttributeContainer.attribute(name: String, value: T) =
169177
attribute(attributeFrom(name), value)
170178

171179
/**
@@ -174,8 +182,7 @@ inline fun <reified T> AttributeContainer.attribute(name: String, value: T) =
174182
* pluginManager.apply(T::class)
175183
* ```
176184
*/
177-
inline fun <reified T : Any> org.gradle.api.plugins.PluginManager.`apply`(): Unit =
178-
`apply`(T::class)
185+
inline fun <reified T : Any> PluginManager.apply(): Unit = apply(T::class)
179186

180187
/**
181188
* The name provided to this artifact when published.
@@ -186,4 +193,54 @@ inline fun <reified T : Any> org.gradle.api.plugins.PluginManager.`apply`(): Uni
186193
* ```
187194
*/
188195
val Dependency.artifactName: String
189-
get() = listOf(group, name, version).filterNotNull().joinToString(":")
196+
get() = listOfNotNull(group, name, version).joinToString(":")
197+
198+
/**
199+
* Creates an archive of this directory at the [dest] file.
200+
*
201+
* Should only be ran within the context of a [Task], as outside of a [Task] so you should likely be
202+
* using the `copy` or `sync` tasks instead.
203+
*/
204+
fun File.zipFilesTo(task: Task, dest: File): File {
205+
val logger = task.logger
206+
207+
logger.info("Zipping '$absolutePath' to '${dest.absolutePath}'")
208+
209+
logger.debug("Ensuring parent directories are present for zip file")
210+
dest.parentFile?.mkdirs()
211+
212+
logger.debug("Creating empty zip file to write to")
213+
dest.createNewFile()
214+
215+
logger.debug("Packing file contents into zip")
216+
ZipOutputStream(BufferedOutputStream(dest.outputStream())).use { zipFile ->
217+
for (file in walk().filter { it.isFile }) {
218+
val relativePath = file.relativeTo(this).unixPath
219+
logger.debug("Adding file to zip: $relativePath")
220+
221+
zipFile.putNextEntry(ZipEntry(relativePath))
222+
file.inputStream().use { it.copyTo(zipFile) }
223+
zipFile.closeEntry()
224+
}
225+
}
226+
227+
return dest
228+
}
229+
230+
/**
231+
* Bind a callback to run whenever there are release variants for this android build.
232+
*
233+
* Syntax sugar for:
234+
* ```
235+
* components.onVariants(components.selector().withBuildType("release")) {
236+
* // ...
237+
* }
238+
* ```
239+
*
240+
* @see LibraryAndroidComponentsExtension.onVariants
241+
*/
242+
fun LibraryAndroidComponentsExtension.onReleaseVariants(
243+
callback: (variant: LibraryVariant) -> Unit
244+
) {
245+
onVariants(selector().withBuildType("release"), callback)
246+
}

plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinUtils.kt renamed to plugins/src/main/java/com/google/firebase/gradle/plugins/KotlinExtensions.kt

Lines changed: 71 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.firebase.gradle.plugins
1818

19+
import java.io.File
1920
import org.w3c.dom.Element
2021
import org.w3c.dom.NodeList
2122

@@ -26,16 +27,14 @@ fun String.remove(regex: Regex) = replace(regex, "")
2627
fun String.remove(str: String) = replace(str, "")
2728

2829
/**
29-
* Returns a sequence containing all elements.
30-
*
31-
* The operation is _terminal_.
30+
* Joins a variable amount of [strings][Any.toString] to a single [String] split by newlines (`\n`).
3231
*
33-
* Syntax sugar for:
34-
* ```
35-
* take(count())
32+
* For example:
33+
* ```kotlin
34+
* println(multiLine("Hello", "World", "!")) // "Hello\nWorld\n!"
3635
* ```
3736
*/
38-
public fun <T> Sequence<T>.takeAll(): Sequence<T> = take(count())
37+
fun multiLine(vararg strings: Any?) = strings.joinToString("\n")
3938

4039
/**
4140
* Converts an [Element] to an Artifact string.
@@ -77,19 +76,30 @@ fun Element.toArtifactString() =
7776
* ```
7877
*
7978
* @throws NoSuchElementException if the [Element] does not have descendant [Element]s with tags
80-
* that match the components of an Artifact string; groupId, artifactId, version.
79+
* that match the components of an Artifact string; groupId and artifactId.
8180
*/
8281
fun Element.toMavenName() = "${textByTag("groupId")}:${textByTag("artifactId")}"
8382

8483
/**
85-
* Finds a descendant [Element] by a given [tag], and returns the [textContent]
86-
* [Element.getTextContent] of it.
84+
* Finds a descendant [Element] by a [tag], and returns the [textContent][Element.getTextContent] of
85+
* it.
8786
*
8887
* @param tag the XML tag to filter for (the special value "*" matches all tags)
8988
* @throws NoSuchElementException if an [Element] with the given [tag] does not exist
9089
* @see findElementsByTag
90+
* @see textByTagOrNull
91+
*/
92+
fun Element.textByTag(tag: String) =
93+
textByTagOrNull(tag) ?: throw RuntimeException("Element tag was missing: $tag")
94+
95+
/**
96+
* Finds a descendant [Element] by a [tag], and returns the [textContent][Element.getTextContent] of
97+
* it, or null if it couldn't be found.
98+
*
99+
* @param tag the XML tag to filter for (the special value "*" matches all tags)
100+
* @see textByTag
91101
*/
92-
fun Element.textByTag(tag: String) = findElementsByTag(tag).first().textContent
102+
fun Element.textByTagOrNull(tag: String) = findElementsByTag(tag).firstOrNull()?.textContent
93103

94104
/**
95105
* 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
99109
* @param tag the XML tag to filter for (the special value "*" matches all tags)
100110
* @see findElementsByTag
101111
*/
102-
fun Element.findOrCreate(tag: String) =
112+
fun Element.findOrCreate(tag: String): Element =
103113
findElementsByTag(tag).firstOrNull() ?: ownerDocument.createElement(tag).also { appendChild(it) }
104114

105115
/**
@@ -118,27 +128,21 @@ fun Element.findElementsByTag(tag: String) =
118128
* Yields the items of this [NodeList] as a [Sequence].
119129
*
120130
* [NodeList] does not typically offer an iterator. This extension method offers a means to loop
121-
* through a NodeList's [item][NodeList.item] method, while also taking into account its [length]
122-
* [NodeList.getLength] property to avoid an [IndexOutOfBoundsException].
131+
* through a NodeList's [item][NodeList.item] method, while also taking into account the element's
132+
* [length][NodeList.getLength] property to avoid an [IndexOutOfBoundsException].
123133
*
124134
* Additionally, this operation is _intermediate_ and _stateless_.
125135
*/
126-
fun NodeList.children() = sequence {
127-
for (index in 0..length) {
128-
yield(item(index))
136+
fun NodeList.children(removeDOMSections: Boolean = true) = sequence {
137+
for (index in 0 until length) {
138+
val child = item(index)
139+
140+
if (!removeDOMSections || !child.nodeName.startsWith("#")) {
141+
yield(child)
142+
}
129143
}
130144
}
131145

132-
/**
133-
* Joins a variable amount of [strings][Any.toString] to a single [String] split by newlines (`\n`).
134-
*
135-
* For example:
136-
* ```kotlin
137-
* println(multiLine("Hello", "World", "!")) // "Hello\nWorld\n!"
138-
* ```
139-
*/
140-
fun multiLine(vararg strings: Any?) = strings.joinToString("\n")
141-
142146
/**
143147
* Returns the first match of a regular expression in the [input], beginning at the specified
144148
* [startIndex].
@@ -152,6 +156,26 @@ fun Regex.findOrThrow(input: CharSequence, startIndex: Int = 0) =
152156
find(input, startIndex)
153157
?: throw RuntimeException(multiLine("No match found for the given input:", input.toString()))
154158

159+
/**
160+
* Returns the value of the first capture group.
161+
*
162+
* Intended to be used in [MatchResult] that are only supposed to capture a single entry.
163+
*/
164+
val MatchResult.firstCapturedValue: String
165+
get() = groupValues[1]
166+
167+
/**
168+
* Returns a sequence containing all elements.
169+
*
170+
* The operation is _terminal_.
171+
*
172+
* Syntax sugar for:
173+
* ```
174+
* take(count())
175+
* ```
176+
*/
177+
fun <T> Sequence<T>.takeAll(): Sequence<T> = take(count())
178+
155179
/**
156180
* Creates a [Pair] out of an [Iterable] with only two elements.
157181
*
@@ -212,14 +236,6 @@ fun List<String>.replaceMatches(regex: Regex, transform: (MatchResult) -> String
212236
}
213237
}
214238

215-
/**
216-
* Returns the value of the first capture group.
217-
*
218-
* Intended to be used in [MatchResult] that are only supposed to capture a single entry.
219-
*/
220-
val MatchResult.firstCapturedValue: String
221-
get() = groupValues[1]
222-
223239
/**
224240
* Creates a diff between two lists.
225241
*
@@ -250,3 +266,23 @@ infix fun <T> List<T>.diff(other: List<T>): List<Pair<T?, T?>> {
250266
* ```
251267
*/
252268
fun <T> List<T>.coerceToSize(targetSize: Int) = List(targetSize) { getOrNull(it) }
269+
270+
/**
271+
* The [path][File.path] represented as a qualified unix path.
272+
*
273+
* Useful when a system expects a unix path, but you need to be able to run it on non unix systems.
274+
*
275+
* @see absoluteUnixPath
276+
*/
277+
val File.unixPath: String
278+
get() = path.replace("\\", "/")
279+
280+
/**
281+
* The [absolutePath][File.getAbsolutePath] represented as a qualified unix path.
282+
*
283+
* Useful when a system expects a unix path, but you need to be able to run it on non unix systems.
284+
*
285+
* @see unixPath
286+
*/
287+
val File.absoluteUnixPath: String
288+
get() = absolutePath.replace("\\", "/")

0 commit comments

Comments
 (0)