Skip to content

Commit 5aa056e

Browse files
authored
Refactor Publisher (#4879)
* Add some helper methods * Refactor Publisher to BaseFirebaseLibraryPlugin * Remove excess comment * Align similar TODO comments * Remove Publisher referenced in PublishingPlugin * Remove excess code * Change TODO description to be more specific * Utilize standard for over foreach * Fix broken tests * Remove multidex from installations * Fix issue with task configuration for time being until Kotlin migration * possible fix for smoke test failures * ktfmt * temp solution to test smoke tests * remove snapshot from smoke tests * Remove traces of the publishing mode * Cache the android dependencies to reduce overhead
1 parent 3e5cd47 commit 5aa056e

File tree

11 files changed

+312
-373
lines changed

11 files changed

+312
-373
lines changed

buildSrc/src/main/java/com/google/firebase/gradle/MultiProjectReleasePlugin.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@
4949
*/
5050
public class MultiProjectReleasePlugin implements Plugin<Project> {
5151

52+
// TODO() - Will be removed once migrated to Kotlin
53+
private static String findStringProperty(Project p, String property) {
54+
Object value = p.findProperty(property);
55+
return value != null ? value.toString() : null;
56+
}
57+
5258
@Override
5359
public void apply(Project project) {
5460
project.apply(ImmutableMap.of("plugin", PublishingPlugin.class));
@@ -73,10 +79,10 @@ public void apply(Project project) {
7379
ReleaseGenerator.class,
7480
task -> {
7581
task.getCurrentRelease()
76-
.convention(project.property("currentRelease").toString());
77-
task.getPastRelease().convention(project.property("pastRelease").toString());
82+
.convention(findStringProperty(project, "currentRelease"));
83+
task.getPastRelease().convention(findStringProperty(project, "pastRelease"));
7884
task.getPrintReleaseConfig()
79-
.convention(project.property("printOutput").toString());
85+
.convention(findStringProperty(project, "printOutput"));
8086
task.getReleaseConfigFile()
8187
.convention(project.getLayout().getBuildDirectory().file("release.cfg"));
8288
task.getReleaseReportFile()

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

Lines changed: 164 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@ import java.io.File
1818
import java.nio.file.Paths
1919
import org.gradle.api.Plugin
2020
import org.gradle.api.Project
21+
import org.gradle.api.artifacts.ProjectDependency
2122
import org.gradle.api.publish.PublishingExtension
23+
import org.gradle.api.publish.maven.MavenPom
2224
import org.gradle.api.publish.maven.MavenPublication
2325
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
2426
import org.gradle.api.tasks.TaskProvider
2527
import org.gradle.kotlin.dsl.apply
2628
import org.gradle.kotlin.dsl.configure
2729
import org.gradle.kotlin.dsl.create
30+
import org.gradle.kotlin.dsl.findByType
2831
import org.gradle.kotlin.dsl.register
32+
import org.w3c.dom.Element
2933

3034
abstract class BaseFirebaseLibraryPlugin : Plugin<Project> {
3135

@@ -94,22 +98,173 @@ abstract class BaseFirebaseLibraryPlugin : Plugin<Project> {
9498
sources.value(project.provider { srcDirs })
9599
}
96100

101+
/**
102+
* Adds + configures the [MavenPublishPlugin] for the given [project].
103+
*
104+
* This provides the repository we publish to (a folder in the root build directory), and
105+
* configures maven pom generation.
106+
*
107+
* @see [applyPomTransformations]
108+
* @see [FirebaseLibraryExtension.applyPomCustomization]
109+
*/
97110
protected fun configurePublishing(project: Project, firebaseLibrary: FirebaseLibraryExtension) {
98-
project.afterEvaluate {
99-
project.apply<MavenPublishPlugin>()
100-
project.extensions.configure<PublishingExtension> {
111+
with(project) {
112+
apply<MavenPublishPlugin>()
113+
extensions.configure<PublishingExtension> {
101114
repositories.maven {
102-
val s = project.rootProject.buildDir.toString() + "/m2repository"
103-
url = File(s).toURI()
115+
url = rootProject.fileFromBuildDir("m2repository").toURI()
104116
name = "BuildDir"
105117
}
106118
publications.create<MavenPublication>("mavenAar") {
107-
from(project.components.findByName(firebaseLibrary.type.componentName))
108-
artifactId = firebaseLibrary.artifactId.get()
109-
groupId = firebaseLibrary.groupId.get()
110-
firebaseLibrary.applyPomCustomization(pom)
119+
afterEvaluate {
120+
artifactId =
121+
firebaseLibrary.artifactId.get() // these dont get populated until afterEvaluate :(
122+
groupId = firebaseLibrary.groupId.get()
123+
124+
firebaseLibrary.applyPomCustomization(pom)
125+
firebaseLibrary.applyPomTransformations(pom)
126+
from(components.findByName(firebaseLibrary.type.componentName))
127+
}
111128
}
112129
}
113130
}
114131
}
132+
133+
/**
134+
* Performs various transformations needed to ensure the given [pom] is ready for a release.
135+
*
136+
* The transformations are done lazily via the [withXml][MavenPom.withXml] provider.
137+
*
138+
* @param pom the [MavenPom] to prepare
139+
* @see [convertToCompileDependency]
140+
* @see [addTypeWithAARSupport]
141+
*/
142+
// TODO(b/270576405): Combine with applyPomCustomization when migrating FirebaseLibraryExtension
143+
private fun FirebaseLibraryExtension.applyPomTransformations(pom: MavenPom) {
144+
pom.withXml {
145+
val dependencies = asElement().findElementsByTag("dependency")
146+
val androidDependencies = resolveAndroidDependencies()
147+
for (dependency in dependencies) {
148+
convertToCompileDependency(dependency)
149+
addTypeWithAARSupport(dependency, androidDependencies)
150+
}
151+
}
152+
}
153+
154+
/**
155+
* Adds + configures the `scope` element as a direct descendant of the provided [Element].
156+
*
157+
* Sets the [textContent][Element.getTextContent] of `scope` to "compile"- regardless of its
158+
* initial value. This is needed to avoid a breaking change until the bug below is fixed.
159+
*
160+
* @param dependency the element to append the `scope` to
161+
* @see applyPomTransformations
162+
*/
163+
// TODO(b/277605778): Remove after configurations have been migrated to the right type
164+
private fun convertToCompileDependency(dependency: Element) {
165+
dependency.findOrCreate("scope").textContent = "compile"
166+
}
167+
168+
/**
169+
* Adds + configures the `type` element as a direct descendant of the provided [Element].
170+
*
171+
* The `type` element specifies what the given [dependency] is published as. This could be another
172+
* `pom`, a `jar`, an `aar`, etc., Usually, the [MavenPublishPlugin] can infer these types; this
173+
* is not the case however with `aar` artifacts.
174+
*
175+
* This method will check if the provided [dependency] is in the provided list of artifact strings
176+
* ([androidLibraries]), and map it to an `aar` or `jar` as needed.
177+
*
178+
* The following is an example of a `type` element:
179+
* ```
180+
* <dependency>
181+
* <type>aar</type>
182+
* </dependency>
183+
* ```
184+
*
185+
* @param dependency the element to append the `type` to
186+
* @param androidLibraries a list of dependencies for this given SDK that publish `aar` artifacts
187+
* @see applyPomTransformations
188+
*/
189+
// TODO(b/277607560): Remove when Gradle's MavenPublishPlugin adds functionality for aar types
190+
private fun addTypeWithAARSupport(dependency: Element, androidLibraries: List<String>) {
191+
dependency.findOrCreate("type").apply {
192+
textContent = if (androidLibraries.contains(dependency.toArtifactString())) "aar" else "jar"
193+
}
194+
}
115195
}
196+
197+
/**
198+
* A list of _all_ dependencies that publish `aar` artifacts.
199+
*
200+
* This is collected via the [runtimeClasspath][FirebaseLibraryExtension.getRuntimeClasspath], and
201+
* includes project level dependencies as well as external dependencies.
202+
*
203+
* The dependencies are mapped to their [artifactName].
204+
*
205+
* @see resolveProjectLevelDependencies
206+
* @see resolveExternalAndroidLibraries
207+
*/
208+
// TODO(b/277607560): Remove when Gradle's MavenPublishPlugin adds functionality for aar types
209+
fun FirebaseLibraryExtension.resolveAndroidDependencies() =
210+
resolveExternalAndroidLibraries() +
211+
resolveProjectLevelDependencies()
212+
.filter { it.type == LibraryType.ANDROID }
213+
.map { it.artifactName }
214+
215+
/**
216+
* A list of project level dependencies.
217+
*
218+
* This is collected via the [runtimeClasspath][FirebaseLibraryExtension.getRuntimeClasspath].
219+
*
220+
* @throws RuntimeException if a project level dependency is found that doesn't have
221+
* [FirebaseLibraryExtension]
222+
*/
223+
// TODO(b/277607560): Remove when Gradle's MavenPublishPlugin adds functionality for aar types
224+
fun FirebaseLibraryExtension.resolveProjectLevelDependencies() =
225+
project.configurations
226+
.getByName(runtimeClasspath)
227+
.allDependencies
228+
.mapNotNull { it as? ProjectDependency }
229+
.map {
230+
it.dependencyProject.extensions.findByType<FirebaseLibraryExtension>()
231+
?: throw RuntimeException(
232+
"Project level dependencies must have the firebaseLibrary plugin. The following dependency does not: ${it.artifactName}"
233+
)
234+
}
235+
236+
/**
237+
* A list of _external_ dependencies that publish `aar` artifacts.
238+
*
239+
* This is collected via the [runtimeClasspath][FirebaseLibraryExtension.getRuntimeClasspath], using
240+
* an [ArtifactView][org.gradle.api.artifacts.ArtifactView] that filters for `aar` artifactType.
241+
*
242+
* Artifacts are mapped to their respective display name:
243+
* ```
244+
* groupId:artifactId:version
245+
* ```
246+
*/
247+
// TODO(b/277607560): Remove when Gradle's MavenPublishPlugin adds functionality for aar types
248+
fun FirebaseLibraryExtension.resolveExternalAndroidLibraries() =
249+
project.configurations
250+
.getByName(runtimeClasspath)
251+
.incoming
252+
.artifactView { attributes { attribute("artifactType", "aar") } }
253+
.artifacts
254+
.map { it.variant.displayName.substringBefore(" ") }
255+
256+
/**
257+
* The name provided to this artifact when published.
258+
*
259+
* Syntax sugar for:
260+
* ```
261+
* "$mavenName:$version"
262+
* ```
263+
*
264+
* For example, the following could be an artifact name:
265+
* ```
266+
* "com.google.firebase:firebase-common:16.0.5"
267+
* ```
268+
*/
269+
val FirebaseLibraryExtension.artifactName: String
270+
get() = "$mavenName:$version"

buildSrc/src/main/java/com/google/firebase/gradle/plugins/CheckHeadDependencies.kt

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

1717
import org.gradle.api.DefaultTask
1818
import org.gradle.api.GradleException
19-
import org.gradle.api.artifacts.ProjectDependency
2019
import org.gradle.api.provider.ListProperty
2120
import org.gradle.api.tasks.Input
2221
import org.gradle.api.tasks.TaskAction
@@ -54,12 +53,8 @@ abstract class CheckHeadDependencies : DefaultTask() {
5453
}
5554
}
5655

57-
fun FirebaseLibraryExtension.projectDependenciesByName(): List<String> =
58-
project.configurations
59-
.getByName(runtimeClasspath)
60-
.allDependencies
61-
.filter { it is ProjectDependency }
62-
.map { it.name }
56+
private fun FirebaseLibraryExtension.projectDependenciesByName() =
57+
resolveProjectLevelDependencies().map { it.artifactId.get() }
6358

6459
companion object {
6560
val DEPENDENCIES_TO_IGNORE: List<String> = listOf("protolite-well-known-types")

buildSrc/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt

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

1717
import java.io.File
1818
import org.gradle.api.Project
19+
import org.gradle.api.artifacts.Dependency
1920
import org.gradle.api.attributes.Attribute
2021
import org.gradle.api.attributes.AttributeContainer
2122
import org.gradle.api.provider.Provider
@@ -102,3 +103,14 @@ inline fun <reified T> AttributeContainer.attribute(name: String, value: T) =
102103
*/
103104
inline fun <reified T : Any> org.gradle.api.plugins.PluginManager.`apply`(): Unit =
104105
`apply`(T::class)
106+
107+
/**
108+
* The name provided to this artifact when published.
109+
*
110+
* For example, the following could be an artifact name:
111+
* ```
112+
* "com.google.firebase:firebase-common:16.0.5"
113+
* ```
114+
*/
115+
val Dependency.artifactName: String
116+
get() = listOf(group, name, version).filterNotNull().joinToString(":")

buildSrc/src/main/java/com/google/firebase/gradle/plugins/KotlinUtils.kt

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
package com.google.firebase.gradle.plugins
1616

17+
import org.w3c.dom.Element
18+
import org.w3c.dom.NodeList
19+
1720
/** Replaces all matching substrings with an empty string (nothing) */
1821
fun String.remove(regex: Regex) = replace(regex, "")
1922

@@ -31,3 +34,74 @@ fun String.remove(str: String) = replace(str, "")
3134
* ```
3235
*/
3336
public fun <T> Sequence<T>.takeAll(): Sequence<T> = take(count())
37+
38+
/**
39+
* Converts an [Element] to an Artifact string.
40+
*
41+
* An Artifact string can be defined as a dependency with the following format:
42+
* ```
43+
* groupId:artifactId:version
44+
* ```
45+
*
46+
* For example, the following would be a valid [Element]:
47+
* ```
48+
* <mySuperCoolElement>
49+
* <groupId>com.google.firebase</groupId>
50+
* <artifactId>firebase-common</artifactId>
51+
* <version>16.0.1</version>
52+
* </mySuperCoolElement>
53+
* ```
54+
*
55+
* @throws NoSuchElementException if the [Element] does not have descendant [Element]s with tags
56+
* that match the components of an Artifact string; groupId, artifactId, version.
57+
*/
58+
fun Element.toArtifactString() =
59+
"${textByTag("groupId")}:${textByTag("artifactId")}:${textByTag("version")}"
60+
61+
/**
62+
* Finds a descendant [Element] by a given [tag], and returns the [textContent]
63+
* [Element.getTextContent] of it.
64+
*
65+
* @param tag the XML tag to filter for (the special value "*" matches all tags)
66+
* @throws NoSuchElementException if an [Element] with the given [tag] does not exist
67+
* @see findElementsByTag
68+
*/
69+
fun Element.textByTag(tag: String) = findElementsByTag(tag).first().textContent
70+
71+
/**
72+
* Finds a descendant [Element] by a given [tag], or creates a new one.
73+
*
74+
* If a new one is created, it is also appended to the [ownerDocument][Element.findOrCreate].
75+
*
76+
* @param tag the XML tag to filter for (the special value "*" matches all tags)
77+
* @see findElementsByTag
78+
*/
79+
fun Element.findOrCreate(tag: String) =
80+
findElementsByTag(tag).firstOrNull() ?: ownerDocument.createElement(tag).also { appendChild(it) }
81+
82+
/**
83+
* Returns a [Sequence] of all descendant [Element]s that match the given [tag].
84+
*
85+
* Essentially a rewrite of [Element.getElementsByTagName] that offers the elements as a [Sequence]
86+
* and properly converts them to [Element].
87+
*
88+
* @param tag the XML tag to filter for (the special value "*" matches all tags)
89+
* @see Element.getElementsByTagName
90+
*/
91+
fun Element.findElementsByTag(tag: String) =
92+
getElementsByTagName(tag).children().mapNotNull { it as? Element }
93+
94+
/**
95+
* Yields the items of this [NodeList] as a [Sequence].
96+
*
97+
* [NodeList] does not typically offer an iterator. This extension method offers a means to loop
98+
* through a NodeList's [item][NodeList.item] method, while also taking into account its [length]
99+
* [NodeList.getLength] property to avoid an [IndexOutOfBoundsException].
100+
*
101+
* Additionally, this operation is _intermediate_ and _stateless_.
102+
*/
103+
fun NodeList.children() = sequence {
104+
for (index in 0..length) {
105+
yield(item(index))
106+
}
107+
}

buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void apply(Project project) {
6060
String groupId = firebaseLibrary.groupId.get();
6161
String artifactId = firebaseLibrary.artifactId.get();
6262
String artifact =
63-
String.format("%s:%s:%s-SNAPSHOT", groupId, artifactId, sub.getVersion());
63+
String.format("%s:%s:%s", groupId, artifactId, sub.getVersion());
6464
allArtifacts.add(artifact);
6565

6666
if (changedProjects.contains(sub)) {

buildSrc/src/main/java/com/google/firebase/gradle/plugins/publish/Mode.java

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

0 commit comments

Comments
 (0)