Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ updates:
commit-message:
prefix: chore
include: scope
ignore:
- dependency-name: "gradle"
Comment thread
rafaeltonholo marked this conversation as resolved.
119 changes: 119 additions & 0 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: Publish - Release

on:
workflow_dispatch:
inputs:
component_path:
description: "Gradle path of the component to publish, e.g. :components:bom"
required: true
default: ":components:bom"

permissions:
contents: write

concurrency:
group: publish-release-${{ inputs.component_path }}
cancel-in-progress: false

jobs:
publish-release:
name: Publish release
runs-on: ubuntu-latest
timeout-minutes: 60

env:
COMPONENT_PATH: ${{ inputs.component_path }}
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_IN_MEMORY_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }}

steps:
- name: Validate branch
run: |
set -euo pipefail

if [[ "${GITHUB_REF_NAME}" != "main" ]]; then
echo "Releases must be published from main. Current ref: ${GITHUB_REF_NAME}"
exit 1
fi

- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
fetch-depth: 0

- name: Setup Gradle environment
uses: ./.github/actions/setup-gradle

- name: Configure Git author
run: |
git config user.name "TMC Release Bot"
git config user.email "tmc-release-bot@thunderbird.net"

- name: Create release tag
id: release-tag
run: |
set -euo pipefail

tag="$(./gradlew -q "${COMPONENT_PATH}:printReleaseTag")"

if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
tag_commit="$(git rev-list -n 1 "${tag}")"
head_commit="$(git rev-parse HEAD)"

if [[ "${tag_commit}" != "${head_commit}" ]]; then
echo "Release tag ${tag} already exists but does not point at HEAD."
exit 1
fi

echo "Release tag ${tag} already exists on HEAD."
echo "tag=${tag}" >> "${GITHUB_OUTPUT}"
exit 0
fi

./gradlew "${COMPONENT_PATH}:createReleaseTag"

if ! git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
echo "Expected release tag ${tag} to be created."
exit 1
fi

echo "tag=${tag}" >> "${GITHUB_OUTPUT}"

- name: Write release notes
run: ./gradlew "${COMPONENT_PATH}:writeReleaseNotes" -PreleaseNotesFile="${RUNNER_TEMP}/release-notes.md"

- name: Publish release
run: ./gradlew "${COMPONENT_PATH}:validateStableVersionForPublishing" "${COMPONENT_PATH}:publishAndReleaseToMavenCentral"

- name: Push release tag
run: |
set -euo pipefail

tag="${{ steps.release-tag.outputs.tag }}"
if git ls-remote --exit-code --tags origin "refs/tags/${tag}" >/dev/null 2>&1; then
echo "Remote release tag ${tag} already exists."
exit 0
fi

git push origin "refs/tags/${tag}"

- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail

tag="${{ steps.release-tag.outputs.tag }}"
if gh release view "${tag}" >/dev/null 2>&1; then
echo "GitHub release ${tag} already exists."
exit 0
fi

gh release create "${tag}" \
--title "${tag}" \
--target "${GITHUB_SHA}" \
--verify-tag \
--notes-file "${RUNNER_TEMP}/release-notes.md"
72 changes: 72 additions & 0 deletions .github/workflows/publish-snapshot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: Publish - Snapshot

on:
workflow_dispatch:

permissions:
contents: write

concurrency:
group: publish-snapshot
cancel-in-progress: false

jobs:
publish-snapshot:
name: Publish snapshot
runs-on: ubuntu-latest
timeout-minutes: 60

env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.SIGNING_IN_MEMORY_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyId: ${{ secrets.SIGNING_IN_MEMORY_KEY_ID }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_IN_MEMORY_KEY_PASSWORD }}

steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: main
fetch-depth: 0

- name: Check snapshot marker
id: snapshot-marker
run: |
set -euo pipefail

marker="snapshot/latest"
head_commit="$(git rev-parse HEAD)"

if git rev-parse -q --verify "refs/tags/${marker}" >/dev/null; then
marker_commit="$(git rev-list -n 1 "${marker}")"

if [[ "${marker_commit}" == "${head_commit}" ]]; then
echo "Snapshot marker ${marker} already points at HEAD. Nothing to publish."
echo "publish=false" >> "${GITHUB_OUTPUT}"
exit 0
fi
fi

echo "publish=true" >> "${GITHUB_OUTPUT}"

- name: Setup Gradle environment
if: steps.snapshot-marker.outputs.publish == 'true'
uses: ./.github/actions/setup-gradle

- name: Publish snapshot
if: steps.snapshot-marker.outputs.publish == 'true'
run: |
set -euo pipefail

./gradlew validateSnapshotVersionForPublishing
./gradlew publishToMavenCentral

- name: Update snapshot marker
if: steps.snapshot-marker.outputs.publish == 'true'
run: |
set -euo pipefail

marker="snapshot/latest"
git tag --force "${marker}" HEAD
git push --force origin "refs/tags/${marker}"
14 changes: 12 additions & 2 deletions build-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,13 @@ Supportive/quality and tooling plugins:
[Release Guide](../docs/release-guide.md).
- `finalizeChangelog`: finalizes the current `## Unreleased` section for a release version and inserts a new
empty `## Unreleased` section above it.
- Required property: `-PreleaseVersion=<version>`
- Version source: nearest `version.properties`
- Optional property: `-PreleaseVersion=<version>` (must match `version.properties` when provided)
- Optional property: `-PreleaseDate=YYYY-MM-DD` (defaults to the current local date)
- Validation: fails when `Unreleased` is missing, empty, or when the target release already exists.
- `writeReleaseNotes`: writes the finalized release section to a markdown file for GitHub Releases.
- Version source: nearest `version.properties`
- Optional property: `-PreleaseNotesFile=<path>` (defaults to `build/release/release-notes.md`)
- Usage:
```kotlin
// In any project that should contribute a component changelog
Expand All @@ -81,7 +85,7 @@ Supportive/quality and tooling plugins:
./gradlew :components:bom:updateChangelog

# Finalize the changelog for a release
./gradlew :components:bom:finalizeChangelog -PreleaseVersion=1.0.0
./gradlew :components:bom:finalizeChangelog
```

