Skip to content

Commit 5b7d3f0

Browse files
authored
chore(build): prepare for releases (#78)
2 parents 8b7c0fe + b6396a3 commit 5b7d3f0

36 files changed

Lines changed: 1773 additions & 259 deletions

.github/dependabot.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ updates:
2424
commit-message:
2525
prefix: chore
2626
include: scope
27+
ignore:
28+
- dependency-name: "gradle"
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
name: Publish - Release
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
component_path:
7+
description: "Gradle path of the component to publish, e.g. :components:bom"
8+
required: true
9+
default: ":components:bom"
10+
11+
permissions:
12+
contents: write
13+
14+
concurrency:
15+
group: publish-release-${{ inputs.component_path }}
16+
cancel-in-progress: false
17+
18+
jobs:
19+
publish-release:
20+
name: Publish release
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 60
23+
24+
env:
25+
COMPONENT_PATH: ${{ inputs.component_path }}
26+
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
27+
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
28+
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }}
29+
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_IN_MEMORY_KEY_ID }}
30+
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }}
31+
32+
steps:
33+
- name: Validate branch
34+
run: |
35+
set -euo pipefail
36+
37+
if [[ "${GITHUB_REF_NAME}" != "main" ]]; then
38+
echo "Releases must be published from main. Current ref: ${GITHUB_REF_NAME}"
39+
exit 1
40+
fi
41+
42+
- name: Checkout
43+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
44+
with:
45+
fetch-depth: 0
46+
47+
- name: Setup Gradle environment
48+
uses: ./.github/actions/setup-gradle
49+
50+
- name: Configure Git author
51+
run: |
52+
git config user.name "TMC Release Bot"
53+
git config user.email "tmc-release-bot@thunderbird.net"
54+
55+
- name: Create release tag
56+
id: release-tag
57+
run: |
58+
set -euo pipefail
59+
60+
tag="$(./gradlew -q "${COMPONENT_PATH}:printReleaseTag")"
61+
62+
if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
63+
tag_commit="$(git rev-list -n 1 "${tag}")"
64+
head_commit="$(git rev-parse HEAD)"
65+
66+
if [[ "${tag_commit}" != "${head_commit}" ]]; then
67+
echo "Release tag ${tag} already exists but does not point at HEAD."
68+
exit 1
69+
fi
70+
71+
echo "Release tag ${tag} already exists on HEAD."
72+
echo "tag=${tag}" >> "${GITHUB_OUTPUT}"
73+
exit 0
74+
fi
75+
76+
./gradlew "${COMPONENT_PATH}:createReleaseTag"
77+
78+
if ! git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
79+
echo "Expected release tag ${tag} to be created."
80+
exit 1
81+
fi
82+
83+
echo "tag=${tag}" >> "${GITHUB_OUTPUT}"
84+
85+
- name: Write release notes
86+
run: ./gradlew "${COMPONENT_PATH}:writeReleaseNotes" -PreleaseNotesFile="${RUNNER_TEMP}/release-notes.md"
87+
88+
- name: Publish release
89+
run: ./gradlew "${COMPONENT_PATH}:validateStableVersionForPublishing" "${COMPONENT_PATH}:publishAndReleaseToMavenCentral"
90+
91+
- name: Push release tag
92+
run: |
93+
set -euo pipefail
94+
95+
tag="${{ steps.release-tag.outputs.tag }}"
96+
if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
97+
echo "Remote release tag ${tag} already exists."
98+
exit 0
99+
fi
100+
101+
git push origin "refs/tags/${tag}"
102+
103+
- name: Create GitHub release
104+
env:
105+
GH_TOKEN: ${{ github.token }}
106+
run: |
107+
set -euo pipefail
108+
109+
tag="${{ steps.release-tag.outputs.tag }}"
110+
if gh release view "${tag}" >/dev/null 2>&1; then
111+
echo "GitHub release ${tag} already exists."
112+
exit 0
113+
fi
114+
115+
gh release create "${tag}" \
116+
--title "${tag}" \
117+
--target "${GITHUB_SHA}" \
118+
--verify-tag \
119+
--notes-file "${RUNNER_TEMP}/release-notes.md"
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
name: Publish - Snapshot
2+
3+
on:
4+
workflow_dispatch:
5+
6+
permissions:
7+
contents: write
8+
9+
concurrency:
10+
group: publish-snapshot
11+
cancel-in-progress: false
12+
13+
jobs:
14+
publish-snapshot:
15+
name: Publish snapshot
16+
runs-on: ubuntu-latest
17+
timeout-minutes: 60
18+
19+
env:
20+
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
21+
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
22+
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }}
23+
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_IN_MEMORY_KEY_ID }}
24+
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }}
25+
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
29+
with:
30+
ref: main
31+
fetch-depth: 0
32+
33+
- name: Check snapshot marker
34+
id: snapshot-marker
35+
run: |
36+
set -euo pipefail
37+
38+
marker="snapshot/latest"
39+
head_commit="$(git rev-parse HEAD)"
40+
41+
if git rev-parse -q --verify "refs/tags/${marker}" >/dev/null; then
42+
marker_commit="$(git rev-list -n 1 "${marker}")"
43+
44+
if [[ "${marker_commit}" == "${head_commit}" ]]; then
45+
echo "Snapshot marker ${marker} already points at HEAD. Nothing to publish."
46+
echo "publish=false" >> "${GITHUB_OUTPUT}"
47+
exit 0
48+
fi
49+
fi
50+
51+
echo "publish=true" >> "${GITHUB_OUTPUT}"
52+
53+
- name: Setup Gradle environment
54+
if: steps.snapshot-marker.outputs.publish == 'true'
55+
uses: ./.github/actions/setup-gradle
56+
57+
- name: Publish snapshot
58+
if: steps.snapshot-marker.outputs.publish == 'true'
59+
run: |
60+
set -euo pipefail
61+
62+
./gradlew validateSnapshotVersionForPublishing
63+
./gradlew publishToMavenCentral
64+
65+
- name: Update snapshot marker
66+
if: steps.snapshot-marker.outputs.publish == 'true'
67+
run: |
68+
set -euo pipefail
69+
70+
marker="snapshot/latest"
71+
git tag --force "${marker}" HEAD
72+
git push --force origin "refs/tags/${marker}"

