Skip to content

Refactor Publisher #4879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@
*/
public class MultiProjectReleasePlugin implements Plugin<Project> {

// TODO() - Will be removed once migrated to Kotlin
private static String findStringProperty(Project p, String property) {
Object value = p.findProperty(property);
return value != null ? value.toString() : null;
}

@Override
public void apply(Project project) {
project.apply(ImmutableMap.of("plugin", PublishingPlugin.class));
Expand All @@ -73,10 +79,10 @@ public void apply(Project project) {
ReleaseGenerator.class,
task -> {
task.getCurrentRelease()
.convention(project.property("currentRelease").toString());
task.getPastRelease().convention(project.property("pastRelease").toString());
.convention(findStringProperty(project, "currentRelease"));
task.getPastRelease().convention(findStringProperty(project, "pastRelease"));
task.getPrintReleaseConfig()
.convention(project.property("printOutput").toString());
.convention(findStringProperty(project, "printOutput"));
task.getReleaseConfigFile()
.convention(project.getLayout().getBuildDirectory().file("release.cfg"));
task.getReleaseReportFile()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,18 @@ import java.io.File
import java.nio.file.Paths
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPom
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.register
import org.w3c.dom.Element

abstract class BaseFirebaseLibraryPlugin : Plugin<Project> {

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

/**
* Adds + configures the [MavenPublishPlugin] for the given [project].
*
* This provides the repository we publish to (a folder in the root build directory), and
* configures maven pom generation.
*
* @see [applyPomTransformations]
* @see [FirebaseLibraryExtension.applyPomCustomization]
*/
protected fun configurePublishing(project: Project, firebaseLibrary: FirebaseLibraryExtension) {
project.afterEvaluate {
project.apply<MavenPublishPlugin>()
project.extensions.configure<PublishingExtension> {
with(project) {
apply<MavenPublishPlugin>()
extensions.configure<PublishingExtension> {
repositories.maven {
val s = project.rootProject.buildDir.toString() + "/m2repository"
url = File(s).toURI()
url = rootProject.fileFromBuildDir("m2repository").toURI()
name = "BuildDir"
}
publications.create<MavenPublication>("mavenAar") {
from(project.components.findByName(firebaseLibrary.type.componentName))
artifactId = firebaseLibrary.artifactId.get()
groupId = firebaseLibrary.groupId.get()
firebaseLibrary.applyPomCustomization(pom)
afterEvaluate {
artifactId =
firebaseLibrary.artifactId.get() // these dont get populated until afterEvaluate :(
groupId = firebaseLibrary.groupId.get()

firebaseLibrary.applyPomCustomization(pom)
firebaseLibrary.applyPomTransformations(pom)
from(components.findByName(firebaseLibrary.type.componentName))
}
}
}
}
}

/**
* Performs various transformations needed to ensure the given [pom] is ready for a release.
*
* The transformations are done lazily via the [withXml][MavenPom.withXml] provider.
*
* @param pom the [MavenPom] to prepare
* @see [convertToCompileDependency]
* @see [addTypeWithAARSupport]
*/
// TODO(b/270576405): Combine with applyPomCustomization when migrating FirebaseLibraryExtension
private fun FirebaseLibraryExtension.applyPomTransformations(pom: MavenPom) {
pom.withXml {
val dependencies = asElement().findElementsByTag("dependency")
val androidDependencies = resolveAndroidDependencies()
for (dependency in dependencies) {
convertToCompileDependency(dependency)
addTypeWithAARSupport(dependency, androidDependencies)
}
}
}

/**
* Adds + configures the `scope` element as a direct descendant of the provided [Element].
*
* Sets the [textContent][Element.getTextContent] of `scope` to "compile"- regardless of its
* initial value. This is needed to avoid a breaking change until the bug below is fixed.
*
* @param dependency the element to append the `scope` to
* @see applyPomTransformations
*/
// TODO(b/277605778): Remove after configurations have been migrated to the right type
private fun convertToCompileDependency(dependency: Element) {
dependency.findOrCreate("scope").textContent = "compile"
}

/**
* Adds + configures the `type` element as a direct descendant of the provided [Element].
*
* The `type` element specifies what the given [dependency] is published as. This could be another
* `pom`, a `jar`, an `aar`, etc., Usually, the [MavenPublishPlugin] can infer these types; this
* is not the case however with `aar` artifacts.
*
* This method will check if the provided [dependency] is in the provided list of artifact strings
* ([androidLibraries]), and map it to an `aar` or `jar` as needed.
*
* The following is an example of a `type` element:
* ```
* <dependency>
* <type>aar</type>
* </dependency>
* ```
*
* @param dependency the element to append the `type` to
* @param androidLibraries a list of dependencies for this given SDK that publish `aar` artifacts
* @see applyPomTransformations
*/
// TODO(b/277607560): Remove when Gradle's MavenPublishPlugin adds functionality for aar types
private fun addTypeWithAARSupport(dependency: Element, androidLibraries: List<String>) {
dependency.findOrCreate("type").apply {
textContent = if (androidLibraries.contains(dependency.toArtifactString())) "aar" else "jar"
}
}
}

/**
* A list of _all_ dependencies that publish `aar` artifacts.
*
* This is collected via the [runtimeClasspath][FirebaseLibraryExtension.getRuntimeClasspath], and
* includes project level dependencies as well as external dependencies.
*
* The dependencies are mapped to their [artifactName].
*
* @see resolveProjectLevelDependencies
* @see resolveExternalAndroidLibraries
*/
// TODO(b/277607560): Remove when Gradle's MavenPublishPlugin adds functionality for aar types
fun FirebaseLibraryExtension.resolveAndroidDependencies() =
resolveExternalAndroidLibraries() +
resolveProjectLevelDependencies()
.filter { it.type == LibraryType.ANDROID }
.map { it.artifactName }

/**
* A list of project level dependencies.
*
* This is collected via the [runtimeClasspath][FirebaseLibraryExtension.getRuntimeClasspath].
*
* @throws RuntimeException if a project level dependency is found that doesn't have
* [FirebaseLibraryExtension]
*/
// TODO(b/277607560): Remove when Gradle's MavenPublishPlugin adds functionality for aar types
fun FirebaseLibraryExtension.resolveProjectLevelDependencies() =
project.configurations
.getByName(runtimeClasspath)
.allDependencies
.mapNotNull { it as? ProjectDependency }
.map {
it.dependencyProject.extensions.findByType<FirebaseLibraryExtension>()
?: throw RuntimeException(
"Project level dependencies must have the firebaseLibrary plugin. The following dependency does not: ${it.artifactName}"
)
}

/**
* A list of _external_ dependencies that publish `aar` artifacts.
*
* This is collected via the [runtimeClasspath][FirebaseLibraryExtension.getRuntimeClasspath], using
* an [ArtifactView][org.gradle.api.artifacts.ArtifactView] that filters for `aar` artifactType.
*
* Artifacts are mapped to their respective display name:
* ```
* groupId:artifactId:version
* ```
*/
// TODO(b/277607560): Remove when Gradle's MavenPublishPlugin adds functionality for aar types
fun FirebaseLibraryExtension.resolveExternalAndroidLibraries() =
project.configurations
.getByName(runtimeClasspath)
.incoming
.artifactView { attributes { attribute("artifactType", "aar") } }
.artifacts
.map { it.variant.displayName.substringBefore(" ") }

/**
* The name provided to this artifact when published.
*
* Syntax sugar for:
* ```
* "$mavenName:$version"
* ```
*
* For example, the following could be an artifact name:
* ```
* "com.google.firebase:firebase-common:16.0.5"
* ```
*/
val FirebaseLibraryExtension.artifactName: String
get() = "$mavenName:$version"
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package com.google.firebase.gradle.plugins

import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
Expand Down Expand Up @@ -54,12 +53,8 @@ abstract class CheckHeadDependencies : DefaultTask() {
}
}

fun FirebaseLibraryExtension.projectDependenciesByName(): List<String> =
project.configurations
.getByName(runtimeClasspath)
.allDependencies
.filter { it is ProjectDependency }
.map { it.name }
private fun FirebaseLibraryExtension.projectDependenciesByName() =
resolveProjectLevelDependencies().map { it.artifactId.get() }

companion object {
val DEPENDENCIES_TO_IGNORE: List<String> = listOf("protolite-well-known-types")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package com.google.firebase.gradle.plugins

import java.io.File
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeContainer
import org.gradle.api.provider.Provider
Expand Down Expand Up @@ -102,3 +103,14 @@ inline fun <reified T> AttributeContainer.attribute(name: String, value: T) =
*/
inline fun <reified T : Any> org.gradle.api.plugins.PluginManager.`apply`(): Unit =
`apply`(T::class)

/**
* The name provided to this artifact when published.
*
* For example, the following could be an artifact name:
* ```
* "com.google.firebase:firebase-common:16.0.5"
* ```
*/
val Dependency.artifactName: String
get() = listOf(group, name, version).filterNotNull().joinToString(":")
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

package com.google.firebase.gradle.plugins

import org.w3c.dom.Element
import org.w3c.dom.NodeList

/** Replaces all matching substrings with an empty string (nothing) */
fun String.remove(regex: Regex) = replace(regex, "")

Expand All @@ -31,3 +34,74 @@ fun String.remove(str: String) = replace(str, "")
* ```
*/
public fun <T> Sequence<T>.takeAll(): Sequence<T> = take(count())

/**
* Converts an [Element] to an Artifact string.
*
* An Artifact string can be defined as a dependency with the following format:
* ```
* groupId:artifactId:version
* ```
*
* For example, the following would be a valid [Element]:
* ```
* <mySuperCoolElement>
* <groupId>com.google.firebase</groupId>
* <artifactId>firebase-common</artifactId>
* <version>16.0.1</version>
* </mySuperCoolElement>
* ```
*
* @throws NoSuchElementException if the [Element] does not have descendant [Element]s with tags
* that match the components of an Artifact string; groupId, artifactId, version.
*/
fun Element.toArtifactString() =
"${textByTag("groupId")}:${textByTag("artifactId")}:${textByTag("version")}"

/**
* Finds a descendant [Element] by a given [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
*/
fun Element.textByTag(tag: String) = findElementsByTag(tag).first().textContent

/**
* Finds a descendant [Element] by a given [tag], or creates a new one.
*
* If a new one is created, it is also appended to the [ownerDocument][Element.findOrCreate].
*
* @param tag the XML tag to filter for (the special value "*" matches all tags)
* @see findElementsByTag
*/
fun Element.findOrCreate(tag: String) =
findElementsByTag(tag).firstOrNull() ?: ownerDocument.createElement(tag).also { appendChild(it) }

/**
* Returns a [Sequence] of all descendant [Element]s that match the given [tag].
*
* Essentially a rewrite of [Element.getElementsByTagName] that offers the elements as a [Sequence]
* and properly converts them to [Element].
*
* @param tag the XML tag to filter for (the special value "*" matches all tags)
* @see Element.getElementsByTagName
*/
fun Element.findElementsByTag(tag: String) =
getElementsByTagName(tag).children().mapNotNull { it as? Element }

/**
* 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].
*
* Additionally, this operation is _intermediate_ and _stateless_.
*/
fun NodeList.children() = sequence {
for (index in 0..length) {
yield(item(index))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public void apply(Project project) {
String groupId = firebaseLibrary.groupId.get();
String artifactId = firebaseLibrary.artifactId.get();
String artifact =
String.format("%s:%s:%s-SNAPSHOT", groupId, artifactId, sub.getVersion());
String.format("%s:%s:%s", groupId, artifactId, sub.getVersion());
allArtifacts.add(artifact);

if (changedProjects.contains(sub)) {
Expand Down

This file was deleted.

Loading