Add draft GitHub release, X.Y.Z.N versions and Plugin Portal publishing · openjavaformat/open-java-format@30575fc · GitHub
Skip to content

Commit 30575fc

Browse files
committed
Add draft GitHub release, X.Y.Z.N versions and Plugin Portal publishing
- release.yml opens a draft GitHub release on the tag with a runnable open-java-format-<version>-all.jar, the IntelliJ plugin zip, the Eclipse plugin jar and every native binary, each signed, plus a checksum file - Releases accept X.Y.Z.N; the IntelliJ plugin compares such versions itself instead of through sls-versions, which is gone - The Gradle plugins publish to the Gradle Plugin Portal by hand with `mise run release:gradle-plugin`, refusing anything but a release version
1 parent bd71bd7 commit 30575fc

12 files changed

Lines changed: 338 additions & 30 deletions

File tree

‎.github/workflows/release.yml‎

Lines changed: 50 additions & 2 deletions

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ replaced with something a stranger can run.
119119
| Concern | Today | Replacement |
120120
|---|---|---|
121121
| CI | ~~CircleCI (`.circleci/config.yml`, generated by Palantir's Excavator)~~ | **Done** — [`.github/workflows/ci.yml`](.github/workflows/ci.yml), one explicit job per platform |
122-
| Release | ~~Palantir Autorelease + `com.palantir.gradle.externalpublish`~~ | **In progress** — JReleaser → Maven Central Portal, configured as [`buildSrc`](buildSrc/src/main/groovy) conventions and triggered by a tag ([`release.yml`](.github/workflows/release.yml)): one deployment for the jars, one for the native images of every platform. Uploads only for now — each deployment waits in the Portal until someone publishes it. Still to do: the Gradle plugin and the IDE plugins, and signing keys in a protected GitHub environment |
122+
| Release | ~~Palantir Autorelease + `com.palantir.gradle.externalpublish`~~ | **In progress** — JReleaser → Maven Central Portal, configured as [`buildSrc`](buildSrc/src/main/groovy) conventions and triggered by a tag ([`release.yml`](.github/workflows/release.yml)): one deployment for the jars, one for the native images of every platform. Uploads only for now — each deployment waits in the Portal until someone publishes it. The same tag opens a draft GitHub release with a runnable formatter jar (`java -jar`, no flags), the IntelliJ plugin zip, the Eclipse plugin jar and the native binaries, each file signed. The Gradle plugins go to the Gradle Plugin Portal by hand, with `mise run release:gradle-plugin`, once those jars are published. Still to do: the JetBrains Marketplace, and signing keys in a protected GitHub environment |
123123
| Dependency updates | Excavator (`.excavator.yml`) | Renovate |
124124
| Auto-merge | Bulldozer (`.bulldozer.yml`) | GitHub merge queue + auto-merge |
125125
| Changelog | Palantir changelog-app (`.changelog.yml`) | Release Drafter, changelog entry in the PR template |

‎build.gradle‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ buildscript {
77

88
dependencies {
99
classpath 'com.palantir.jakartapackagealignment:jakarta-package-alignment:0.7.0'
10-
classpath 'com.gradle.publish:plugin-publish-plugin:2.1.1'
10+
classpath 'com.gradle.publish:plugin-publish-plugin:2.2.1'
1111
classpath 'com.palantir.baseline-error-prone:gradle-baseline-error-prone:0.7.0'
1212
classpath 'com.palantir.baseline:gradle-baseline-java:7.9.0'
1313
classpath 'com.palantir.gradle.failure-reports:gradle-failure-reports:1.21.0'

‎buildSrc/src/main/groovy/open-java-format.release-conventions.gradle‎

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ plugins {
1010
// jobs, long after the jars are staged. `-PreleaseTarget=native` deploys the binaries those jobs
1111
// collected, the default deploys the jars; on a tag both read the same version from it.
1212
//
13-
// Not released yet: gradle-open-java-format, whose plugin markers and Gradle Plugin Portal listing are
14-
// a decision of their own. The Eclipse and IntelliJ plugins have no Maven publications at all.
13+
// `-PreleaseTarget=github` deploys nothing. It is for `jreleaserRelease`, which opens a draft GitHub
14+
// release on the tag with what Maven Central does not carry — the runnable formatter jar, the IntelliJ
15+
// plugin zip, the Eclipse plugin jar and every platform's native binary as a plain download — each file
16+
// signed, plus a checksum file.
17+
//
18+
// gradle-open-java-format is not released here: its plugins go to the Gradle Plugin Portal by hand, with
19+
// `mise run release:gradle-plugin`, once the jars they depend on are published.
1520
def releaseTarget = providers.gradleProperty('releaseTarget').getOrElse('jars')
1621
def releasedProjects
1722
if (releaseTarget == 'jars') {
@@ -22,13 +27,32 @@ if (releaseTarget == 'jars') {
2227
]
2328
} else if (releaseTarget == 'native') {
2429
releasedProjects = [':open-java-format-native']
30+
} else if (releaseTarget == 'github') {
31+
releasedProjects = []
2532
} else {
26-
throw new GradleException("Unknown -PreleaseTarget=${releaseTarget}: use 'jars' or 'native'")
33+
throw new GradleException("Unknown -PreleaseTarget=${releaseTarget}: use 'jars', 'native' or 'github'")
2734
}
2835
def stagingRepositories = releasedProjects.collect { path ->
2936
relativePath(project(path).layout.buildDirectory.dir('staging-deploy'))
3037
}
3138

39+
// The GitHub release takes the binaries the per-platform jobs collected from -PnativeImages=<dir>, exactly
40+
// as the native deployment does.
41+
def nativeBinaries = []
42+
if (releaseTarget == 'github') {
43+
def collectedNativeImages = providers.gradleProperty('nativeImages').getOrNull()
44+
if (collectedNativeImages == null) {
45+
throw new GradleException('-PreleaseTarget=github needs -PnativeImages=<dir> holding the native binaries')
46+
}
47+
nativeBinaries = fileTree(file(collectedNativeImages)) {
48+
include 'open-java-format-*'
49+
exclude '**/*.txt'
50+
}.files.sort { it.name }
51+
if (nativeBinaries.isEmpty()) {
52+
throw new GradleException("No native images to release in ${collectedNativeImages}")
53+
}
54+
}
55+
3256
jreleaser {
3357
dependsOnAssemble = false
3458

@@ -50,7 +74,38 @@ jreleaser {
5074

5175
release {
5276
github {
53-
enabled = false
77+
enabled = releaseTarget == 'github'
78+
// The tag is pushed by hand and is the version; the release only attaches to it.
79+
skipTag = true
80+
tagName = '{{projectVersion}}'
81+
releaseName = '{{projectVersion}}'
82+
// Published by hand after a look, like the deployments waiting in the Portal.
83+
draft = true
84+
changelog {
85+
formatted = 'ALWAYS'
86+
format = '- {{commitShortHash}} {{commitTitle}}'
87+
}
88+
}
89+
}
90+
91+
// Name templates, not Gradle's version: the root project sets its version only after applying this
92+
// plugin. The jars and the zip are built by the tasks jreleaserRelease depends on below.
93+
files {
94+
nativeBinaries.each { binary ->
95+
artifact {
96+
path = binary.absolutePath
97+
}
98+
}
99+
if (releaseTarget == 'github') {
100+
artifact {
101+
path = 'open-java-format/build/libs/open-java-format-{{projectVersion}}-all.jar'
102+
}
103+
artifact {
104+
path = 'open-java-format-idea-plugin/build/distributions/open-java-format-idea-plugin-{{projectVersion}}.zip'
105+
}
106+
artifact {
107+
path = 'open-java-format-eclipse-plugin/build/libs/open-java-format-eclipse-plugin-{{projectVersion}}.jar'
108+
}
54109
}
55110
}
56111

@@ -71,7 +126,8 @@ jreleaser {
71126
// The deployer's name selects its credentials — the Central Portal user token, as
72127
// JRELEASER_DEPLOY_MAVEN_MAVENCENTRAL_SONATYPE_USERNAME and _PASSWORD.
73128
sonatype {
74-
active = 'ALWAYS'
129+
// Nothing is deployed when the target is the GitHub release.
130+
active = releaseTarget == 'github' ? 'NEVER' : 'ALWAYS'
75131
url = 'https://central.sonatype.com/api/v1/publisher'
76132
namespace = 'dev.openjavaformat'
77133
applyMavenCentralRules = true
@@ -86,18 +142,31 @@ jreleaser {
86142
}
87143
}
88144

89-
// A release cannot be taken back, and the version comes from `git describe`: anywhere but a clean
90-
// checkout of a release tag it reads like 2.97.0-41-gf6667a2d.dirty. Refuse to upload that.
91-
def releaseVersion = provider { version.toString() }
92-
93145
tasks.named('jreleaserDeploy') {
94146
releasedProjects.each { path ->
95147
dependsOn "${path}:publishAllPublicationsToStagingRepository"
96148
}
97-
doFirst {
98-
if (!dryrun.getOrElse(false) && !(releaseVersion.get() ==~ /\d+\.\d+\.\d+/)) {
99-
throw new GradleException("Refusing to release ${releaseVersion.get()}: check out a release tag on a "
100-
+ 'clean working tree, or pass --dryrun to rehearse without uploading.')
149+
}
150+
151+
tasks.named('jreleaserRelease') {
152+
if (releaseTarget == 'github') {
153+
dependsOn ':open-java-format:allJar', ':open-java-format-idea-plugin:buildPlugin',
154+
':open-java-format-eclipse-plugin:jar'
155+
}
156+
}
157+
158+
// A release cannot be taken back, and the version comes from `git describe`: anywhere but a clean
159+
// checkout of a release tag it reads like 2.97.0-41-gf6667a2d.dirty. Refuse to upload that. A release is
160+
// upstream's X.Y.Z, optionally with a build number of our own after it: 2.98.0.1.
161+
def releaseVersion = provider { version.toString() }
162+
163+
['jreleaserDeploy', 'jreleaserRelease'].each { taskName ->
164+
tasks.named(taskName) {
165+
doFirst {
166+
if (!dryrun.getOrElse(false) && !(releaseVersion.get() ==~ /\d+\.\d+\.\d+(\.\d+)?/)) {
167+
throw new GradleException("Refusing to release ${releaseVersion.get()}: check out a release tag on a "
168+
+ 'clean working tree, or pass --dryrun to rehearse without uploading.')
169+
}
101170
}
102171
}
103172
}

‎gradle-open-java-format/build.gradle‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
apply plugin: 'java-gradle-plugin'
1+
// For the Gradle Plugin Portal: publishPlugins, run by hand once the version's jars are published on Maven
2+
// Central — the plugin depends on them. It applies java-gradle-plugin and maven-publish itself.
3+
apply plugin: 'com.gradle.plugin-publish'
24
apply plugin: 'groovy'
35
apply plugin: 'open-java-format.publishing-conventions'
46

@@ -156,3 +158,15 @@ tasks.named("test").configure {
156158
exclude '**/FormatDiffTest.class'
157159
}
158160
}
161+
162+
// The Portal never takes a version back, so only a release version goes up — X.Y.Z or X.Y.Z.N, as for Maven
163+
// Central in open-java-format.release-conventions. --validate-only checks the metadata of any version.
164+
def portalVersion = provider { project.version.toString() }
165+
tasks.named('publishPlugins') {
166+
doFirst {
167+
if (!validateOnly.getOrElse(false) && !(portalVersion.get() ==~ /\d+\.\d+\.\d+(\.\d+)?/)) {
168+
throw new GradleException("Refusing to publish ${portalVersion.get()} to the Gradle Plugin Portal: check "
169+
+ 'out a release tag on a clean working tree, or pass --validate-only.')
170+
}
171+
}
172+
}

‎gradle/libs.versions.toml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,4 @@ junit-jupiter-migrationsupport = { module = "org.junit.jupiter:junit-jupiter-mig
4040
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version = "6.1.3" }
4141
junit-vintage-engine = { module = "org.junit.vintage:junit-vintage-engine", version = "6.1.3" }
4242
palantir-platform = { module = "com.palantir.gradle.utils:platform", version = "0.29.0" }
43-
slsVersions = { module = "com.palantir.sls.versions:sls-versions", version = "1.18.0" }
4443
spotless-gradlePlugin = { module = "com.diffplug.spotless:spotless-plugin-gradle", version = "8.10.2" }

‎mise.toml‎

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,44 @@ run = "act push --dryrun"
1414

1515
[tasks."ci:build"]
1616
description = "Run the linux jar+native build job locally in Docker (slow: downloads JDKs, runs all tests)"
17-
run = "act push --job build"
17+
run = "act push --job build --env JAVA_HOME=/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/21.0.12-101.0.LTS/arm64"
1818

1919
[tasks."ci:native"]
2020
description = "Run the linux-aarch64 native job locally in Docker"
2121
run = "act push --job native --matrix platform:linux-aarch64"
2222

23+
# ---- Releases ----
24+
# JReleaser stages, signs and uploads to the Maven Central Portal the modules listed in
25+
# buildSrc/src/main/groovy/open-java-format.release-conventions.gradle. The PGP key and the Portal
26+
# token come from JRELEASER_* variables or ~/.jreleaser/config.properties. Only a clean checkout of a
27+
# release tag is accepted. Run these through mise, not ./gradlew: JReleaser's POM check finds Java
28+
# through JAVA_HOME, which mise sets, and without it logs an error and carries on unchecked.
29+
#
30+
# These deploy the jars. The native images are a deployment of their own — one binary per platform,
31+
# built by four CI jobs and uploaded together by .github/workflows/release.yml on a tag, because a
32+
# published version can never gain files afterwards.
33+
34+
[tasks.release]
35+
description = "Stage, sign, and upload the release to the Maven Central Portal (publishing is a click there)"
36+
run = "./gradlew clean jreleaserDeploy"
37+
38+
[tasks."release:dry-run"]
39+
description = "Rehearse the release: stage, sign and check the artifacts, upload nothing"
40+
run = "./gradlew clean jreleaserDeploy --dryrun"
41+
42+
# The Gradle plugins go to the Gradle Plugin Portal instead, by hand and last: they depend on the version's
43+
# jars, so publish only once that deployment is live on Maven Central. The key and secret are
44+
# gradle.publish.key and gradle.publish.secret in ~/.gradle/gradle.properties. The Portal never takes a
45+
# version back; publishPlugins refuses anything but a release tag's version.
46+
47+
[tasks."release:gradle-plugin"]
48+
description = "Publish the Gradle plugins to the Gradle Plugin Portal (once the jars are live on Maven Central)"
49+
run = "./gradlew :gradle-open-java-format:publishPlugins"
50+
51+
[tasks."release:gradle-plugin:validate"]
52+
description = "Check the Gradle plugins' Portal metadata, publish nothing"
53+
run = "./gradlew :gradle-open-java-format:publishPlugins --validate-only"
54+
2355
# ---- GitHub Actions secrets ----
2456
# Read from 1Password at apply time; nothing secret is stored in the repo. Requires gh
2557
# (authenticated with admin on this repository) and the 1Password CLI (`op signin` first).

‎open-java-format-idea-plugin/build.gradle‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,6 @@ dependencies {
6565

6666
implementation project(':open-java-format-jdk-bootstrap')
6767
implementation libs.caffeine
68-
implementation(libs.slsVersions) {
69-
// Has class version conflicts with slf4j brought in by Intellij
70-
exclude group: 'org.slf4j', module: 'slf4j-api'
71-
}
7268
implementation libs.palantir.platform
7369

7470
formatter project(':open-java-format')
Lines changed: 71 additions & 0 deletions

0 commit comments

Comments
 (0)