build-plugin/README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,13 @@ Supportive/quality and tooling plugins:
6464
[Release Guide](../docs/release-guide.md).
6565
- `finalizeChangelog`: finalizes the current `## Unreleased` section for a release version and inserts a new
6666
empty `## Unreleased` section above it.
67-
- Required property: `-PreleaseVersion=<version>`
67+
- Version source: nearest `version.properties`
68+
- Optional property: `-PreleaseVersion=<version>` (must match `version.properties` when provided)
6869
- Optional property: `-PreleaseDate=YYYY-MM-DD` (defaults to the current local date)
6970
- Validation: fails when `Unreleased` is missing, empty, or when the target release already exists.
71+
- `writeReleaseNotes`: writes the finalized release section to a markdown file for GitHub Releases.
72+
- Version source: nearest `version.properties`
73+
- Optional property: `-PreleaseNotesFile=<path>` (defaults to `build/release/release-notes.md`)
7074
- Usage:
7175
```kotlin
7276
// In any project that should contribute a component changelog
@@ -81,7 +85,7 @@ Supportive/quality and tooling plugins:
8185
./gradlew :components:bom:updateChangelog
8286
8387
# Finalize the changelog for a release
84-
./gradlew :components:bom:finalizeChangelog -PreleaseVersion=1.0.0
88+
./gradlew :components:bom:finalizeChangelog
8589
```
8690

8791
### Applying a plugin
@@ -132,6 +136,12 @@ The `net.thunderbird.gradle.plugin.publishing` plugin:
132136
- Sets Maven coordinates from the project and configures POM metadata
133137
- Adds local repositories: `mavenLocal()` and `${rootProject}/build/maven-repo`
134138
- Configures publishing to Maven Central and signs all publications
139+
- Adds release-oriented validation tasks:
140+
- `validateStableVersionForPublishing`: validates a non-`SNAPSHOT` version before release publishing.
141+
- `validateSnapshotVersionForPublishing`: validates a `SNAPSHOT` version before snapshot publishing.
142+
- Adds release tag tasks:
143+
- `printReleaseTag`: prints the component release tag derived from `version.properties`.
144+
- `createReleaseTag`: creates the component release tag locally.
135145
136146
Signing properties can be supplied from a file at `${rootProject}/.signing/signing.properties` with keys:
137147

