From bfdd15c4472b39d0e6a797ed52b97cf02a6af1c8 Mon Sep 17 00:00:00 2001 From: Daymon Date: Fri, 21 Apr 2023 16:09:57 -0500 Subject: [PATCH 1/9] Refactor PublishingPlugin --- build.gradle | 6 +- .../gradle/MultiProjectReleasePlugin.java | 128 ------- .../gradle/plugins/CheckHeadDependencies.kt | 38 +- .../firebase/gradle/plugins/DackkaPlugin.kt | 2 + .../firebase/gradle/plugins/GradleUtils.kt | 21 ++ .../firebase/gradle/plugins/ProjectUtils.kt | 27 ++ .../gradle/plugins/PublishingPlugin.kt | 337 ++++++++++++++++++ .../firebase/gradle/plugins/ReleaseConfig.kt | 60 ++++ .../gradle/{ => plugins}/ReleaseGenerator.kt | 32 +- .../gradle/plugins/ci/SmokeTestsPlugin.java | 3 +- .../plugins/publish/PublishingPlugin.java | 270 -------------- 11 files changed, 474 insertions(+), 450 deletions(-) delete mode 100644 buildSrc/src/main/java/com/google/firebase/gradle/MultiProjectReleasePlugin.java create mode 100644 buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt create mode 100644 buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt rename buildSrc/src/main/java/com/google/firebase/gradle/{ => plugins}/ReleaseGenerator.kt (87%) delete mode 100644 buildSrc/src/main/java/com/google/firebase/gradle/plugins/publish/PublishingPlugin.java diff --git a/build.gradle b/build.gradle index e3fe0c5a3fc..2ce0dc4d7a7 100644 --- a/build.gradle +++ b/build.gradle @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import com.google.firebase.gradle.plugins.license.LicenseResolverPlugin -import com.google.firebase.gradle.MultiProjectReleasePlugin - buildscript { // TODO: remove once all sdks have migrated to version catalog ext.kotlinVersion = libs.versions.kotlin.get() @@ -58,11 +55,10 @@ ext { protobufJavaUtilVersion = libs.versions.protobufjavautil.get() } -apply plugin: com.google.firebase.gradle.plugins.publish.PublishingPlugin apply plugin: com.google.firebase.gradle.plugins.ci.ContinuousIntegrationPlugin apply plugin: com.google.firebase.gradle.plugins.ci.SmokeTestsPlugin -apply plugin: MultiProjectReleasePlugin +apply plugin: com.google.firebase.gradle.plugins.PublishingPlugin firebaseContinuousIntegration { ignorePaths = [ diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/MultiProjectReleasePlugin.java b/buildSrc/src/main/java/com/google/firebase/gradle/MultiProjectReleasePlugin.java deleted file mode 100644 index f8bb4fc2f52..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/MultiProjectReleasePlugin.java +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2021 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; - -import com.google.common.collect.ImmutableMap; -import com.google.firebase.gradle.bomgenerator.BomGeneratorTask; -import com.google.firebase.gradle.plugins.FirebaseLibraryExtension; -import com.google.firebase.gradle.plugins.publish.PublishingPlugin; -import java.util.Set; -import java.util.stream.Collectors; -import org.gradle.api.GradleException; -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.tasks.Copy; -import org.gradle.api.tasks.TaskProvider; -import org.gradle.api.tasks.bundling.Zip; - -/** - * Orchestrates the release process by automating validations, documentation and prebuilts - * generation. - * - * - */ -public class MultiProjectReleasePlugin implements Plugin { - - // 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)); - - project - .getTasks() - .create( - "buildBomZip", - Zip.class, - task -> { - task.dependsOn(project.getTasks().create("generateBom", BomGeneratorTask.class)); - task.from("bom"); - task.getArchiveFileName().set("bom.zip"); - task.getDestinationDirectory().set(project.getRootDir()); - }); - - TaskProvider generatorTask = - project - .getTasks() - .register( - "makeReleaseConfigFiles", - ReleaseGenerator.class, - task -> { - task.getCurrentRelease() - .convention(findStringProperty(project, "currentRelease")); - task.getPastRelease().convention(findStringProperty(project, "pastRelease")); - task.getPrintReleaseConfig() - .convention(findStringProperty(project, "printOutput")); - task.getReleaseConfigFile() - .convention(project.getLayout().getBuildDirectory().file("release.cfg")); - task.getReleaseReportFile() - .convention( - project.getLayout().getBuildDirectory().file("release_report.md")); - }); - - project - .getTasks() - .register( - "generateReleaseConfig", - Copy.class, - task -> { - task.from(generatorTask); - task.into(project.getRootDir()); - }); - - project - .getGradle() - .projectsEvaluated( - gradle -> { - Set librariesToPublish = - (Set) - project.getExtensions().getExtraProperties().get("projectsToPublish"); - - Set projectsToPublish = - librariesToPublish.stream().map(lib -> lib.project).collect(Collectors.toSet()); - - Task validateProjectsToPublish = - project.task( - "validateProjectsToPublish", - task -> - task.doLast( - t -> { - if (projectsToPublish.isEmpty()) { - throw new GradleException( - "Required projectsToPublish parameter missing."); - } - })); - project.getTasks().findByName("firebasePublish").dependsOn(validateProjectsToPublish); - }); - } -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/CheckHeadDependencies.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/CheckHeadDependencies.kt index edbbb1e2b34..fa2fd8ec691 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/CheckHeadDependencies.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/CheckHeadDependencies.kt @@ -20,43 +20,45 @@ import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction +/** + * Validates that all project level dependencies are in the release. + * + * Any releasing library that has a project level dependency on another library invokes the release + * of said dependent libary. This is checked via the [artifactId] + * [FirebaseLibraryExtension.artifactId], so that the check is version agnostic. + * + * @throws GradleException if any project level dependencies are found that are not included in the + * release + */ abstract class CheckHeadDependencies : DefaultTask() { @get:Input abstract val projectsToPublish: ListProperty - @get:Input abstract val allFirebaseProjects: ListProperty - @TaskAction fun run() { - val projectsReleasing: Set = projectsToPublish.get().map { it.artifactId.get() }.toSet() + val projectsReleasing = computeProjectsReleasing() + val errors = projectsToPublish .get() - .associate { - it.artifactId.get() to - it - .projectDependenciesByName() - .intersect(allFirebaseProjects.get()) - .subtract(projectsReleasing) - .subtract(DEPENDENCIES_TO_IGNORE) - } + .associate { it.artifactId.get() to it.projectLevelDepsAsArtifactIds() - projectsReleasing } .filterValues { it.isNotEmpty() } .map { "${it.key} requires: ${it.value.joinToString(", ") }" } if (errors.isNotEmpty()) { throw GradleException( - "Project-level dependency errors found. Please update the release config.\n${ - errors.joinToString( - "\n" - ) - }" + "Project-level dependency errors found. Please update the release config.\n" + + "${errors.joinToString("\n")}" ) } } - private fun FirebaseLibraryExtension.projectDependenciesByName() = + private fun FirebaseLibraryExtension.projectLevelDepsAsArtifactIds() = resolveProjectLevelDependencies().map { it.artifactId.get() } + private fun computeProjectsReleasing() = + projectsToPublish.get().map { it.artifactId.get() } + DEPENDENCIES_TO_IGNORE + companion object { - val DEPENDENCIES_TO_IGNORE: List = listOf("protolite-well-known-types") + val DEPENDENCIES_TO_IGNORE = listOf("protolite-well-known-types") } } diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/DackkaPlugin.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/DackkaPlugin.kt index 49602dd1618..a48b993ec81 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/DackkaPlugin.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/DackkaPlugin.kt @@ -134,6 +134,8 @@ abstract class DackkaPlugin : Plugin { kotlinDoc.configure { dependsOn(generateDocumentation, firesiteTransform, copyDocsToCommonDirectory) + + outputs.dir(copyDocsToCommonDirectory.map { it.destinationDir }) } } } diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt index 11333b75b76..1d96054999f 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GradleUtils.kt @@ -15,6 +15,7 @@ package com.google.firebase.gradle.plugins import java.io.File +import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.artifacts.Dependency import org.gradle.api.attributes.Attribute @@ -24,6 +25,7 @@ import org.gradle.kotlin.dsl.apply import org.gradle.workers.WorkAction import org.gradle.workers.WorkParameters import org.gradle.workers.WorkQueue +import org.jetbrains.kotlin.gradle.utils.provider /** * Creates a file at the buildDir for the given [Project]. @@ -45,6 +47,25 @@ fun Project.fileFromBuildDir(path: String) = file("$buildDir/$path") */ fun Provider.childFile(path: String) = map { File("${it.path}/$path") } +/** + * Returns a new [File] under the given sub directory. + * + * Syntax sugar for: + * ``` + * File("$path/$childPath") + * ``` + */ +fun File.childFile(childPath: String) = File("$path/$childPath") + +/** + * Provides a temporary file for use during the task. + * + * Creates a file under the [temporaryDir][DefaultTask.getTemporaryDir] of the task, and should be + * preferred to defining an explicit [File]. This will allow Gradle to make better optimizations on + * our part, and helps us avoid edge-case scenarios like conflicting file names. + */ +fun DefaultTask.tempFile(path: String) = provider { temporaryDir.childFile(path) } + /** * Returns a list of children files, or an empty list if this [File] doesn't exist or doesn't have * any children. diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ProjectUtils.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ProjectUtils.kt index 5b695df4a31..cad62080bf7 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ProjectUtils.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ProjectUtils.kt @@ -18,6 +18,7 @@ import org.gradle.api.Task import org.gradle.api.artifacts.Configuration import org.gradle.api.file.FileCollection import org.gradle.api.provider.Provider +import org.gradle.kotlin.dsl.findByType import org.gradle.kotlin.dsl.getByType /** Checks if the project has any of the common Android specific plugins. */ @@ -74,6 +75,17 @@ val Project.dackkaConfig: Configuration val Project.firebaseLibrary: FirebaseLibraryExtension get() = extensions.getByType() +/** + * The [FirebaseLibraryExtension] for this [Project], or null if not found. + * + * Syntax sugar for: + * ```kotlin + * extensions.findByType() + * ``` + */ +val Project.firebaseLibraryOrNull: FirebaseLibraryExtension? + get() = extensions.findByType() + /** * Hacky-check to see if the module is a ktx variant. * @@ -84,6 +96,21 @@ val Project.firebaseLibrary: FirebaseLibraryExtension val Project.isKTXLibary: Boolean get() = firebaseLibrary.artifactId.get().endsWith("-ktx") +/** + * Provides a project property as the specified type, or null otherwise. + * + * Utilizing a safe cast, an attempt is made to cast the project property (if found) to the receiver + * type. Should this cast fail, the resulting value will be null. + * + * Keep in mind that is provided lazily via a [Provider], so evalutation does not occur until the + * value is needed. + * + * @param property the name of the property to look for + */ +inline fun Project.provideProperty(property: String) = provider { + findProperty(property) as? T +} + /** Fetches the jars of dependencies associated with this configuration through an artifact view. */ val Configuration.jars: FileCollection get() = diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt new file mode 100644 index 00000000000..d64c813d8b0 --- /dev/null +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt @@ -0,0 +1,337 @@ +package com.google.firebase.gradle.plugins + +import com.google.firebase.gradle.bomgenerator.BomGeneratorTask +import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.BUILD_KOTLINDOC_ZIP_TASK +import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.BUILD_MAVEN_ZIP_TASK +import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.FIREBASE_PUBLISH_TASK +import com.google.firebase.gradle.plugins.semver.ApiDiffer +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.publish.maven.tasks.PublishToMavenRepository +import org.gradle.api.tasks.bundling.Zip +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.register + +/** + * Plugin for providing tasks to release [FirebaseLibrary][FirebaseLibraryPlugin] projects. + * + * Projects to release are computed via [computeReleasingLibraries]. A multitude of tasks are then + * registered at the root project. + * + * The following pertain specifically to a release: + * - [CHECK_HEAD_DEPS_TASK][registerCheckHeadDependenciesTask] + * - [VALIDATE_POM_TASK][registerValidatePomForReleaseTask] + * - [VALIDATE_PROJECTS_TO_PUBLISH_TASK][registerValidateProjectsToPublishTask] + * - [BUILD_MAVEN_ZIP_TASK] -> Creates a zip file of the contents of + * [PUBLISH_RELEASING_LIBS_TO_BUILD_TASK] [registerPublishReleasingLibrariesToBuildDirTask] + * - [BUILD_KOTLINDOC_ZIP_TASK] -> Creates a zip file of the contents of + * [GENERATE_KOTLINDOC_FOR_RELEASE_TASK] [registerGenerateKotlindocForReleaseTask] + * - [FIREBASE_PUBLISH_TASK] -> Runs all the tasks above + * + * The following are additional tasks provided- that are either for convenience sake, or are used + * outside of the standard [FIREBASE_PUBLISH_TASK] workflow (possibly at a later time in the release + * cycle): + * - [BUILD_BOM_ZIP_TASK] -> Creates a zip file of the contents of [GENERATE_BOM_TASK] + * [registerGenerateBomTask] + * - [RELEASE_GENEATOR_TASK][registerGenerateReleaseConfigFilesTask] + * - [PUBLISH_RELEASING_LIBS_TO_LOCAL_TASK][registerPublishReleasingLibrariesToMavenLocalTask] + * - [SEMVER_CHECK_TASK][registerSemverCheckForReleaseTask] + */ +abstract class PublishingPlugin : Plugin { + override fun apply(project: Project) { + project.gradle.projectsEvaluated { + val releasingFirebaseLibraries = computeReleasingLibraries(project) + val releasingProjects = releasingFirebaseLibraries.map { it.project } + + val generateBom = registerGenerateBomTask(project) + val validatePomForRelease = registerValidatePomForReleaseTask(project, releasingProjects) + val checkHeadDependencies = + registerCheckHeadDependenciesTask(project, releasingFirebaseLibraries) + val validateProjectsToPublish = + registerValidateProjectsToPublishTask(project, releasingProjects) + val publishReleasingLibrariesToBuildDir = + registerPublishReleasingLibrariesToBuildDirTask(project, releasingProjects) + val generateKotlindocsForRelease = + registerGenerateKotlindocForReleaseTask(project, releasingProjects) + + registerGenerateReleaseConfigFilesTask(project) + registerPublishReleasingLibrariesToMavenLocalTask(project, releasingProjects) + registerSemverCheckForReleaseTask(project, releasingProjects) + + val buildMavenZip = + project.tasks.register(BUILD_MAVEN_ZIP_TASK) { + from(publishReleasingLibrariesToBuildDir) + archiveFileName.set("m2repository.zip") + destinationDirectory.set(project.layout.buildDirectory) + } + + val buildKotlindocZip = + project.tasks.register(BUILD_KOTLINDOC_ZIP_TASK) { + from(generateKotlindocsForRelease) + archiveFileName.set("kotlindoc.zip") + destinationDirectory.set(project.layout.buildDirectory) + } + + project.tasks.register(BUILD_BOM_ZIP_TASK) { + from(generateBom) + archiveFileName.set("bom.zip") + destinationDirectory.set(project.layout.projectDirectory) + } + + project.tasks.register(FIREBASE_PUBLISH_TASK) { + dependsOn( + validateProjectsToPublish, + checkHeadDependencies, + validatePomForRelease, + buildMavenZip, + buildKotlindocZip + ) + + doLast { + logger.lifecycle( + "Publishing the following libraries:\n{}", + releasingProjects.map { it.path }.joinToString("\n") + ) + } + } + } + } + + /** + * Figures out what libraries are intended to release. + * + * Libraries can be provided wither via the `projectsToRelease` property, or a [ReleaseConfig] + * file. + * + * The `projectsToRelease` property takes priority over the [ReleaseConfig]. + * + * While [ReleaseConfig] expects a certain format for its libraries (see [ReleaseConfig.toFile]), + * the `projectsToRelease` property has two ways of structuring libraries; + * - The artifactId: `firebase-appcheck-debug` + * - The project path: `:appcheck:firebase-appcheck-debug` + * + * Either option can take any number of arguments via a comma seperated list: + * ``` + * ./gradlew firebasePublish -PprojectsToRelease="firebase-firestore,firebase-common" + * ``` + * + * If both the `projectsToRelease` and [ReleaseConfig] are either empty, or do not exist- this + * method will return an empty list. + */ + private fun computeReleasingLibraries(project: Project): List { + val releasingLibs = fetchReleasingLibs(project) + val allFirebaseLibraries = project.subprojects.mapNotNull { it.firebaseLibraryOrNull } + + return allFirebaseLibraries.filter { + it.artifactId.get() in releasingLibs || it.path in releasingLibs + } + } + + private fun fetchReleasingLibs(project: Project) = + releasingLibsFromProperty(project) ?: releasingLibsFromReleaseConfig(project) ?: emptyList() + + private fun releasingLibsFromProperty(project: Project) = + project.provideProperty("projectsToRelease").orNull?.split(",") + + private fun releasingLibsFromReleaseConfig(project: Project) = + project.layout.projectDirectory + .file(RELEASE_CONFIG_FILE) + .asFile + .takeIf { it.exists() } + ?.let { ReleaseConfig.fromFile(it).libs.toList() } + + /** + * Registers the [GENERATE_BOM_TASK] task. + * + * Generates a BOM for a release, although it relies on gmaven to be updated- so it should be + * invoked manually later on in the release process. + * + * @see BomGeneratorTask + */ + private fun registerGenerateBomTask(project: Project) = + project.tasks.register(GENERATE_BOM_TASK) + + /** + * Registers the [VALIDATE_POM_TASK] task. + * + * A collection of [PomValidator] for each releasing project. + * + * Ensures that pom dependencies are not accidently downgraded. + */ + private fun registerValidatePomForReleaseTask( + project: Project, + releasingProjects: List + ) = + project.tasks.register(VALIDATE_POM_TASK) { + for (releasingProject in releasingProjects) { + val pomValidatorTask = releasingProject.tasks.named("isPomDependencyValid") + + dependsOn(pomValidatorTask) + } + } + + /** + * Registers the [CHECK_HEAD_DEPS_TASK] task. + * + * Ensures that project level dependencies are included in the release. + * + * @see [CheckHeadDependencies] + */ + private fun registerCheckHeadDependenciesTask( + project: Project, + releasingLibraries: List + ) = + project.tasks.register(CHECK_HEAD_DEPS_TASK) { + projectsToPublish.set(releasingLibraries) + } + + /** + * Registers the [VALIDATE_PROJECTS_TO_PUBLISH_TASK] task. + * + * Ensures that there are releasing projects provided, to avoid potential false positive + * edge-cases. + * + * @throws GradleException if no releasing projects are found. + */ + private fun registerValidateProjectsToPublishTask( + project: Project, + releasingProjects: List + ) = + project.tasks.register(VALIDATE_PROJECTS_TO_PUBLISH_TASK) { + if (releasingProjects.isEmpty()) { + throw GradleException( + "No projects to release. " + + "Ensure you've specified the projectsToPublish parameter, " + + "or have a valid release.cfg file at the root directory." + ) + } + } + + /** + * Registers the [PUBLISH_RELEASING_LIBS_TO_BUILD_TASK] task. + * + * A collection of [publishMavenAarPublicationToBuildDirRepository][PublishToMavenRepository] for + * each releasing project. + * + * The artifacts are provided in a repository under the [buildDir][Project.getBuildDir] of the + * provided [project]. + * + * This task outputs the final repository directory. + */ + private fun registerPublishReleasingLibrariesToBuildDirTask( + project: Project, + releasingProjects: List + ) = + project.tasks.register(PUBLISH_RELEASING_LIBS_TO_BUILD_TASK) { + for (releasingProject in releasingProjects) { + val publishTask = + releasingProject.tasks.named( + "publishMavenAarPublicationToBuildDirRepository" + ) + + dependsOn(publishTask) + outputs.file(publishTask.map { it.repository.url }) + } + } + + /** + * Registers the [GENERATE_KOTLINDOC_FOR_RELEASE_TASK] task. + * + * A collection of [kotlindoc][DackkaPlugin] for each releasing project. + * + * Outputs a directory containing all the documentation generated for releasing projects. + */ + private fun registerGenerateKotlindocForReleaseTask( + project: Project, + releasingProjects: List + ) = + project.tasks.register(GENERATE_KOTLINDOC_FOR_RELEASE_TASK) { + for (releasingProject in releasingProjects) { + val kotlindocTask = releasingProject.tasks.named("kotlindoc") + + dependsOn(kotlindocTask) + outputs.dir(kotlindocTask.map { it.outputs.files }) + } + } + + /** + * Registers the [RELEASE_GENEATOR_TASK] task. + * + * Creates a config file that keeps track of what libraries are releasing. To learn more about the + * file's format see [ReleaseConfig]. + * + * @see [ReleaseGenerator] + */ + private fun registerGenerateReleaseConfigFilesTask(project: Project) = + project.tasks.register(RELEASE_GENEATOR_TASK) { + currentRelease.convention(project.provideProperty("currentRelease")) + pastRelease.convention(project.provideProperty("pastRelease")) + printReleaseConfig.convention(project.provideProperty("printOutput")) + + releaseConfigFile.convention(project.layout.projectDirectory.file(RELEASE_CONFIG_FILE)) + releaseReportFile.convention(project.layout.buildDirectory.file(RELEASE_REPORT_FILE)) + } + + /** + * Registers the [PUBLISH_RELEASING_LIBS_TO_LOCAL_TASK] task. + * + * A collection of [publishMavenAarPublicationToMavenLocal][PublishToMavenRepository] for each + * releasing project. + * + * Allows users to do local testing of releasing libraries, pulling the artifacts via the maven + * local repository. + */ + private fun registerPublishReleasingLibrariesToMavenLocalTask( + project: Project, + releasingProjects: List + ) = + project.tasks.register(PUBLISH_RELEASING_LIBS_TO_LOCAL_TASK) { + for (releasingProject in releasingProjects) { + val publishTask = releasingProject.tasks.named("publishMavenAarPublicationToMavenLocal") + + dependsOn(publishTask) + } + } + + /** + * Registers the [SEMVER_CHECK_TASK] task. + * + * A collection of [ApiDiffer] for each releasing project. + * + * Ensures that the version of a releasing project aligns with the API. That is, if the API shows + * signs of a major or minor bump- the version is bumped as expected. + */ + private fun registerSemverCheckForReleaseTask( + project: Project, + releasingProjects: List + ) = + project.tasks.register(SEMVER_CHECK_TASK) { + for (releasingProject in releasingProjects) { + val semverCheckTask = releasingProject.tasks.named("semverCheck") + + dependsOn(semverCheckTask) + } + } + + companion object { + const val RELEASE_CONFIG_FILE = "release.cfg" + const val RELEASE_REPORT_FILE = "release_report.md" + + const val GENERATE_BOM_TASK = "generateBom" + const val VALIDATE_PROJECTS_TO_PUBLISH_TASK = "validateProjectsToPublish" + const val SEMVER_CHECK_TASK = "semverCheckForRelease" + const val RELEASE_GENEATOR_TASK = "generateReleaseConfig" + const val VALIDATE_POM_TASK = "validatePomForRelease" + const val PUBLISH_RELEASING_LIBS_TO_BUILD_TASK = "publishReleasingLibrariesToBuildDir" + const val PUBLISH_RELEASING_LIBS_TO_LOCAL_TASK = "publishReleasingLibrariesToMavenLocal" + const val GENERATE_KOTLINDOC_FOR_RELEASE_TASK = "generateKotlindocForRelease" + const val CHECK_HEAD_DEPS_TASK = "checkHeadDependencies" + const val BUILD_MAVEN_ZIP_TASK = "buildMavenZip" + const val BUILD_KOTLINDOC_ZIP_TASK = "buildKotlindocZip" + const val BUILD_BOM_ZIP_TASK = "buildBomZip" + const val FIREBASE_PUBLISH_TASK = "firebasePublish" + + const val BUILD_DIR_REPOSITORY_DIR = "m2repository" + } +} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt new file mode 100644 index 00000000000..599e01a1ef8 --- /dev/null +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt @@ -0,0 +1,60 @@ +package com.google.firebase.gradle.plugins + +import com.google.firebase.gradle.plugins.ReleaseConfig.Companion.fromFile +import java.io.File + +/** + * Container for providing data about a release. + * + * Allows a type safe representation of data generated for an official release. + * + * @see ReleaseGenerator + * @see fromFile + * @see toFile + * + * @property releaseName the name of a release, such as `m130` + * @property libs a list of project paths intending to release + */ +data class ReleaseConfig(val releaseName: String, val libs: Set) { + companion object { + /** + * Parses a [ReleaseConfig] from the contents of a given [File]. + * + * Allows one to reuse an already generated [ReleaseConfig] that was saved to disc. + * + * @param file the [File] to parse + * @see toFile + */ + fun fromFile(file: File): ReleaseConfig { + val contents = file.readLines() + val libs = contents.filter { it.startsWith(":") }.toSet() + val releaseName = contents.first { it.startsWith("name") }.substringAfter("=").trim() + return ReleaseConfig(releaseName, libs) + } + } + + /** + * Converts a [ReleaseConfig] to a [String] to be saved in a [File]. + * + * An example of the output fomat can be seen below: + * ``` + * [release] + * name = m130 + * + * [modules] + * :firebase-common + * :appcheck:firebase-appcheck + * ``` + * + * @see fromFile + */ + fun toFile() = + """ + |[release] + |name = $releaseName + + |[modules] + |${libs.sorted().joinToString("\n")} + """ + .trimMargin() +} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/ReleaseGenerator.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt similarity index 87% rename from buildSrc/src/main/java/com/google/firebase/gradle/ReleaseGenerator.kt rename to buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt index 36c9e6bb885..de2d2b240ed 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/ReleaseGenerator.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt @@ -11,10 +11,9 @@ // 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 +package com.google.firebase.gradle.plugins import com.google.common.collect.ImmutableList -import com.google.firebase.gradle.plugins.FirebaseLibraryExtension import java.io.File import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.ListBranchCommand @@ -27,11 +26,12 @@ import org.gradle.api.Project import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.findByType -data class FirebaseLibrary(val moduleNames: List, val directories: List) +data class FirebaseLibraryEntry(val moduleNames: List, val directories: List) data class CommitDiff( val commitId: String, @@ -57,7 +57,7 @@ abstract class ReleaseGenerator : DefaultTask() { @get:Input abstract val pastRelease: Property - @get:Input abstract val printReleaseConfig: Property + @get:Optional @get:Input abstract val printReleaseConfig: Property @get:OutputFile abstract val releaseConfigFile: RegularFileProperty @@ -84,7 +84,7 @@ abstract class ReleaseGenerator : DefaultTask() { ReleaseConfig(currentRelease.get(), libsToRelease.map { it.path }.toSet()) ) val releaseReport = generateReleaseReport(changes, changedLibsWithNoChangelog) - if (printReleaseConfig.get().toBoolean()) { + if (printReleaseConfig.orNull.toBoolean()) { project.logger.info(releaseReport) } writeReleaseReport(releaseReportFile.get().asFile, releaseReport) @@ -207,25 +207,3 @@ abstract class ReleaseGenerator : DefaultTask() { private fun getRelativeDir(project: Project) = project.path.substring(1).replace(':', '/') } - -data class ReleaseConfig(val releaseName: String, val libs: Set) { - companion object { - fun fromFile(file: File): ReleaseConfig { - val contents = file.readLines() - val libs = contents.filter { it.startsWith(":") }.toSet() - val releaseName = contents.first { it.startsWith("name") }.substringAfter("=").trim() - return ReleaseConfig(releaseName, libs) - } - } - - fun toFile() = - """ - |[release] - |name = $releaseName - |mode = RELEASE - - |[modules] - |${libs.sorted().joinToString("\n")} - """ - .trimMargin() -} diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java index 7855c987504..492f5d517b0 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java @@ -69,8 +69,7 @@ public void apply(Project project) { }); // Reuse the publish task for building the libraries. - Task publishAllTask = project.getTasks().getByPath("publishAllToBuildDir"); - assembleAllTask.dependsOn(publishAllTask); + assembleAllTask.dependsOn("publishAllPublicationsToBuildDirRepository"); // Generate a JSON file listing the artifacts after everything is complete. assembleAllTask.doLast( diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/publish/PublishingPlugin.java b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/publish/PublishingPlugin.java deleted file mode 100644 index 2cc038bfd20..00000000000 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/publish/PublishingPlugin.java +++ /dev/null @@ -1,270 +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.publish; - -import com.google.firebase.gradle.plugins.CheckHeadDependencies; -import com.google.firebase.gradle.plugins.FirebaseLibraryExtension; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import org.gradle.api.Plugin; -import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.tasks.bundling.Zip; - -/** - * Enables releasing of the SDKs. - * - *

The plugin supports multiple workflows. - * - *

Build all SDK snapshots - * - *

- * ./gradlew publishAllToLocal # publishes to maven local repo
- * ./gradlew publishAllToBuildDir # publishes to build/m2repository
- * 
- * - *

Prepare a release - * - *

- * ./gradlew -PpublishConfigFilePath=release.cfg
- *           -PpublishMode=(RELEASE|SNAPSHOT) \
- *           firebasePublish
- * 
- * - *
- * ./gradlew -PprojectsToPublish="firebase-inappmessaging,firebase-inappmessaging-display"\
- *           -PpublishMode=(RELEASE|SNAPSHOT) \
- *           firebasePublish
- * 
- * - *
    - *
  • {@code publishConfigFilePath} is the path to the configuration file from which to read the - * list of projects to release. The file format should be consistent with Python's - * configparser, and the list of projects should be in a section called "modules". If both - * this, and {@code projectsToPublish} are specified, this property takes precedence.
    - *
    - * Example config file content: - *
    - *         [release]
    - *         name = M126
    - *         ...
    - *         [modules]
    - *         firebase-database
    - *         firebase-common
    - *         firebase-firestore
    - *       
    - *
  • {@code projectsToPublish} is a list of projects to release separated by {@code - * projectsToPublishSeparator}(default: ","), these projects will have their versions depend - * on the {@code publishMode} parameter. - *
  • {@code publishMode} can one of two values: {@code SNAPSHOT} results in version to be {@code - * "${project.version}-SNAPSHOT"}. {@code RELEASE} results in versions to be whatever is - * specified in the SDKs gradle.properties. Additionally when {@code RELEASE} is specified, - * the release validates the pom to make sure no SDKs point to unreleased SDKs. - *
  • {@code projectsToPublishSeparator}: separates project names in the {@code - * projectsToPublish} parameter. Default is: ",". - *

    The artifacts will be built to build/m2repository.zip - *

    Prepare release(to maven local) - *

    Same as above but publishes artifacts to maven local. - *

    - * ./gradlew -PprojectsToPublish="firebase-inappmessaging,firebase-inappmessaging-display"\
    - *           -PpublishMode=(RELEASE|SNAPSHOT) \
    - *           publishProjectsToMavenLocal
    - * 
    - */ -public class PublishingPlugin implements Plugin { - - public PublishingPlugin() {} - - private static String getPropertyOr(Project p, String property, String defaultValue) { - Object value = p.findProperty(property); - if (value != null) { - return value.toString(); - } - return defaultValue; - } - - private static String getPublishTask(FirebaseLibraryExtension p, String repoName) { - return p.getPath() + ":publishMavenAarPublicationTo" + repoName; - } - - @Override - public void apply(Project project) { - String projectNamesToPublish = getPropertyOr(project, "projectsToPublish", ""); - String projectsToPublishSeparator = getPropertyOr(project, "projectsToPublishSeparator", ","); - String publishConfigFilePath = getPropertyOr(project, "publishConfigFilePath", ""); - - Task publishAllToLocal = project.task("publishAllToLocal"); - Task publishAllToBuildDir = project.task("publishAllToBuildDir"); - Task firebasePublish = project.task("firebasePublish"); - - project - .getGradle() - .projectsEvaluated( - gradle -> { - List projectsNames; - if (!publishConfigFilePath.isEmpty()) { - projectsNames = readReleaseConfigFile(publishConfigFilePath); - } else { - projectsNames = - List.of(projectNamesToPublish.split(projectsToPublishSeparator, -1)); - } - - Set allFirebaseProjects = - project.getSubprojects().stream() - .map(sub -> sub.getExtensions().findByType(FirebaseLibraryExtension.class)) - .filter(ext -> ext != null) - .map(ext -> ext.artifactId.get()) - .collect(Collectors.toSet()); - - Set projectsToPublish = - projectsNames.stream() - .filter(name -> !name.isEmpty()) - .map( - name -> - project - .project(name) - .getExtensions() - .findByType(FirebaseLibraryExtension.class)) - .filter(ext -> ext != null) - .flatMap(lib -> lib.getLibrariesToRelease().stream()) - .collect(Collectors.toSet()); - - project - .getExtensions() - .getExtraProperties() - .set("projectsToPublish", projectsToPublish); - - project - .getTasks() - .register( - "semverCheckForRelease", - t -> { - for (FirebaseLibraryExtension toPublish : projectsToPublish) { - t.dependsOn(toPublish.getPath() + ":semverCheck"); - } - }); - project - .getTasks() - .create( - "validatePomForRelease", - t -> { - for (FirebaseLibraryExtension toPublish : projectsToPublish) { - t.dependsOn(toPublish.getPath() + ":isPomDependencyValid"); - } - }); - project.subprojects( - sub -> { - if (sub.getExtensions().findByType(FirebaseLibraryExtension.class) == null) - return; - publishAllToLocal.dependsOn( - sub.getPath() + ":publishMavenAarPublicationToMavenLocal"); - publishAllToBuildDir.dependsOn( - sub.getPath() + ":publishMavenAarPublicationToBuildDirRepository"); - }); - project - .getTasks() - .create( - "checkHeadDependencies", - CheckHeadDependencies.class, - t -> { - t.getProjectsToPublish().set(projectsToPublish); - t.getAllFirebaseProjects().set(allFirebaseProjects); - firebasePublish.dependsOn(t); - }); - - project - .getTasks() - .register( - "publishProjectsToMavenLocal", - t -> { - for (FirebaseLibraryExtension toPublish : projectsToPublish) { - t.dependsOn(getPublishTask(toPublish, "MavenLocal")); - } - }); - - Task publishProjectsToBuildDir = - project - .getTasks() - .create( - "publishProjectsToBuildDir", - t -> { - for (FirebaseLibraryExtension toPublish : projectsToPublish) { - t.dependsOn(getPublishTask(toPublish, "BuildDirRepository")); - t.dependsOn(toPublish.getPath() + ":kotlindoc"); - } - }); - Zip buildMavenZip = - project - .getTasks() - .create( - "buildMavenZip", - Zip.class, - zip -> { - zip.dependsOn(publishProjectsToBuildDir); - zip.getArchiveFileName().set("m2repository.zip"); - zip.getDestinationDirectory().set(project.getBuildDir()); - zip.from(project.getBuildDir() + "/m2repository"); - }); - Zip buildKotlindocZip = - project - .getTasks() - .create( - "buildKotlindocZip", - Zip.class, - zip -> { - zip.dependsOn(publishProjectsToBuildDir); - zip.getArchiveFileName().set("kotlindoc.zip"); - zip.getDestinationDirectory().set(project.getBuildDir()); - zip.from(project.getBuildDir() + "/firebase-kotlindoc"); - }); - Task info = - project - .getTasks() - .create( - "publishPrintInfo", - t -> - publishAllToLocal.doLast( - it -> - project - .getLogger() - .lifecycle( - "Publishing the following libraries:\n{}", - projectsToPublish.stream() - .map(FirebaseLibraryExtension::getPath) - .collect(Collectors.joining("\n"))))); - buildMavenZip.mustRunAfter(info); - buildKotlindocZip.mustRunAfter(info); - firebasePublish.dependsOn(info, buildMavenZip, buildKotlindocZip); - }); - } - - private List readReleaseConfigFile(String publishConfigurationFilePath) { - try (Stream stream = Files.lines(Path.of(publishConfigurationFilePath))) { - return stream - .dropWhile((line) -> !line.equals("[modules]")) - // We need to skip the "[modules]" line since it's not dropped - .skip(1) - .collect(Collectors.toList()); - } catch (IOException e) { - throw new IllegalArgumentException( - "Error reading configuration file " + publishConfigurationFilePath, e); - } - } -} From 2708dc30672a7671276ef6d406624aa1c7080235 Mon Sep 17 00:00:00 2001 From: Daymon Date: Fri, 21 Apr 2023 16:17:25 -0500 Subject: [PATCH 2/9] Update workflows --- .github/workflows/build-release-artifacts.yml | 2 +- .github/workflows/check-head-dependencies.yml | 2 +- .github/workflows/semver-check.yml | 2 +- .github/workflows/validate-dependencies.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-release-artifacts.yml b/.github/workflows/build-release-artifacts.yml index 049f1acdd50..2c0e050e947 100644 --- a/.github/workflows/build-release-artifacts.yml +++ b/.github/workflows/build-release-artifacts.yml @@ -24,7 +24,7 @@ jobs: - name: Perform gradle build run: | - ./gradlew firebasePublish -PpublishConfigFilePath=release.cfg -PpublishMode=RELEASE + ./gradlew firebasePublish - name: Generate release notes run: | diff --git a/.github/workflows/check-head-dependencies.yml b/.github/workflows/check-head-dependencies.yml index d451b78d4ce..9dac59365df 100644 --- a/.github/workflows/check-head-dependencies.yml +++ b/.github/workflows/check-head-dependencies.yml @@ -14,4 +14,4 @@ jobs: - name: Perform gradle build run: | - ./gradlew checkHeadDependencies -PpublishConfigFilePath=release.cfg -PpublishMode=RELEASE + ./gradlew checkHeadDependencies diff --git a/.github/workflows/semver-check.yml b/.github/workflows/semver-check.yml index c2729df2106..aad6bff759d 100644 --- a/.github/workflows/semver-check.yml +++ b/.github/workflows/semver-check.yml @@ -14,4 +14,4 @@ jobs: - name: Perform gradle build run: | - ./gradlew semverCheckForRelease -PpublishConfigFilePath=release.cfg -PpublishMode=RELEASE + ./gradlew semverCheckForRelease diff --git a/.github/workflows/validate-dependencies.yml b/.github/workflows/validate-dependencies.yml index 73bacb1d08b..5c7a19643b0 100644 --- a/.github/workflows/validate-dependencies.yml +++ b/.github/workflows/validate-dependencies.yml @@ -14,4 +14,4 @@ jobs: - name: Perform gradle build run: | - ./gradlew validatePomForRelease -PpublishConfigFilePath=release.cfg -PpublishMode=RELEASE + ./gradlew validatePomForRelease From ed0a8c0c5ab09b53f3f00436b18bf6bac3e05d72 Mon Sep 17 00:00:00 2001 From: Daymon Date: Fri, 21 Apr 2023 16:35:07 -0500 Subject: [PATCH 3/9] Add missing copyright --- .../firebase/gradle/plugins/PublishingPlugin.kt | 14 ++++++++++++++ .../firebase/gradle/plugins/ReleaseConfig.kt | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt index d64c813d8b0..21931000749 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt @@ -1,3 +1,17 @@ +// Copyright 2023 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 com.google.firebase.gradle.bomgenerator.BomGeneratorTask diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt index 599e01a1ef8..0dcf81138c2 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt @@ -1,3 +1,17 @@ +// Copyright 2023 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 com.google.firebase.gradle.plugins.ReleaseConfig.Companion.fromFile From b8015483c7095ffa81df94a4b82365f2de701c3a Mon Sep 17 00:00:00 2001 From: Daymon Date: Fri, 21 Apr 2023 16:37:39 -0500 Subject: [PATCH 4/9] Fix typo --- .../java/com/google/firebase/gradle/plugins/PublishingPlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt index 21931000749..7346c24faee 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt @@ -115,7 +115,7 @@ abstract class PublishingPlugin : Plugin { /** * Figures out what libraries are intended to release. * - * Libraries can be provided wither via the `projectsToRelease` property, or a [ReleaseConfig] + * Libraries can be provided either via the `projectsToRelease` property, or a [ReleaseConfig] * file. * * The `projectsToRelease` property takes priority over the [ReleaseConfig]. From 91f0cf5f3a204713a263471daa7e9008a4f63f71 Mon Sep 17 00:00:00 2001 From: Daymon Date: Mon, 24 Apr 2023 12:25:26 -0500 Subject: [PATCH 5/9] Fix old package being used for exposing plugin --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 73d6169bd6f..300eadde08d 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -90,7 +90,7 @@ gradlePlugin { } register("publishingPlugin") { id = "PublishingPlugin" - implementationClass = "com.google.firebase.gradle.plugins.publish.PublishingPlugin" + implementationClass = "com.google.firebase.gradle.plugins.PublishingPlugin" } register("firebaseLibraryPlugin") { id = "firebase-library" From 22fdeb27b245b4b144d770db9af5e340a3f19295 Mon Sep 17 00:00:00 2001 From: Daymon Date: Mon, 24 Apr 2023 14:07:59 -0500 Subject: [PATCH 6/9] Fix bugs with missing kotlindoc + validatePomForRelease testing --- .../firebase/gradle/plugins/GmavenHelper.kt | 1 + .../gradle/plugins/PublishingPlugin.kt | 69 ++++++++++++------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GmavenHelper.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GmavenHelper.kt index 64e23ca898f..2f2618cb898 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GmavenHelper.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/GmavenHelper.kt @@ -20,6 +20,7 @@ import javax.xml.parsers.DocumentBuilder import javax.xml.parsers.DocumentBuilderFactory import org.w3c.dom.Document +/** TODO(b/279466888) - Make GmavenHelper testable */ class GmavenHelper(val groupId: String, val artifactId: String) { val GMAVEN_ROOT = "https://dl.google.com/dl/android/maven2" diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt index 7346c24faee..cd589903e5d 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt @@ -15,9 +15,13 @@ package com.google.firebase.gradle.plugins import com.google.firebase.gradle.bomgenerator.BomGeneratorTask +import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.BUILD_BOM_ZIP_TASK import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.BUILD_KOTLINDOC_ZIP_TASK import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.BUILD_MAVEN_ZIP_TASK import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.FIREBASE_PUBLISH_TASK +import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.GENERATE_BOM_TASK +import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.GENERATE_KOTLINDOC_FOR_RELEASE_TASK +import com.google.firebase.gradle.plugins.PublishingPlugin.Companion.PUBLISH_RELEASING_LIBS_TO_BUILD_TASK import com.google.firebase.gradle.plugins.semver.ApiDiffer import org.gradle.api.GradleException import org.gradle.api.Plugin @@ -67,7 +71,7 @@ abstract class PublishingPlugin : Plugin { val publishReleasingLibrariesToBuildDir = registerPublishReleasingLibrariesToBuildDirTask(project, releasingProjects) val generateKotlindocsForRelease = - registerGenerateKotlindocForReleaseTask(project, releasingProjects) + registerGenerateKotlindocForReleaseTask(project, releasingFirebaseLibraries) registerGenerateReleaseConfigFilesTask(project) registerPublishReleasingLibrariesToMavenLocalTask(project, releasingProjects) @@ -97,7 +101,7 @@ abstract class PublishingPlugin : Plugin { dependsOn( validateProjectsToPublish, checkHeadDependencies, - validatePomForRelease, + // validatePomForRelease, TODO(b/279466888) - Make GmavenHelper testable buildMavenZip, buildKotlindocZip ) @@ -115,38 +119,52 @@ abstract class PublishingPlugin : Plugin { /** * Figures out what libraries are intended to release. * - * Libraries can be provided either via the `projectsToRelease` property, or a [ReleaseConfig] + * Libraries can be provided either via the `projectsToPublish` property, or a [ReleaseConfig] * file. * - * The `projectsToRelease` property takes priority over the [ReleaseConfig]. + * The `projectsToPublish` property takes priority over the [ReleaseConfig]. It expects a comma + * separated list of the publishing project(s) `artifactId`. It also takes into account + * [librariesToRelease][FirebaseLibraryExtension.getLibrariesToRelease] -> so there's no need to + * specify multiple of the same co-releasing libs. * - * While [ReleaseConfig] expects a certain format for its libraries (see [ReleaseConfig.toFile]), - * the `projectsToRelease` property has two ways of structuring libraries; - * - The artifactId: `firebase-appcheck-debug` - * - The project path: `:appcheck:firebase-appcheck-debug` + * The [ReleaseConfig] is a pre-defined set of data for a release. It expects a valid [file] + * [ReleaseConfig.fromFile], which provides a list of libraries via their [path] + * [FirebaseLibraryExtension.getPath]. Additionally, it does __NOT__ take into account + * co-releasing libraries-> meaning libraries that should be releasing alongside one another will + * need to be individually specified in the [ReleaseConfig], otherwise it will likely cause an + * error during the release process. * - * Either option can take any number of arguments via a comma seperated list: + * Example usage of `projectsToPublish`: * ``` - * ./gradlew firebasePublish -PprojectsToRelease="firebase-firestore,firebase-common" + * ./gradlew firebasePublish -PprojectsToPublish="firebase-firestore,firebase-common" * ``` * - * If both the `projectsToRelease` and [ReleaseConfig] are either empty, or do not exist- this - * method will return an empty list. + * See [ReleaseConfig.toFile] for example usage of [ReleaseConfig]. + * + * If both `projectsToPublish` and [ReleaseConfig] are empty, or do not exist- this method will + * return an empty list. */ private fun computeReleasingLibraries(project: Project): List { - val releasingLibs = fetchReleasingLibs(project) val allFirebaseLibraries = project.subprojects.mapNotNull { it.firebaseLibraryOrNull } - return allFirebaseLibraries.filter { - it.artifactId.get() in releasingLibs || it.path in releasingLibs + val releasingLibrariesFromProperty = releasingLibsFromProperty(project) + if (releasingLibrariesFromProperty != null) { + return allFirebaseLibraries + .filter { it.artifactId.get() in releasingLibrariesFromProperty } + .flatMap { it.librariesToRelease } + .distinctBy { it.artifactId.get() } } - } - private fun fetchReleasingLibs(project: Project) = - releasingLibsFromProperty(project) ?: releasingLibsFromReleaseConfig(project) ?: emptyList() + val releasingLibrariesFromReleseConfig = releasingLibsFromReleaseConfig(project) + if (releasingLibrariesFromReleseConfig != null) { + return allFirebaseLibraries.filter { it.path in releasingLibrariesFromReleseConfig } + } + + return emptyList() + } private fun releasingLibsFromProperty(project: Project) = - project.provideProperty("projectsToRelease").orNull?.split(",") + project.provideProperty("projectsToPublish").orNull?.split(",") private fun releasingLibsFromReleaseConfig(project: Project) = project.layout.projectDirectory @@ -252,20 +270,23 @@ abstract class PublishingPlugin : Plugin { /** * Registers the [GENERATE_KOTLINDOC_FOR_RELEASE_TASK] task. * - * A collection of [kotlindoc][DackkaPlugin] for each releasing project. + * A collection of [kotlindoc][DackkaPlugin] for each releasing library. * * Outputs a directory containing all the documentation generated for releasing projects. */ private fun registerGenerateKotlindocForReleaseTask( project: Project, - releasingProjects: List + releasingLibraries: List ) = project.tasks.register(GENERATE_KOTLINDOC_FOR_RELEASE_TASK) { - for (releasingProject in releasingProjects) { - val kotlindocTask = releasingProject.tasks.named("kotlindoc") + for (releasingLibrary in releasingLibraries) { + val kotlindocTask = releasingLibrary.project.tasks.named("kotlindoc") dependsOn(kotlindocTask) - outputs.dir(kotlindocTask.map { it.outputs.files }) + + if (releasingLibrary.publishJavadoc) { + outputs.dir(kotlindocTask.map { it.outputs.files }).optional() + } } } From 3626bd0dc8317f4c9a22591beff12cdb24ce1318 Mon Sep 17 00:00:00 2001 From: Daymon Date: Mon, 24 Apr 2023 14:39:07 -0500 Subject: [PATCH 7/9] Add publishAll task for smoke tests --- .../gradle/plugins/PublishingPlugin.kt | 35 ++++++++++++++++--- .../gradle/plugins/ci/SmokeTestsPlugin.java | 3 +- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt index cd589903e5d..b788bbb5c2a 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt @@ -55,11 +55,13 @@ import org.gradle.kotlin.dsl.register * - [RELEASE_GENEATOR_TASK][registerGenerateReleaseConfigFilesTask] * - [PUBLISH_RELEASING_LIBS_TO_LOCAL_TASK][registerPublishReleasingLibrariesToMavenLocalTask] * - [SEMVER_CHECK_TASK][registerSemverCheckForReleaseTask] + * - [PUBLISH_ALL_TO_BUILD_TASK][registerPublishAllToBuildDir] */ abstract class PublishingPlugin : Plugin { override fun apply(project: Project) { project.gradle.projectsEvaluated { - val releasingFirebaseLibraries = computeReleasingLibraries(project) + val allFirebaseLibraries = project.subprojects.mapNotNull { it.firebaseLibraryOrNull } + val releasingFirebaseLibraries = computeReleasingLibraries(project, allFirebaseLibraries) val releasingProjects = releasingFirebaseLibraries.map { it.project } val generateBom = registerGenerateBomTask(project) @@ -76,6 +78,7 @@ abstract class PublishingPlugin : Plugin { registerGenerateReleaseConfigFilesTask(project) registerPublishReleasingLibrariesToMavenLocalTask(project, releasingProjects) registerSemverCheckForReleaseTask(project, releasingProjects) + registerPublishAllToBuildDir(project, allFirebaseLibraries) val buildMavenZip = project.tasks.register(BUILD_MAVEN_ZIP_TASK) { @@ -144,9 +147,10 @@ abstract class PublishingPlugin : Plugin { * If both `projectsToPublish` and [ReleaseConfig] are empty, or do not exist- this method will * return an empty list. */ - private fun computeReleasingLibraries(project: Project): List { - val allFirebaseLibraries = project.subprojects.mapNotNull { it.firebaseLibraryOrNull } - + private fun computeReleasingLibraries( + project: Project, + allFirebaseLibraries: List + ): List { val releasingLibrariesFromProperty = releasingLibsFromProperty(project) if (releasingLibrariesFromProperty != null) { return allFirebaseLibraries @@ -349,6 +353,28 @@ abstract class PublishingPlugin : Plugin { } } + /** + * Registers the [PUBLISH_ALL_TO_BUILD_TASK] task. + * + * A collection of [publishMavenAarPublicationToBuildDirRepository][PublishToMavenRepository] for + * __ALL__ firebase library projects. + * + * The artifacts are provided in a repository under the [buildDir][Project.getBuildDir] of the + * provided [project]. + */ + private fun registerPublishAllToBuildDir( + project: Project, + allFirebaseLibraries: List + ) = + project.tasks.register(PUBLISH_ALL_TO_BUILD_TASK) { + for (firebaseLibrary in allFirebaseLibraries) { + val publishTask = + firebaseLibrary.project.tasks.named("publishMavenAarPublicationToBuildDirRepository") + + dependsOn(publishTask) + } + } + companion object { const val RELEASE_CONFIG_FILE = "release.cfg" const val RELEASE_REPORT_FILE = "release_report.md" @@ -366,6 +392,7 @@ abstract class PublishingPlugin : Plugin { const val BUILD_KOTLINDOC_ZIP_TASK = "buildKotlindocZip" const val BUILD_BOM_ZIP_TASK = "buildBomZip" const val FIREBASE_PUBLISH_TASK = "firebasePublish" + const val PUBLISH_ALL_TO_BUILD_TASK = "publishAllToBuildDir" const val BUILD_DIR_REPOSITORY_DIR = "m2repository" } diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java index 492f5d517b0..3556c1b62b9 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ci/SmokeTestsPlugin.java @@ -15,6 +15,7 @@ package com.google.firebase.gradle.plugins.ci; import com.google.firebase.gradle.plugins.FirebaseLibraryExtension; +import com.google.firebase.gradle.plugins.PublishingPlugin; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; @@ -69,7 +70,7 @@ public void apply(Project project) { }); // Reuse the publish task for building the libraries. - assembleAllTask.dependsOn("publishAllPublicationsToBuildDirRepository"); + assembleAllTask.dependsOn(PublishingPlugin.PUBLISH_ALL_TO_BUILD_TASK); // Generate a JSON file listing the artifacts after everything is complete. assembleAllTask.doLast( From 4485baeeefa1dacb4ad8e7ccff4b3bb6d17b509a Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 25 Apr 2023 13:06:02 -0500 Subject: [PATCH 8/9] Utilize kotlinx-serialization instead of homebrew --- .github/workflows/create_releases.yml | 2 +- buildSrc/build.gradle.kts | 2 + .../gradle/plugins/PublishingPlugin.kt | 6 +-- .../firebase/gradle/plugins/ReleaseConfig.kt | 48 ++++++++----------- .../gradle/plugins/ReleaseGenerator.kt | 13 ++--- docs/make_release_notes.py | 2 +- gradle/libs.versions.toml | 1 + 7 files changed, 33 insertions(+), 41 deletions(-) diff --git a/.github/workflows/create_releases.yml b/.github/workflows/create_releases.yml index 48ca77079e0..0f37c425066 100644 --- a/.github/workflows/create_releases.yml +++ b/.github/workflows/create_releases.yml @@ -39,7 +39,7 @@ jobs: with: base: 'releases/${{ inputs.name }}' branch: 'releases/${{ inputs.name }}.release' - add-paths: release.cfg,release_report.md + add-paths: release.json,release_report.md title: '${{ inputs.name}} release' body: 'Auto-generated PR for release ${{ inputs.name}}' commit-message: 'Create release config for ${{ inputs.name }}' diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 300eadde08d..4348d604566 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -15,6 +15,7 @@ plugins { id("com.ncorti.ktfmt.gradle") version "0.11.0" id("com.github.sherter.google-java-format") version "0.9" + kotlin("plugin.serialization") version "1.7.10" `kotlin-dsl` } @@ -67,6 +68,7 @@ dependencies { implementation("org.eclipse.jgit:org.eclipse.jgit:6.3.0.202209071007-r") + implementation(libs.kotlinx.serialization.json) implementation("com.google.code.gson:gson:2.8.9") implementation("com.android.tools.build:gradle:7.2.2") implementation("com.android.tools.build:builder-test-api:7.2.2") diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt index b788bbb5c2a..7299c7e7487 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/PublishingPlugin.kt @@ -175,7 +175,7 @@ abstract class PublishingPlugin : Plugin { .file(RELEASE_CONFIG_FILE) .asFile .takeIf { it.exists() } - ?.let { ReleaseConfig.fromFile(it).libs.toList() } + ?.let { ReleaseConfig.fromFile(it).libraries } /** * Registers the [GENERATE_BOM_TASK] task. @@ -239,7 +239,7 @@ abstract class PublishingPlugin : Plugin { throw GradleException( "No projects to release. " + "Ensure you've specified the projectsToPublish parameter, " + - "or have a valid release.cfg file at the root directory." + "or have a valid $RELEASE_CONFIG_FILE file at the root directory." ) } } @@ -376,7 +376,7 @@ abstract class PublishingPlugin : Plugin { } companion object { - const val RELEASE_CONFIG_FILE = "release.cfg" + const val RELEASE_CONFIG_FILE = "release.json" const val RELEASE_REPORT_FILE = "release_report.md" const val GENERATE_BOM_TASK = "generateBom" diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt index 0dcf81138c2..f8d6978aaed 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt @@ -14,8 +14,11 @@ package com.google.firebase.gradle.plugins -import com.google.firebase.gradle.plugins.ReleaseConfig.Companion.fromFile import java.io.File +import kotlinx.serialization.Serializable +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json /** * Container for providing data about a release. @@ -26,11 +29,14 @@ import java.io.File * @see fromFile * @see toFile * - * @property releaseName the name of a release, such as `m130` - * @property libs a list of project paths intending to release + * @property name the name of a release, such as `m130` + * @property libraries a list of project paths intending to release */ -data class ReleaseConfig(val releaseName: String, val libs: Set) { +@Serializable +data class ReleaseConfig(val name: String, val libraries: List) { companion object { + val formatter = Json { prettyPrint = true } + /** * Parses a [ReleaseConfig] from the contents of a given [File]. * @@ -39,36 +45,24 @@ data class ReleaseConfig(val releaseName: String, val libs: Set) { * @param file the [File] to parse * @see toFile */ - fun fromFile(file: File): ReleaseConfig { - val contents = file.readLines() - val libs = contents.filter { it.startsWith(":") }.toSet() - val releaseName = contents.first { it.startsWith("name") }.substringAfter("=").trim() - return ReleaseConfig(releaseName, libs) - } + fun fromFile(file: File): ReleaseConfig = formatter.decodeFromString(file.readText()) } /** - * Converts a [ReleaseConfig] to a [String] to be saved in a [File]. + * Writes a [ReleaseConfig] into a [File] as JSON. * - * An example of the output fomat can be seen below: + * An example of the output can be seen below: * ``` - * [release] - * name = m130 - * - * [modules] - * :firebase-common - * :appcheck:firebase-appcheck + * { + * "name": "m130", + * "libraries": [ + * ":firebase-appdistribution", + * ":firebase-config", + * ] + * } * ``` * * @see fromFile */ - fun toFile() = - """ - |[release] - |name = $releaseName - - |[modules] - |${libs.sorted().joinToString("\n")} - """ - .trimMargin() + fun toFile(file: File) = file.also { it.writeText(formatter.encodeToString(this)) } } diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt index de2d2b240ed..7834c0bff5f 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseGenerator.kt @@ -31,8 +31,6 @@ import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.findByType -data class FirebaseLibraryEntry(val moduleNames: List, val directories: List) - data class CommitDiff( val commitId: String, val author: String, @@ -79,10 +77,10 @@ abstract class ReleaseGenerator : DefaultTask() { libsToRelease.map { it.path }.toSet() val changes = getChangesForLibraries(repo, branchRef, headRef, libsToRelease) - writeReleaseConfig( - releaseConfigFile.get().asFile, - ReleaseConfig(currentRelease.get(), libsToRelease.map { it.path }.toSet()) - ) + + val releaseConfig = ReleaseConfig(currentRelease.get(), libsToRelease.map { it.path }) + releaseConfig.toFile(releaseConfigFile.get().asFile) + val releaseReport = generateReleaseReport(changes, changedLibsWithNoChangelog) if (printReleaseConfig.orNull.toBoolean()) { project.logger.info(releaseReport) @@ -202,8 +200,5 @@ abstract class ReleaseGenerator : DefaultTask() { private fun writeReleaseReport(file: File, report: String) = file.writeText(report) - private fun writeReleaseConfig(file: File, config: ReleaseConfig) = - file.writeText(config.toFile()) - private fun getRelativeDir(project: Project) = project.path.substring(1).replace(':', '/') } diff --git a/docs/make_release_notes.py b/docs/make_release_notes.py index 4c32465cf01..62aa486a2a0 100644 --- a/docs/make_release_notes.py +++ b/docs/make_release_notes.py @@ -23,7 +23,7 @@ import string from dataclasses import dataclass, field - +# TODO(b/279610345) - Replace this with a Gradle task @dataclass class Changelog: path: str diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 873c6c3f7ed..41969acbf46 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,7 @@ findbugs-jsr305 = { module = "com.google.code.findbugs:jsr305", version = "3.0.2 javax-inject = { module = "javax.inject:javax.inject", version = "1" } kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlin-coroutines-tasks = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "coroutines" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.5.0" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } okhttp = { module = "com.squareup.okhttp3:okhttp", version = "3.12.13" } org-json = { module = "org.json:json", version = "20210307" } From 573821f34cc3d118bff7391d4992200ba488f442 Mon Sep 17 00:00:00 2001 From: Daymon Date: Tue, 25 Apr 2023 13:06:50 -0500 Subject: [PATCH 9/9] Reorder companion object placement --- .../firebase/gradle/plugins/ReleaseConfig.kt | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt index f8d6978aaed..e452b2398c6 100644 --- a/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt +++ b/buildSrc/src/main/java/com/google/firebase/gradle/plugins/ReleaseConfig.kt @@ -34,19 +34,6 @@ import kotlinx.serialization.json.Json */ @Serializable data class ReleaseConfig(val name: String, val libraries: List) { - companion object { - val formatter = Json { prettyPrint = true } - - /** - * Parses a [ReleaseConfig] from the contents of a given [File]. - * - * Allows one to reuse an already generated [ReleaseConfig] that was saved to disc. - * - * @param file the [File] to parse - * @see toFile - */ - fun fromFile(file: File): ReleaseConfig = formatter.decodeFromString(file.readText()) - } /** * Writes a [ReleaseConfig] into a [File] as JSON. @@ -65,4 +52,18 @@ data class ReleaseConfig(val name: String, val libraries: List) { * @see fromFile */ fun toFile(file: File) = file.also { it.writeText(formatter.encodeToString(this)) } + + companion object { + val formatter = Json { prettyPrint = true } + + /** + * Parses a [ReleaseConfig] from the contents of a given [File]. + * + * Allows one to reuse an already generated [ReleaseConfig] that was saved to disc. + * + * @param file the [File] to parse + * @see toFile + */ + fun fromFile(file: File): ReleaseConfig = formatter.decodeFromString(file.readText()) + } }