### Applying a plugin
Expand Down Expand Up @@ -132,6 +136,12 @@ The `net.thunderbird.gradle.plugin.publishing` plugin:
- Sets Maven coordinates from the project and configures POM metadata
- Adds local repositories: `mavenLocal()` and `${rootProject}/build/maven-repo`
- Configures publishing to Maven Central and signs all publications
- Adds release-oriented validation tasks:
- `validateStableVersionForPublishing`: validates a non-`SNAPSHOT` version before release publishing.
- `validateSnapshotVersionForPublishing`: validates a `SNAPSHOT` version before snapshot publishing.
- Adds release tag tasks:
- `printReleaseTag`: prints the component release tag derived from `version.properties`.
- `createReleaseTag`: creates the component release tag locally.

Signing properties can be supplied from a file at `${rootProject}/.signing/signing.properties` with keys:

Expand Down
5 changes: 5 additions & 0 deletions build-plugin/plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
compileOnly(plugin(libs.plugins.compose.multiplatform))

implementation(plugin(libs.plugins.dependency.check))
implementation(plugin(libs.plugins.dokka))

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

Expand Down Expand Up @@ -43,6 +44,10 @@ gradlePlugin {
id = "net.thunderbird.gradle.plugin.library.kmp.compose"
implementationClass = "net.thunderbird.gradle.plugin.library.kmp.compose.LibraryKmpComposePlugin"
}
register("Bom") {
id = "net.thunderbird.gradle.plugin.bom"
implementationClass = "net.thunderbird.gradle.plugin.bom.BomPlugin"
}

register("DependencyCheck") {
id = "net.thunderbird.gradle.plugin.dependency.check"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ object ProjectConfig {
const val group = "net.thunderbird"

object Android {
const val sdkMin = 21
const val sdkMin = 23

// Only needed for application
const val sdkTarget = 35
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ class AppKmpComposePlugin : Plugin<Project> {
warningsAsErrors = false
abortOnError = true
checkDependencies = true
lintConfig = project.file("${project.rootProject.projectDir}/config/lint/lint.xml")
@Suppress("UnstableApiUsage")
lintConfig = project.isolated.rootProject.projectDirectory.file("config/lint/lint.xml").asFile
}

packaging {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package net.thunderbird.gradle.plugin.bom

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPlatformExtension
import org.gradle.kotlin.dsl.configure

class BomPlugin : Plugin<Project> {

override fun apply(target: Project) {
with(target) {
pluginManager.apply("java-platform")
pluginManager.apply("net.thunderbird.gradle.plugin.changelog")
pluginManager.apply("net.thunderbird.gradle.plugin.versioning")
pluginManager.apply("net.thunderbird.gradle.plugin.publishing")

extensions.configure<JavaPlatformExtension> {
allowDependencies()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ class ChangelogPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
val start = project.projectDir
val root = rootProject.projectDir

@Suppress("UnstableApiUsage")
val root = isolated.rootProject.projectDirectory.asFile
val versionDir = FileHelper.locateNearestVersionDir(start, root)

if (versionDir == null) {
Expand All @@ -45,7 +47,8 @@ class ChangelogPlugin : Plugin<Project> {
),
)

repoRootDir.set(rootProject.layout.projectDirectory)
@Suppress("UnstableApiUsage")
repoRootDir.set(isolated.rootProject.projectDirectory)
repoUrl.set(ProjectConfig.Publishing.url)
}

Expand All @@ -58,8 +61,34 @@ class ChangelogPlugin : Plugin<Project> {
project.provider { File(versionDir, FileHelper.CHANGELOG_FILE) },
),
)
releaseVersion.set(providers.gradleProperty("releaseVersion"))
releaseDate.set(providers.gradleProperty("releaseDate"))
versionFile.set(
project.layout.file(
project.provider { File(versionDir, FileHelper.VERSION_FILE) },
),
)
releaseVersion.convention(providers.gradleProperty("releaseVersion"))
releaseDate.convention(providers.gradleProperty("releaseDate"))
}

tasks.register<WriteReleaseNotesTask>(WriteReleaseNotesTask.TASK_NAME) {
group = "documentation"
description = "Write GitHub release notes from the finalized component-local CHANGELOG.md section"

changelogFile.set(
project.layout.file(
project.provider { File(versionDir, FileHelper.CHANGELOG_FILE) },
),
)
versionFile.set(
project.layout.file(
project.provider { File(versionDir, FileHelper.VERSION_FILE) },
),
)
outputFile.convention(layout.buildDirectory.file("release/release-notes.md"))
providers.gradleProperty("releaseNotesFile").orNull?.let { releaseNotesFile ->
outputFile.set(layout.file(provider { File(releaseNotesFile) }))
}
releaseVersion.convention(providers.gradleProperty("releaseVersion"))
}
}
}
Expand Down
Loading