build-plugin/plugin/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies {
1616
compileOnly(plugin(libs.plugins.compose.multiplatform))
1717

1818
implementation(plugin(libs.plugins.dependency.check))
19+
implementation(plugin(libs.plugins.dokka))
1920

2021
implementation(plugin(libs.plugins.maven.publish))
2122

@@ -43,6 +44,10 @@ gradlePlugin {
4344
id = "net.thunderbird.gradle.plugin.library.kmp.compose"
4445
implementationClass = "net.thunderbird.gradle.plugin.library.kmp.compose.LibraryKmpComposePlugin"
4546
}
47+
register("Bom") {
48+
id = "net.thunderbird.gradle.plugin.bom"
49+
implementationClass = "net.thunderbird.gradle.plugin.bom.BomPlugin"
50+
}
4651

4752
register("DependencyCheck") {
4853
id = "net.thunderbird.gradle.plugin.dependency.check"

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/ProjectConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ object ProjectConfig {
88
const val group = "net.thunderbird"
99

1010
object Android {
11-
const val sdkMin = 21
11+
const val sdkMin = 23
1212

1313
// Only needed for application
1414
const val sdkTarget = 35

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/kmp/compose/AppKmpComposePlugin.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ class AppKmpComposePlugin : Plugin<Project> {
126126
warningsAsErrors = false
127127
abortOnError = true
128128
checkDependencies = true
129-
lintConfig = project.file("${project.rootProject.projectDir}/config/lint/lint.xml")
129+
@Suppress("UnstableApiUsage")
130+
lintConfig = project.isolated.rootProject.projectDirectory.file("config/lint/lint.xml").asFile
130131
}
131132

132133
packaging {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package net.thunderbird.gradle.plugin.bom
2+
3+
import org.gradle.api.Plugin
4+
import org.gradle.api.Project
5+
import org.gradle.api.plugins.JavaPlatformExtension
6+
import org.gradle.kotlin.dsl.configure
7+
8+
class BomPlugin : Plugin<Project> {
9+
10+
override fun apply(target: Project) {
11+
with(target) {
12+
pluginManager.apply("java-platform")
13+
pluginManager.apply("net.thunderbird.gradle.plugin.changelog")
14+
pluginManager.apply("net.thunderbird.gradle.plugin.versioning")
15+
pluginManager.apply("net.thunderbird.gradle.plugin.publishing")
16+
17+
extensions.configure<JavaPlatformExtension> {
18+
allowDependencies()
19+
}
20+
}
21+
}
22+
}

build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/changelog/ChangelogPlugin.kt

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ class ChangelogPlugin : Plugin<Project> {
1818
override fun apply(target: Project) {
1919
with(target) {
2020
val start = project.projectDir
21-
val root = rootProject.projectDir
21+
22+
@Suppress("UnstableApiUsage")
23+
val root = isolated.rootProject.projectDirectory.asFile
2224
val versionDir = FileHelper.locateNearestVersionDir(start, root)
2325

2426
if (versionDir == null) {
@@ -45,7 +47,8 @@ class ChangelogPlugin : Plugin<Project> {
4547
),
4648
)
4749

48-
repoRootDir.set(rootProject.layout.projectDirectory)
50+
@Suppress("UnstableApiUsage")
51+
repoRootDir.set(isolated.rootProject.projectDirectory)
4952
repoUrl.set(ProjectConfig.Publishing.url)
5053
}
5154

@@ -58,8 +61,34 @@ class ChangelogPlugin : Plugin<Project> {
5861
project.provider { File(versionDir, FileHelper.CHANGELOG_FILE) },
5962
),
6063
)
61-
releaseVersion.set(providers.gradleProperty("releaseVersion"))
62-
releaseDate.set(providers.gradleProperty("releaseDate"))
64+
versionFile.set(
65+
project.layout.file(
66+
project.provider { File(versionDir, FileHelper.VERSION_FILE) },
67+
),
68+
)
69+
releaseVersion.convention(providers.gradleProperty("releaseVersion"))
70+
releaseDate.convention(providers.gradleProperty("releaseDate"))
71+
}
72+
73+
tasks.register<WriteReleaseNotesTask>(WriteReleaseNotesTask.TASK_NAME) {
74+
group = "documentation"
75+
description = "Write GitHub release notes from the finalized component-local CHANGELOG.md section"
76+
77+
changelogFile.set(
78+
project.layout.file(
79+
project.provider { File(versionDir, FileHelper.CHANGELOG_FILE) },
80+
),
81+
)
82+
versionFile.set(
83+
project.layout.file(
84+
project.provider { File(versionDir, FileHelper.VERSION_FILE) },
85+
),
86+
)
87+
outputFile.convention(layout.buildDirectory.file("release/release-notes.md"))
88+
providers.gradleProperty("releaseNotesFile").orNull?.let { releaseNotesFile ->
89+
outputFile.set(layout.file(provider { File(releaseNotesFile) }))
90+
}
91+
releaseVersion.convention(providers.gradleProperty("releaseVersion"))
6392
}
6493
}
6594
}

0 commit comments

Comments
 (0)