Skip to content

Commit da0dbdc

Browse files
committed
Implement GMavenService
1 parent 0ce193f commit da0dbdc

File tree

8 files changed

+906
-7
lines changed

8 files changed

+906
-7
lines changed

plugins/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ repositories {
2626
mavenCentral()
2727
maven(url = "https://storage.googleapis.com/android-ci/mvn/")
2828
maven(url = "https://plugins.gradle.org/m2/")
29+
maven(url = "https://s01.oss.sonatype.org/content/repositories/releases/")
2930
}
3031

3132
group = "com.google.firebase"
@@ -66,11 +67,18 @@ dependencies {
6667
implementation("com.google.code.gson:gson:2.8.9")
6768
implementation(libs.android.gradlePlugin.gradle)
6869
implementation(libs.android.gradlePlugin.builder.test.api)
70+
implementation("io.github.pdvrieze.xmlutil:serialization-jvm:0.90.3") {
71+
exclude("org.jetbrains.kotlinx", "kotlinx-serialization-json")
72+
exclude("org.jetbrains.kotlinx", "kotlinx-serialization-core")
73+
}
6974

75+
testImplementation(gradleTestKit())
7076
testImplementation(libs.bundles.kotest)
77+
testImplementation(libs.mockk)
7178
testImplementation(libs.junit)
7279
testImplementation(libs.truth)
7380
testImplementation("commons-io:commons-io:2.15.1")
81+
testImplementation(kotlin("test"))
7482
}
7583

