Skip to content

Commit 6ef8fb4

Browse files
Add release automation: Gradle release task, GitHub Actions workflow, release.md
- ./gradlew release: pre-flight checks, build, Maven Central, Chocolatey (Windows), GitHub Release - .github/workflows/release.yml: tag-triggered automated release (Option B) - release.md: full release documentation - Bump version to 1.10.3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3435874 commit 6ef8fb4

4 files changed

Lines changed: 467 additions & 1 deletion

File tree

.github/workflows/release.yml

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
13+
check:
14+
name: Check if release already exists
15+
runs-on: ubuntu-latest
16+
outputs:
17+
should_release: ${{ steps.check.outputs.should_release }}
18+
steps:
19+
- name: Check for existing release
20+
id: check
21+
run: |
22+
if gh release view "${{ github.ref_name }}" --repo "${{ github.repository }}" &>/dev/null; then
23+
echo "Release ${{ github.ref_name }} already exists — skipping (created by local ./gradlew release)"
24+
echo "should_release=false" >> "$GITHUB_OUTPUT"
25+
else
26+
echo "should_release=true" >> "$GITHUB_OUTPUT"
27+
fi
28+
env:
29+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30+
31+
publish:
32+
needs: check
33+
if: needs.check.outputs.should_release == 'true'
34+
name: Build & publish to Maven Central
35+
runs-on: ubuntu-latest
36+
steps:
37+
- name: Verify required secrets
38+
run: |
39+
missing=()
40+
[ -z "$SIGNING_KEY" ] && missing+=("SIGNING_KEY")
41+
[ -z "$SIGNING_PASSWORD" ] && missing+=("SIGNING_PASSWORD")
42+
[ -z "$OSSRH_USERNAME" ] && missing+=("OSSRH_USERNAME")
43+
[ -z "$OSSRH_PASSWORD" ] && missing+=("OSSRH_PASSWORD")
44+
if [ ${#missing[@]} -gt 0 ]; then
45+
echo "❌ Missing required secrets: ${missing[*]}"
46+
echo "See release.md → Prerequisites → GitHub secrets"
47+
exit 1
48+
fi
49+
echo "✅ All required secrets present"
50+
env:
51+
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
52+
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
53+
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
54+
OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }}
55+
56+
- uses: actions/checkout@v4
57+
58+
- name: Set up JDK 17
59+
uses: actions/setup-java@v4
60+
with:
61+
distribution: temurin
62+
java-version: '17'
63+
cache: gradle
64+
65+
- uses: gradle/actions/setup-gradle@v5
66+
67+
- name: Extract version from tag
68+
id: version
69+
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
70+
71+
- name: Build
72+
run: ./gradlew build
73+
74+
- name: Publish to Maven Central
75+
run: ./gradlew publishAllPublicationsToSonatypeRepository closeAndReleaseSonatypeStagingRepository
76+
env:
77+
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }}
78+
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }}
79+
ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }}
80+
ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }}
81+
82+
- name: Create GitHub Release
83+
run: |
84+
gh release create "${{ github.ref_name }}" \
85+
--title "Robocode ${{ steps.version.outputs.version }}" \
86+
--notes "See [versions.md](https://github.com/${{ github.repository }}/blob/main/versions.md) for release notes." \
87+
"build/robocode-${{ steps.version.outputs.version }}-setup.jar"
88+
env:
89+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
90+
91+
chocolatey:
92+
name: Publish to Chocolatey
93+
needs: [check, publish]
94+
if: needs.check.outputs.should_release == 'true'
95+
runs-on: windows-latest
96+
steps:
97+
- name: Verify CHOCOLATEY_API_KEY
98+
shell: pwsh
99+
run: |
100+
if ([string]::IsNullOrEmpty($env:CHOCOLATEY_API_KEY)) {
101+
Write-Error "❌ CHOCOLATEY_API_KEY secret is not set — see release.md → Prerequisites → GitHub secrets"
102+
exit 1
103+
}
104+
Write-Host "✅ CHOCOLATEY_API_KEY is present"
105+
env:
106+
CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }}
107+
108+
- uses: actions/checkout@v4
109+
110+
- name: Set up JDK 17
111+
uses: actions/setup-java@v4
112+
with:
113+
distribution: temurin
114+
java-version: '17'
115+
cache: gradle
116+
117+
- uses: gradle/actions/setup-gradle@v5
118+
119+
- name: Publish to Chocolatey
120+
run: ./gradlew :robocode.installer:chocoPush
121+
env:
122+
CHOCOLATEY_API_KEY: ${{ secrets.CHOCOLATEY_API_KEY }}

