Skip to content

Commit

Permalink
Implement release plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
daymxn committed Jan 10, 2024
1 parent e70a134 commit 6a72406
Show file tree
Hide file tree
Showing 13 changed files with 613 additions and 44 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/check_for_api_changes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ jobs:
distribution: temurin
cache: gradle

- name: Update released.api
- name: Export the api file
run: |
./gradlew updateApi --no-daemon
./gradlew exportApi --no-daemon
- name: Save released.api from master
run: mv generativeai/released.api ~/released.api
- name: Save exported.api from master
run: mv generativeai/exported.api ~/exported.api

- name: Checkout branch
uses: actions/[email protected]
Expand All @@ -36,7 +36,7 @@ jobs:
cache: gradle

- name: Copy saved api to branch
run: mv ~/released.api generativeai/released.api
run: mv ~/exported.api generativeai/exported.api

- name: Run api warning task
run: |
Expand Down
1 change: 0 additions & 1 deletion generativeai/released.api → api/0.1.0.api
Original file line number Diff line number Diff line change
Expand Up @@ -287,4 +287,3 @@ public final class com/google/ai/client/generativeai/type/UnknownException : com
public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
}

292 changes: 292 additions & 0 deletions api/0.1.1.api

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions generativeai/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ plugins {
id("org.jetbrains.dokka")
id("com.ncorti.ktfmt.gradle")
id("changelog-plugin")
id("release-plugin")
kotlin("android")
kotlin("plugin.serialization")
}
Expand Down
35 changes: 27 additions & 8 deletions plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@ number of monotonous tasks. You can read more on these plugins and the tasks the

## ChangelogPlugin

Creates and manages changelog files. These files are used to signify changes made to the repo that
Creates and manages changelog files. These files are used to signify changes made to the repo that
should invoke a release, alongside text to display in the release notes at release time.

Change files are (by default) created under the `.changes` directory at the root of the repo.
Change files are (by default) created under the `.changes` directory at the root of the repo.
These files are json encoded variants of a [Changelog](./src/main/java/com/google/gradle/types/Changelog.kt) instance-
which is just an organization of what impact the change had (will it invoke a patch, minor, or
major bump?) and an (optional) end-user readable message to show alongside the other changes at
release time. By default, the files are saved as a random sequence of four words (to avoid
collisions).

During a release cycle, the `.changes` directory will be filled with change files. When it comes to
release time, these changes will be combined into a single `release_notes.md` file that contains
all the changes made- in a consumable format. After a release, the `.changes` directory will be
During a release cycle, the `.changes` directory will be filled with change files. When it comes to
release time, these changes will be combined into a single `release_notes.md` file that contains
all the changes made- in a consumable format. After a release, the `.changes` directory will be
wiped, and the cycle will continue.

To assist in this endeavour, the ChangelogPlugin registers an internal plugin and a few tasks:

### APIPlugin

An internal plugin (automatically added, and cannot be explicitly applied) that facilitates the
An internal plugin (automatically added, and cannot be explicitly applied) that facilitates the
generation of `.api` files.

#### Tasks
Expand All @@ -33,8 +33,8 @@ The APIPlugin registers the two following tasks to facilitate this process:

- [buildApi](./src/main/java/com/google/gradle/plugins/ApiPlugin.kt) -> Creates a `.api` file
containing the public API of the project.
- [updateApi](./src/main/java/com/google/gradle/plugins/ApiPlugin.kt) -> Updates (or creates) the
`released.api` file at the project directory; keeping track of the currently released/public api.
- [exportApi](./src/main/java/com/google/gradle/plugins/ApiPlugin.kt) -> Exports the `.api` file
generated by the `buildApi` task to a file at the project directory named `exported.api`.

### Tasks

Expand Down Expand Up @@ -72,3 +72,22 @@ The LicensePlugin registers the two following tasks to facilitate this process:
a license header in present in a set of files.
- [ApplyLicenseTask](./src/main/java/com/google/gradle/tasks/ApplyLicenseTask.kt) -> Applies a
license header to a set of files.

## ReleasePlugin

Facilitates the procedures expected to be done during a release.
While the `publishAllPublicationsToMavenRepository` task is used to actually *generate* the release
artifact, this plugin registers tasks that should be ran *before* the release artifact is generated.
Effectively "preparing" the project to be released.

### Tasks

The ReleasePlugin registers the three following tasks:

