Skip to content

Commit c2290a6

Browse files
authored
Migrate to a different Maven publishing plugin (#681)
Nessie and CEL-java are using the [Gradle Nexus publish plugin](https://github.com/gradle-nexus/publish-plugin), which works great for publishing to Nexus and Sonatype OSSRH. Sonatype OSSRH reaches EOL on June 30, 2025 - a migration to "Maven Central Repository" is necessary. [Sonatype mentions](https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration) that is's enough to update the configuration of the `gradle-nexus/publish-plugin`. A "canary" release of CEL-Java worked fine, but a Nessie release ran into quite some errors (empty deployment, missing mandatory files). This might be because of the _Portal OSSRH Staging API_, which _is a partial reimplementation of the OSSRH / Nexus Repository Manager 2 Staging APIs_. The noteable differences between CEL-Java and Nessie is that Nessie has way more artifacts and the publishing/deployment takes much longer (< 5 minutes vs 25-30 minutes). In the mid/long term it is likely anyway the better alternative to use the [Portal Publisher API](https://central.sonatype.org/publish/publish-portal-api/). Althought there are many Gradle plugins around that support that API, none seems to fully fit the needs of multi-project deployments - to publish an aggregated file containing the contents for all modules to be released. Two Gradle plugins looked promising though: * [GradleUp/nmcp](https://github.com/GradleUp/nmcp) (author also took over the shadow plugin) has support for "aggregated deployments", but in the current form the plugin fails for projects that change the build-directory, which is what the Nessie Spark extensions do. * [zenhelix/maven-central-publish](https://github.com/zenhelix/maven-central-publish/tree/main) however was chosen as it was easier to integrate, but required some work in our root `build.gradle.kts` integrating the support for aggregated multi-project deployments.
1 parent 0c35218 commit c2290a6

File tree

4 files changed

+152
-24
lines changed

4 files changed

+152
-24
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ jobs:
101101
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
102102
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.OSSRH_ACCESS_ID }}
103103
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.OSSRH_TOKEN }}
104-
run: ./gradlew --rerun-tasks --no-watch-fs -x jmh publishToSonatype closeAndReleaseSonatypeStagingRepository -Prelease
104+
run: ./gradlew publishAggregateMavenCentralDeployment -Prelease --no-scan --stacktrace
105105

106106
- name: Bump to next development version
107107
run: echo "${NEXT_VERSION}-SNAPSHOT" > version.txt

build.gradle.kts

Lines changed: 143 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,21 @@
1414
* limitations under the License.
1515
*/
1616

17-
import org.jetbrains.gradle.ext.*
17+
import io.github.zenhelix.gradle.plugin.MavenCentralUploaderPlugin.Companion.MAVEN_CENTRAL_PORTAL_NAME
18+
import io.github.zenhelix.gradle.plugin.extension.MavenCentralUploaderExtension
19+
import io.github.zenhelix.gradle.plugin.extension.PublishingType
20+
import io.github.zenhelix.gradle.plugin.task.PublishBundleMavenCentralTask
21+
import io.github.zenhelix.gradle.plugin.task.ZipDeploymentTask
22+
import java.time.Duration
23+
import org.gradle.api.publish.plugins.PublishingPlugin.PUBLISH_TASK_GROUP
24+
import org.jetbrains.gradle.ext.settings
25+
import org.jetbrains.gradle.ext.taskTriggers
1826

1927
plugins {
2028
signing
21-
`maven-publish`
2229
alias(libs.plugins.testsummary)
2330
alias(libs.plugins.testrerun)
24-
alias(libs.plugins.nexus.publish)
31+
alias(libs.plugins.maven.central.publish)
2532
`cel-conventions`
2633
}
2734