7684
gradlePlugin {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.google.firebase.gradle.plugins
1818

1919
import com.android.build.gradle.LibraryExtension
2020
import com.google.firebase.gradle.plugins.ci.Coverage
21+
import com.google.firebase.gradle.plugins.services.GMavenService
2122
import java.io.File
2223
import java.nio.file.Paths
2324
import org.gradle.api.Plugin
@@ -52,6 +53,7 @@ import org.w3c.dom.Element
5253
abstract class BaseFirebaseLibraryPlugin : Plugin<Project> {
5354
protected fun setupDefaults(project: Project, library: FirebaseLibraryExtension) {
5455
with(library) {
56+
project.gradle.sharedServices.registerIfAbsent<GMavenService, _>("gmaven")
5557
previewMode.convention("")
5658
publishJavadoc.convention(true)
5759
artifactId.convention(project.name)

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,11 @@ import org.gradle.api.attributes.Attribute
3030
import org.gradle.api.attributes.AttributeContainer
3131
import org.gradle.api.plugins.PluginManager
3232
import org.gradle.api.provider.Provider
33+
import org.gradle.api.services.BuildService
34+
import org.gradle.api.services.BuildServiceParameters
35+
import org.gradle.api.services.BuildServiceRegistry
36+
import org.gradle.api.services.BuildServiceSpec
3337
import org.gradle.kotlin.dsl.apply
34-
import org.gradle.kotlin.dsl.provideDelegate
3538
import org.gradle.workers.WorkAction
3639
import org.gradle.workers.WorkParameters
3740
import org.gradle.workers.WorkQueue
@@ -244,3 +247,19 @@ fun LibraryAndroidComponentsExtension.onReleaseVariants(
244247
) {
245248
onVariants(selector().withBuildType("release"), callback)
246249
}
250+
251+
/**
252+
* Register a build service under the specified [name], if it hasn't been registered already.
253+
*
254+
* ```
255+
* project.gradle.sharedServices.registerIfAbsent<GMavenService, _>("gmaven")
256+
* ```
257+
*
258+
* @param T The build service class to register
259+
* @param P The parameters class for the build service to register
260+
* @param name The name to register the build service under
261+
* @param config An optional configuration block to setup the build service with
262+
*/
263+
inline fun <reified T : BuildService<P>, reified P : BuildServiceParameters> BuildServiceRegistry
264+
.registerIfAbsent(name: String, noinline config: BuildServiceSpec<P>.() -> Unit = {}) =
265+
registerIfAbsent(name, T::class.java, config)

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package com.google.firebase.gradle.plugins
1818

1919
import java.io.File
20+
import java.io.InputStream
2021
import org.w3c.dom.Element
22+
import org.w3c.dom.Node
2123
import org.w3c.dom.NodeList
2224

2325
/** Replaces all matching substrings with an empty string (nothing) */
@@ -124,6 +126,13 @@ fun Element.findOrCreate(tag: String): Element =
124126
fun Element.findElementsByTag(tag: String) =
125127
getElementsByTagName(tag).children().mapNotNull { it as? Element }
126128

129+
/**
130+
* Returns the text of an attribute, if it exists.
131+
*
132+
* @param name The name of the attribute to get the text for
133+
*/
134+
fun Node.textByAttributeOrNull(name: String) = attributes?.getNamedItem(name)?.textContent
135+
127136
/**
128137
* Yields the items of this [NodeList] as a [Sequence].
129138
*
@@ -267,6 +276,19 @@ infix fun <T> List<T>.diff(other: List<T>): List<Pair<T?, T?>> {
267276
*/
268277
fun <T> List<T>.coerceToSize(targetSize: Int) = List(targetSize) { getOrNull(it) }
269278

279+
/**
280+
* Writes the [InputStream] to this file.
281+
*
282+
* While this method _does_ close the generated output stream, it's the callers responsibility to
283+
* close the passed [stream].
284+
*
285+
* @return This [File] instance for chaining.
286+
*/
287+
fun File.writeStream(stream: InputStream): File {
288+
outputStream().use { stream.copyTo(it) }
289+
return this
290+
}
291+
270292
/**
271293
* The [path][File.path] represented as a qualified unix path.
272294
*

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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ enum class PreReleaseVersionType {
5757
* Where `Type` is a case insensitive string of any [PreReleaseVersionType], and `Build` is a two
5858
* digit number (single digits should have a leading zero).
5959
*
60-
* Note that `build` will always be present as starting at one by defalt. That is, the following
60+
* Note that `build` will always be present as starting at one by default. That is, the following
6161
* transform occurs:
6262
* ```
6363
* "12.13.1-beta" // 12.13.1-beta01
@@ -92,7 +92,7 @@ data class PreReleaseVersion(val type: PreReleaseVersionType, val build: Int = 1
9292
*/
9393
fun fromStringsOrNull(type: String, build: String): PreReleaseVersion? =
9494
runCatching {
95-
val preType = PreReleaseVersionType.valueOf(type.toUpperCase())
95+
val preType = PreReleaseVersionType.valueOf(type.uppercase())
9696
val buildNumber = build.takeUnless { it.isBlank() }?.toInt() ?: 1
9797

9898
PreReleaseVersion(preType, buildNumber)
@@ -115,7 +115,7 @@ data class PreReleaseVersion(val type: PreReleaseVersionType, val build: Int = 1
115115
* PreReleaseVersion(RC, 12).toString() // "rc12"
116116
* ```
117117
*/
118-
override fun toString() = "${type.name.toLowerCase()}${build.toString().padStart(2, '0')}"
118+
override fun toString() = "${type.name.lowercase()}${build.toString().padStart(2, '0')}"
119119
}
120120

121121
/**
@@ -140,7 +140,7 @@ data class ModuleVersion(
140140
) : Comparable<ModuleVersion> {
141141

142142
/** Formatted as `MAJOR.MINOR.PATCH-PRE` */
143-
override fun toString() = "$major.$minor.$patch${pre?.let { "-${it.toString()}" } ?: ""}"
143+
override fun toString() = "$major.$minor.$patch${pre?.let { "-$it" } ?: ""}"
144144

145145
override fun compareTo(other: ModuleVersion) =
146146
compareValuesBy(
@@ -149,7 +149,7 @@ data class ModuleVersion(
149149
{ it.major },
150150
{ it.minor },
151151
{ it.patch },
152-
{ it.pre == null }, // a version with no prerelease version takes precedence
152+
{ it.pre == null }, // a version with no pre-release version takes precedence
153153
{ it.pre },
154154
)
155155

@@ -176,7 +176,7 @@ data class ModuleVersion(
176176
* ```
177177
*/
178178
val VERSION_REGEX =
179-
"(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)(?:\\-\\b)?(?<pre>\\w\\D+)?(?<build>\\B\\d+)?"
179+
"(?<major>\\d+)\\.(?<minor>\\d+)\\.(?<patch>\\d+)(?:-\\b)?(?<pre>\\w\\D+)?(?<build>\\B\\d+)?"
180180
.toRegex()
181181

182182
/**
@@ -209,6 +209,29 @@ data class ModuleVersion(
209209
}
210210
}
211211
.getOrNull()
212+
213+
/**
214+
* Parse a [ModuleVersion] from a string.
215+
*
216+
* You should use [fromStringOrNull] when you don't know the `artifactId` of the corresponding
217+
* artifact, if you don't need to throw on failure, or if you need to throw a more specific
218+
* message.
219+
*
220+
* This method exists to cover the common ground of getting [ModuleVersion] representations of
221+
* artifacts.
222+
*
223+
* @param artifactId The artifact that this version belongs to. Will be used in the error
224+
* message on failure.
225+
* @param version The version to parse into a [ModuleVersion].
226+
* @return A [ModuleVersion] created from the string.
227+
* @throws IllegalArgumentException If the string doesn't represent a valid semver version.
228+
* @see fromStringOrNull
229+
*/
230+
fun fromString(artifactId: String, version: String): ModuleVersion =
231+
fromStringOrNull(version)
232+
?: throw IllegalArgumentException(
233+
"Invalid module version found for '${artifactId}': $version"
234+
)
212235
}
213236

214237
/**
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
package com.google.firebase.gradle.plugins.datamodels
2+
3+
import com.google.firebase.gradle.plugins.ModuleVersion
4+
import java.io.File
5+
import javax.xml.parsers.DocumentBuilderFactory
6+
import kotlinx.serialization.Serializable
7+
import kotlinx.serialization.encodeToString
8+
import nl.adaptivity.xmlutil.XmlDeclMode
9+
import nl.adaptivity.xmlutil.newReader
10+
import nl.adaptivity.xmlutil.serialization.XML
11+
import nl.adaptivity.xmlutil.serialization.XmlChildrenName
12+
import nl.adaptivity.xmlutil.serialization.XmlElement
13+
import nl.adaptivity.xmlutil.serialization.XmlSerialName
14+
import nl.adaptivity.xmlutil.xmlStreaming
15+
import org.w3c.dom.Element
16+
17+
/**
18+
* Representation of a `<license />` element in a a pom file.
19+
*
20+
* @see PomElement
21+
*/
22+
@Serializable
23+
@XmlSerialName("license")
24+
data class LicenseElement(
25+
@XmlElement val name: String,
26+
@XmlElement val url: String? = null,
27+
@XmlElement val distribution: String? = null,
28+
)
29+
30+
/**
31+
* Representation of an `<scm />` element in a a pom file.
32+
*
33+
* @see PomElement
34+
*/
35+
@Serializable
36+
@XmlSerialName("scm")
37+
data class SourceControlManagement(@XmlElement val connection: String, @XmlElement val url: String)
38+
39+
/**
40+
* Representation of a `<dependency />` element in a pom file.
41+
*
42+
* @see PomElement
43+
*/
44+
@Serializable
45+
@XmlSerialName("dependency")
46+
data class ArtifactDependency(
47+
@XmlElement val groupId: String,
48+
@XmlElement val artifactId: String,
49+
// Can be null if the artifact derives its version from a bom
50+
@XmlElement val version: String? = null,
51+
@XmlElement val type: String? = null,
52+
@XmlElement val scope: String? = null,
53+
) {
54+
/**
55+
* Returns the artifact dependency as a a gradle dependency string.
56+
*
57+
* ```
58+
* implementation("com.google.firebase:firebase-firestore:1.0.0")
59+
* ```
60+
*
61+
* @see configuration
62+
* @see simpleDepString
63+
*/
64+
override fun toString() = "$configuration(\"$simpleDepString\")"
65+
}
66+
67+
/**
68+
* The artifact type of this dependency, or the default inferred by gradle.
69+
*
70+
* We use a separate variable instead of inferring the default in the constructor so we can
71+
* serialize instances of [ArtifactDependency] that should specifically _not_ have a type in the
72+
* output (like in [DependencyManagementElement] instances).
73+
*/
74+
val ArtifactDependency.typeOrDefault: String
75+
get() = type ?: "jar"
76+
77+
/**
78+
* The artifact scope of this dependency, or the default inferred by gradle.
79+
*
80+
* We use a separate variable instead of inferring the default in the constructor so we can
81+
* serialize instances of [ArtifactDependency] that should specifically _not_ have a scope in the
82+
* output (like in [DependencyManagementElement] instances).
83+
*/
84+
val ArtifactDependency.scopeOrDefault: String
85+
get() = scope ?: "compile"
86+
87+
/**
88+
* The [version][ArtifactDependency.version] represented as a [ModuleVersion].
89+
*
90+
* @throws RuntimeException if the version isn't valid semver, or it's missing.
91+
*/
92+
val ArtifactDependency.moduleVersion: ModuleVersion
93+
get() =
94+
version?.let { ModuleVersion.fromString(artifactId, it) }
95+
?: throw RuntimeException(
96+
"Missing required version property for artifact dependency: $artifactId"
97+
)
98+
99+
/**
100+
* The fully qualified name of the artifact.
101+
*
102+
* Shorthand for:
103+
* ```
104+
* "${artifact.groupId}:${artifact.artifactId}"
105+
* ```
106+
*/
107+
val ArtifactDependency.fullArtifactName: String
108+
get() = "$groupId:$artifactId"
109+
110+
/**
111+
* A string representing the dependency as a maven artifact marker.
112+
*
113+
* ```
114+
* "com.google.firebase:firebase-common:21.0.0"
115+
* ```
116+
*/
117+
val ArtifactDependency.simpleDepString: String
118+
get() = "$fullArtifactName${version?.let { ":$it" } ?: ""}"
119+
120+
/** The gradle configuration that this dependency would apply to (eg; `api` or `implementation`). */
121+
val ArtifactDependency.configuration: String
122+
get() = if (scopeOrDefault == "compile") "api" else "implementation"
123+
124+
@Serializable
125+
@XmlSerialName("dependencyManagement")
126+
data class DependencyManagementElement(
127+
@XmlChildrenName("dependency") val dependencies: List<ArtifactDependency>? = null
128+
)
129+
130+
/** Representation of a `<project />` element within a `pom.xml` file. */
131+
@Serializable
132+
@XmlSerialName("project")
133+
data class PomElement(
134+
@XmlSerialName("xmlns") val namespace: String? = null,
135+
@XmlSerialName("xmlns:xsi") val schema: String? = null,
136+
@XmlSerialName("xsi:schemaLocation") val schemaLocation: String? = null,
137+
@XmlElement val modelVersion: String,
138+
@XmlElement val groupId: String,
139+
@XmlElement val artifactId: String,
140+
@XmlElement val version: String,
141+
@XmlElement val packaging: String? = null,
142+
@XmlChildrenName("licenses") val licenses: List<LicenseElement>? = null,
143+
@XmlElement val scm: SourceControlManagement? = null,
144+
@XmlElement val dependencyManagement: DependencyManagementElement? = null,
145+
@XmlChildrenName("dependency") val dependencies: List<ArtifactDependency>? = null,
146+
) {
147+
/**
148+
* Serializes this pom element into a valid XML element and saves it to the specified [file].
149+
*
150+
* @param file Where to save the serialized pom to
151+
* @return The provided file, for chaining purposes.
152+
* @see fromFile
153+
*/
154+
fun toFile(file: File): File {
155+
val xmlWriter = XML {
156+
indent = 2
157+
xmlDeclMode = XmlDeclMode.None
158+
}
159+
file.writeText(xmlWriter.encodeToString(this))
160+
return file
161+
}
162+
163+
companion object {
164+
/**
165+
* Deserializes a [PomElement] from a `pom.xml` file.
166+
*
167+
* @param file The file that contains the pom element.
168+
* @return The deserialized [PomElement]
169+
* @see toFile
170+
* @see fromElement
171+
*/
172+
fun fromFile(file: File): PomElement =
173+
fromElement(
174+
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file).documentElement
175+
)
176+
177+
/**
178+
* Deserializes a [PomElement] from a document [Element].
179+
*
180+
* @param element The HTML element representing the pom element.
181+
* @return The deserialized [PomElement]
182+
* @see fromFile
183+
*/
184+
fun fromElement(element: Element): PomElement =
185+
XML.decodeFromReader(xmlStreaming.newReader(element))
186+
}
187+
}

0 commit comments

Comments
 (0)