- [updateVersion](./src/main/java/com/google/gradle/tasks/VersionBumpTask.kt) -> Updates the project
version declared in the `gradle.properties` file to reflect the version generated by the release
notes.
- [createNewApiFile](./src/main/java/com/google/gradle/plugins/ReleasePlugin.kt) -> Creates a new
`.api` file in the root `api` directory; aligning with the current state of the public api.
- [prepareRelease](./src/main/java/com/google/gradle/plugins/ReleasePlugin.kt) -> Does everything
needed to prepare a release; creates the release notes and runs the above tasks.
4 changes: 4 additions & 0 deletions plugins/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ gradlePlugin {
id = "changelog-plugin"
implementationClass = "com.google.gradle.plugins.ChangelogPlugin"
}
register("release-plugin") {
id = "release-plugin"
implementationClass = "com.google.gradle.plugins.ReleasePlugin"
}
}
}

Expand Down
33 changes: 15 additions & 18 deletions plugins/src/main/java/com/google/gradle/plugins/ApiPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,54 +16,49 @@

package com.google.gradle.plugins

import com.google.gradle.tasks.CopyFileTask
import com.google.gradle.util.android
import com.google.gradle.util.outputFile
import com.google.gradle.util.release
import com.google.gradle.util.tempFile
import java.io.File
import kotlinx.validation.KotlinApiBuildTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.Optional
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.register

typealias BuildApiTask = KotlinApiBuildTask