@@ -33,24 +40,140 @@ tasks.named<Wrapper>("wrapper") { distributionType = Wrapper.DistributionType.AL
3340
// Pass environment variables:
3441
// ORG_GRADLE_PROJECT_sonatypeUsername
3542
// ORG_GRADLE_PROJECT_sonatypePassword
36-
// OR in ~/.gradle/gradle.properties set
37-
// sonatypeUsername
38-
// sonatypePassword
39-
// Call targets:
40-
// publishToSonatype
41-
// closeAndReleaseSonatypeStagingRepository
42-
nexusPublishing {
43-
transitionCheckOptions {
44-
// default==60 (10 minutes), wait up to 60 minutes
45-
maxRetries.set(360)
46-
// default 10s
47-
delayBetween.set(java.time.Duration.ofSeconds(10))
43+
// Gradle targets:
44+
// publishAggregateMavenCentralDeployment
45+
// (zipAggregateMavenCentralDeployment to just generate the single, aggregated deployment zip)
46+
// Ref: Maven Central Publisher API:
47+
// https://central.sonatype.org/publish/publish-portal-api/#uploading-a-deployment-bundle
48+
mavenCentralPortal {
49+
credentials {
50+
username.value(provider { System.getenv("ORG_GRADLE_PROJECT_sonatypeUsername") })
51+
password.value(provider { System.getenv("ORG_GRADLE_PROJECT_sonatypePassword") })
4852
}
49-
repositories {
50-
// see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration
51-
sonatype {
52-
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
53-
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
53+
54+
deploymentName = "${project.name}-$version"
55+
56+
// publishingType
57+
// AUTOMATIC = fully automatic release
58+
// USER_MANAGED = user has to manually publish/drop
59+
publishingType =
60+
if (System.getenv("CI") != null) PublishingType.AUTOMATIC else PublishingType.USER_MANAGED
61+
// baseUrl = "https://central.sonatype.com"
62+
uploader {
63+
// 2 seconds * 3600 = 7200 seconds = 2hrs
64+
delayRetriesStatusCheck = Duration.ofSeconds(2)
65+
maxRetriesStatusCheck = 3600
66+
67+
aggregate {
68+
// Aggregate submodules into a single archive
69+
modules = true
70+
// Aggregate publications into a single archive for each module
71+
modulePublications = true
72+
}
73+
}
74+
}
75+
76+
val mavenCentralDeploymentZipAggregation by configurations.creating
77+
78+
mavenCentralDeploymentZipAggregation.isTransitive = true
79+
80+
val zipAggregateMavenCentralDeployment by
81+
tasks.registering(Zip::class) {
82+
group = PUBLISH_TASK_GROUP
83+
description = "Generates the aggregated Maven publication zip file."
84+
85+
inputs.files(mavenCentralDeploymentZipAggregation)
86+
from(mavenCentralDeploymentZipAggregation.map { zipTree(it) })
87+
// archiveFileName = mavenCentralPortal.deploymentName.orElse(project.name)
88+
destinationDirectory.set(layout.buildDirectory.dir("aggregatedDistribution"))
89+
doLast { logger.lifecycle("Built aggregated distribution ${archiveFile.get()}") }
90+
}
91+
92+
val publishAggregateMavenCentralDeployment by
93+
tasks.registering(PublishBundleMavenCentralTask::class) {
94+
group = PUBLISH_TASK_GROUP
95+
description =
96+
"Publishes the aggregated Maven publications $MAVEN_CENTRAL_PORTAL_NAME repository."
97+
98+
dependsOn(zipAggregateMavenCentralDeployment)
99+
inputs.file(zipAggregateMavenCentralDeployment.flatMap { it.archiveFile })
100+
101+
val task = this
102+
103+
project.extensions.configure<MavenCentralUploaderExtension> {
104+
val ext = this
105+
task.baseUrl.set(ext.baseUrl)
106+
task.credentials.set(
107+
ext.credentials.username.flatMap { username ->
108+
ext.credentials.password.map { password ->
109+
io.github.zenhelix.gradle.plugin.client.model.Credentials.UsernamePasswordCredentials(
110+
username,
111+
password,
112+
)
113+
}
114+
}
115+
)
116+
117+
task.publishingType.set(
118+
ext.publishingType.map {
119+
when (it) {
120+
PublishingType.AUTOMATIC ->
121+
io.github.zenhelix.gradle.plugin.client.model.PublishingType.AUTOMATIC
122+
PublishingType.USER_MANAGED ->
123+
io.github.zenhelix.gradle.plugin.client.model.PublishingType.USER_MANAGED
124+
}
125+
}
126+
)
127+
task.deploymentName.set(ext.deploymentName)
128+
129+
task.maxRetriesStatusCheck.set(ext.uploader.maxRetriesStatusCheck)
130+
task.delayRetriesStatusCheck.set(ext.uploader.delayRetriesStatusCheck)
131+
132+
task.zipFile.set(zipAggregateMavenCentralDeployment.flatMap { it.archiveFile })
133+
}
134+
}
135+
136+
// Configure the 'io.github.zenhelix.maven-central-publish' plugin to all projects
137+
allprojects.forEach { p ->
138+
p.pluginManager.withPlugin("maven-publish") {
139+
p.pluginManager.apply("io.github.zenhelix.maven-central-publish")
140+
p.extensions.configure<MavenCentralUploaderExtension> {
141+
val aggregatedMavenCentralDeploymentZipPart by p.configurations.creating
142+
aggregatedMavenCentralDeploymentZipPart.description = "Maven central publication zip"
143+
val aggregatedMavenCentralDeploymentZipPartElements by p.configurations.creating
144+
aggregatedMavenCentralDeploymentZipPartElements.description =
145+
"Elements for the Maven central publication zip"
146+
aggregatedMavenCentralDeploymentZipPartElements.isCanBeResolved = false
147+
aggregatedMavenCentralDeploymentZipPartElements.extendsFrom(
148+
aggregatedMavenCentralDeploymentZipPart
149+
)
150+
aggregatedMavenCentralDeploymentZipPartElements.attributes {
151+
attribute(
152+
Usage.USAGE_ATTRIBUTE,
153+
project.getObjects().named(Usage::class.java, "publication"),
154+
)
155+
}
156+
157+
val aggregatemavenCentralDeployment by
158+
p.tasks.registering {
159+
val zip = p.tasks.findByName("zipDeploymentMavenPublication") as ZipDeploymentTask
160+
dependsOn(zip)
161+
outputs.file(zip.archiveFile.get().asFile)
162+
}
163+
164+
val artifact =
165+
p.artifacts.add(
166+
aggregatedMavenCentralDeploymentZipPart.name,
167+
aggregatemavenCentralDeployment,
168+
) {
169+
builtBy(aggregatemavenCentralDeployment)
170+
}
171+
aggregatedMavenCentralDeploymentZipPart.outgoing.artifact(artifact)
172+
173+
rootProject.dependencies.add(
174+
mavenCentralDeploymentZipAggregation.name,
175+
rootProject.dependencies.project(p.path, aggregatedMavenCentralDeploymentZipPart.name),
176+
)
54177
}
55178
}
56179
}

buildSrc/src/main/kotlin/PublishingHelperPlugin.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import org.gradle.api.artifacts.component.ModuleComponentSelector
2525
import org.gradle.api.artifacts.result.DependencyResult
2626
import org.gradle.api.publish.PublishingExtension
2727
import org.gradle.api.publish.maven.MavenPublication
28-
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
2928
import org.gradle.api.publish.tasks.GenerateModuleMetadata
3029
import org.gradle.api.tasks.PathSensitivity
3130
import org.gradle.kotlin.dsl.configure
@@ -43,7 +42,7 @@ class PublishingHelperPlugin : Plugin<Project> {
4342
project.run {
4443
extensions.create("publishingHelper", PublishingHelperExtension::class.java, this)
4544

46-
plugins.withType<MavenPublishPlugin>().configureEach {
45+
plugins.withId("io.github.zenhelix.maven-central-publish") {
4746
configure<PublishingExtension> {
4847
publications {
4948
register<MavenPublication>("maven") {
@@ -63,6 +62,8 @@ class PublishingHelperPlugin : Plugin<Project> {
6362
suppressPomMetadataWarningsFor("testJavadocElements")
6463
suppressPomMetadataWarningsFor("testRuntimeElements")
6564
suppressPomMetadataWarningsFor("testSourcesElements")
65+
suppressPomMetadataWarningsFor("testFixturesApiElements")
66+
suppressPomMetadataWarningsFor("testFixturesRuntimeElements")
6667

6768
groupId = "$group"
6869
version = project.version.toString()
@@ -196,6 +197,10 @@ class PublishingHelperPlugin : Plugin<Project> {
196197
useInMemoryPgpKeys(signingKey, signingPassword)
197198
val publishing = project.extensions.getByType(PublishingExtension::class.java)
198199
afterEvaluate { sign(publishing.publications.getByName("maven")) }
200+
201+
if (project.hasProperty("useGpgAgent")) {
202+
useGpgCmd()
203+
}
199204
}
200205
}
201206
}

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ tomcat-annotations-api = { module = "org.apache.tomcat:annotations-api", version
6767
idea-ext = { id = "org.jetbrains.gradle.plugin.idea-ext", version = "1.1.10" }
6868
jandex = { id = "com.github.vlsi.jandex", version.ref = "jandexPlugin" }
6969
jmh = { id = "me.champeau.jmh", version = "0.7.3" }
70-
nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" }
70+
maven-central-publish = { id = "io.github.zenhelix.maven-central-publish", version = "0.8.0" }
7171
protobuf = { id = "com.google.protobuf", version.ref = "protobufPlugin" }
7272
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadowPlugin" }
7373
spotless = { id = "com.diffplug.spotless", version.ref = "spotlessPlugin" }

0 commit comments

Comments
 (0)