build.gradle.kts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,122 @@ tasks {
4343
named("clean") {
4444
delete(".sandbox")
4545
}
46+
47+
val validateReleaseCredentials by registering {
48+
group = "publishing"
49+
description = "Pre-flight credential checks required for release"
50+
doLast {
51+
val signingKey = project.findProperty("signingKey") as String?
52+
val signingPassword = project.findProperty("signingPassword") as String?
53+
val ossrhUser = project.findProperty("ossrhUsername") as String?
54+
val ossrhPass = project.findProperty("ossrhPassword") as String?
55+
val chocoKey = System.getenv("CHOCOLATEY_API_KEY")
56+
val onWindows = System.getProperty("os.name").lowercase().contains("windows")
57+
58+
val labels = mutableListOf<String>()
59+
val results = mutableListOf<Boolean>()
60+
fun check(label: String, value: String?, rejectDummy: Boolean = true) {
61+
labels.add(label)
62+
results.add(!value.isNullOrBlank() && (!rejectDummy || value != "dummy"))
63+
}
64+
65+
val ghOk = ProcessBuilder("gh", "auth", "status")
66+
.redirectErrorStream(true).start().waitFor() == 0
67+
68+
val expectedTag = "v${project.version}"
69+
val gitTagProc = ProcessBuilder("git", "tag", "--points-at", "HEAD")
70+
.redirectErrorStream(true).start()
71+
val tagOk = gitTagProc.inputStream.bufferedReader().readLines()
72+
.any { it.trim() == expectedTag }
73+
.also { gitTagProc.waitFor() }
74+
75+
check("signingKey", signingKey)
76+
check("signingPassword", signingPassword, rejectDummy = false)
77+
check("ossrhUsername", ossrhUser)
78+
check("ossrhPassword", ossrhPass)
79+
if (onWindows) check("CHOCOLATEY_API_KEY (env var)", chocoKey, rejectDummy = false)
80+
81+
println("\n🔑 Credential check:")
82+
labels.zip(results).forEach { (label, ok) -> println(" ${if (ok) "" else ""} $label") }
83+
if (!onWindows) println(" ⏭️ CHOCOLATEY_API_KEY (skipped — Chocolatey publish requires Windows)")
84+
println(" ${if (ghOk) "" else ""} GitHub CLI (gh auth status)")
85+
println(" ${if (tagOk) "" else ""} git tag $expectedTag on HEAD")
86+
println()
87+
88+
val failures = labels.zip(results).filter { !it.second }.map { it.first } +
89+
listOfNotNull(
90+
if (!ghOk) "GitHub CLI (gh auth status)" else null,
91+
if (!tagOk) "git tag $expectedTag not found on HEAD — run: git tag $expectedTag" else null
92+
)
93+
if (failures.isNotEmpty()) {
94+
throw GradleException(
95+
"Release pre-flight failed — missing or invalid credentials:\n" +
96+
failures.joinToString("\n") { "$it" } +
97+
"\n\nSee release.md → Prerequisites for setup instructions."
98+
)
99+
}
100+
println("✅ All credentials present. Starting release...\n")
101+
}
102+
}
103+
104+
val createGitHubRelease by registering(Exec::class) {
105+
group = "publishing"
106+
description = "Creates a GitHub release and attaches the setup JAR"
107+
val version = project.version.toString()
108+
commandLine(
109+
"gh", "release", "create", "v$version",
110+
"--title", "Robocode $version",
111+
"--notes", "See [versions.md](https://github.com/robo-code/robocode/blob/main/versions.md) for release notes.",
112+
"build/robocode-$version-setup.jar"
113+
)
114+
}
115+
116+
register("release") {
117+
group = "publishing"
118+
description = "Full release: pre-flight checks, build, Maven Central, Chocolatey (Windows), GitHub Release"
119+
dependsOn(validateReleaseCredentials)
120+
dependsOn("build")
121+
dependsOn(createGitHubRelease)
122+
// Per-subproject publish tasks, closeAndRelease, and ordering are wired in projectsEvaluated below
123+
}
124+
}
125+
126+
// Use projectsEvaluated (runs after ALL afterEvaluate callbacks, including the nexus plugin's)
127+
// so that per-subproject publishAllPublicationsToSonatypeRepository tasks are already registered.
128+
gradle.projectsEvaluated {
129+
val releaseTask = tasks.named("release")
130+
val validateCreds = tasks.named("validateReleaseCredentials")
131+
tasks.named("build") { mustRunAfter(validateCreds) }
132+
133+
val publishTasks = subprojects
134+
.mapNotNull { it.tasks.findByName("publishAllPublicationsToSonatypeRepository") }
135+
136+
publishTasks.forEach { publishTask ->
137+
releaseTask.configure { dependsOn(publishTask) }
138+
publishTask.mustRunAfter("build")
139+
}
140+
141+
tasks.findByName("closeAndReleaseSonatypeStagingRepository")?.let { closeTask ->
142+
releaseTask.configure { dependsOn(closeTask) }
143+
closeTask.mustRunAfter(*publishTasks.toTypedArray())
144+
}
145+
146+
val onWindows = System.getProperty("os.name").lowercase().contains("windows")
147+
val chocoKey = System.getenv("CHOCOLATEY_API_KEY")
148+
val closeTask = tasks.findByName("closeAndReleaseSonatypeStagingRepository")
149+
val ghRelease = tasks.named("createGitHubRelease")
150+
151+
var lastBeforeGhRelease: Any = closeTask ?: "build"
152+
153+
if (onWindows && !chocoKey.isNullOrBlank()) {
154+
project(":robocode.installer").tasks.findByName("chocoPush")?.let { chocoPush ->
155+
releaseTask.configure { dependsOn(chocoPush) }
156+
if (closeTask != null) chocoPush.mustRunAfter(closeTask)
157+
lastBeforeGhRelease = chocoPush
158+
}
159+
}
160+
161+
ghRelease.configure { mustRunAfter(lastBeforeGhRelease) }
46162
}
47163

48164
nexusPublishing {

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# The version of Robocode
2-
version=1.10.2
2+
version=1.10.3
33

44
# These are set to dummy values as they don't exist when run on GitHub actions
55
ossrhUsername=dummy

0 commit comments

Comments
 (0)