/**
* A Gradle plugin for creating `.api` files; representing the public API of the project.
*
* By default, a `released.api` file (at the root of the project) will be used as a base for the
* released api.
*
* Registers two tasks:
* - `buildApi` -> creates a `.api` file containing the *current* public API of the project.
* - `updateApi` -> updates the `released.api` file at the project root to match the one generated by
* `buildApi`; effectively saying that the released api is up to date with the current repo state.
* - `exportApi` -> exports the file generated by `buildApi` to a `exported.api` file at the project
* directory
*
* @see ApiPluginExtension
* @see ChangelogPluginExtension
*/
abstract class ApiPlugin : Plugin<Project> {
override fun apply(project: Project) {
with(project) {
val extension = extensions.create<ApiPluginExtension>("api").apply { commonConfiguration() }
extensions.create<ApiPluginExtension>("api").apply { commonConfiguration() }

val buildApi = registerBuildApiTask()

tasks.register<Copy>("updateApi") {
val fileName = extension.apiFile.map { it.name }
val filePath = extension.apiFile.map { it.parent }

from(buildApi)
into(filePath)

rename { fileName.get() }
tasks.register<CopyFileTask>("exportApi") {
source.set(buildApi.outputFile)
dest.set(project.file("exported.api"))
}
}
}

private fun Project.registerBuildApiTask() =
tasks.register<KotlinApiBuildTask>("buildApi") {
tasks.register<BuildApiTask>("buildApi") {
val classes = provider { android.release.output.classesDirs }

inputClassesDirs = files(classes)
Expand All @@ -73,14 +68,16 @@ abstract class ApiPlugin : Plugin<Project> {

context(Project)
private fun ApiPluginExtension.commonConfiguration() {
apiFile.convention(file("released.api"))
val latestApiFile = project.file("api/${project.version}.api")

apiFile.convention(latestApiFile)
}
}

/**
* Extension properties for the [ApiPlugin].
*
* @property apiFile The file to reference as (and save to) in regards to the publicly released api.
* @property apiFile The file to reference to for the publicly released api.
*/
abstract class ApiPluginExtension {
@get:Optional abstract val apiFile: Property<File>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ import com.google.gradle.tasks.MakeChangeTask
import com.google.gradle.tasks.MakeReleaseNotesTask
import com.google.gradle.tasks.WarnAboutApiChangesTask
import com.google.gradle.types.Changelog
import com.google.gradle.types.ModuleVersion
import com.google.gradle.types.RandomWordsGenerator
import com.google.gradle.util.apply
import com.google.gradle.util.buildDir
import com.google.gradle.util.childFile
import com.google.gradle.util.moduleVersion
import com.google.gradle.util.orElseIfNotExists
import com.google.gradle.util.outputFile
import com.google.gradle.util.provideProperty
import com.google.gradle.util.tempFile
Expand Down Expand Up @@ -69,7 +70,9 @@ abstract class ChangelogPlugin : Plugin<Project> {
with(project) {
val extension =
extensions.create<ChangelogPluginExtension>("changelog").apply { commonConfiguration() }
val releasedApiFile = apiPlugin.apiFile

val exportedApiFile = provider { file("exported.api") }
val releasedApiFile = exportedApiFile.orElseIfNotExists(apiPlugin.apiFile)
val newApiFile = tasks.named("buildApi").outputFile

val findChanges =
Expand Down Expand Up @@ -108,8 +111,9 @@ abstract class ChangelogPlugin : Plugin<Project> {

tasks.register<MakeReleaseNotesTask>("makeReleaseNotes") {
onlyIf("No changelog files found") { changelogFiles.get().isNotEmpty() }

changeFiles.set(changelogFiles)
version.set(ModuleVersion.fromStringOrNull(project.version.toString()))
version.set(project.moduleVersion)
outputFile.set(rootProject.buildDir("release_notes.md"))

finalizedBy(deleteChangeFiles)
Expand Down
87 changes: 87 additions & 0 deletions plugins/src/main/java/com/google/gradle/plugins/ReleasePlugin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2024 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.gradle.plugins

import com.google.gradle.tasks.CopyFileTask
import com.google.gradle.tasks.MakeReleaseNotesTask
import com.google.gradle.tasks.VersionBumpTask
import com.google.gradle.types.ModuleVersion
import com.google.gradle.util.moduleVersion
import com.google.gradle.util.outputFile
import com.google.gradle.util.readFirstLine
import java.io.File
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.register

/**
* A Gradle plugin for preparing the release of a [Project].
*
* Intended to be ran before running `publishAllPublicationsToMavenRepository`.
*
* Registers three tasks:
* - `updateVersion` -> updates the project version declared in `gradle.properties` file, according
* to the release notes.
* - `createNewApiFile` -> creates a new `.api` file in the `api` directory for the release,
* aligning with the current state of the public api; for future auditing.
* - `prepareRelease` -> does everything needed to prepare a release; creates the release notes and
* runs the above tasks.
*
* If any of these tasks are ran without changelog files present, the current version declared in
* the `gradle.properties` file will be used instead.
*
* @see ApiPluginExtension
* @see ChangelogPluginExtension
*/
abstract class ReleasePlugin : Plugin<Project> {
override fun apply(project: Project) {
with(project) {
val buildApi = tasks.named<BuildApiTask>("buildApi")
val makeReleaseNotes = tasks.named<MakeReleaseNotesTask>("makeReleaseNotes")

val releaseNotes = makeReleaseNotes.outputFile
val releasingVersion =
releaseNotes.map { parseReleaseVersion(it) }.orElse(project.moduleVersion)

val updateVersion =
tasks.register<VersionBumpTask>("updateVersion") { newVersion.set(releasingVersion) }

val createNewApiFile =
tasks.register<CopyFileTask>("createNewApiFile") {
val newApiFile = releasingVersion.map { rootProject.file("api/$it.api") }

source.set(buildApi.outputFile)
dest.set(newApiFile)
}

tasks.register("prepareRelease") {
group = "publishing"

dependsOn(makeReleaseNotes, updateVersion, createNewApiFile)
}
}
}

private fun parseReleaseVersion(releaseNotes: File): ModuleVersion {
val version = releaseNotes.readFirstLine().substringAfter("#").trim()

return ModuleVersion.fromStringOrNull(version)
?: throw RuntimeException("Invalid release notes version found")
}
}
50 changes: 50 additions & 0 deletions plugins/src/main/java/com/google/gradle/tasks/CopyFileTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2024 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.gradle.tasks

import java.io.File
import org.gradle.api.DefaultTask
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction

/**
* Copies a file (or directory) from one place to another.
*
* An alternative to the standard [Copy] task provided by gradle; that allows better interop with
* providers, caching, directories, and individual files.
*
* If the file is a directory, all of its contents will be copied alongside it.
*
* ***If there is already a file or directory present at the destination, its contents will be
* overwritten.***
*
* @property source the file or directory to copy from
* @property dest where to copy the file or directory to
*/
abstract class CopyFileTask : DefaultTask() {
@get:InputFile abstract val source: Property<File>

@get:OutputFile abstract val dest: Property<File>

@TaskAction
fun create() {
source.get().copyRecursively(dest.get(), overwrite = true)
}
}
Loading

0 comments on commit 6a72406

Please sign in to comment.