From d681a336e7cbe8049fa05db0e28f0dc5c8346db8 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Wed, 5 Nov 2025 12:11:03 +0000 Subject: [PATCH 1/2] Replace nexus publishing repo with direct call to central sonatype uploading URL This replaces the usage of the nexus publishing plugin with direct calls to the sonatype central publishing API. It makes the following modifications: 1. Each published project will write to a central staging repo if we have enabled central publishing. 1. A new task, `createMavenCentralBundle`, will go through and scoop up all of the relevant files from the staging repo to produce the final bundle 1. An additional task, `publishMavenCentralBundle`, will upload the bundle to the central publishing API (see: https://central.sonatype.com/api-doc and https://central.sonatype.org/publish/publish-portal-api/#uploading-a-deployment-bundle) 1. The release action now calls the new `publishMavenCentralBundle` task This should allow us to retire our usage of the legacy API. --- .github/workflows/release.yml | 2 +- build.gradle | 106 +++++++++++++++++++++++++++++++--- gradle/libs.versions.toml | 1 - gradle/publishing.gradle | 8 +++ 4 files changed, 107 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b6de4deefe..a79079e0fd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -185,7 +185,7 @@ jobs: - name: Publish Artifacts uses: ./actions/run-gradle with: - gradle_command: publish closeAndReleaseStagingRepositories -PreleaseBuild=true -PpublishBuild=true -PgithubPublish=true -PcentralPublish=true + gradle_command: publish publishMavenCentralBundle -PreleaseBuild=true -PpublishBuild=true -PgithubPublish=true -PcentralPublish=true env: ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSPHRASE }} diff --git a/build.gradle b/build.gradle index d4dc5c72ea..430bdfd5f9 100644 --- a/build.gradle +++ b/build.gradle @@ -41,7 +41,6 @@ plugins { alias(libs.plugins.protobuf) alias(libs.plugins.versions) alias(libs.plugins.spotbugs) - alias(libs.plugins.nexus) alias(libs.plugins.download) } @@ -310,13 +309,104 @@ subprojects { // Configure publishing for maven central. This is done in the top-level build.gradle, and then // all of the subprojects configure the artifacts they want published by applying // ${rootDir}/gradle/publishing.gradle in the project gradle. By default, we publish a library -// from each package, but this can be configured by adjusting the publishLibrary variable -if (Boolean.parseBoolean(centralPublish)) { - nexusPublishing { - repositories { - sonatype { - // Update the URL now that the OSSRH service has been sunset: https://central.sonatype.org/news/20250326_ossrh_sunset/ - nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) +// from each package, but this can be configured by adjusting the publishLibrary variable. +// Each subproject will place its artifacts into the central staging repository, +// which will then be picked up by the bundle task +if (Boolean.parseBoolean(publishBuild) && Boolean.parseBoolean(centralPublish)) { + var stagingRepo = layout.buildDirectory.dir('staging-repo') + + // Task to create a bundle zip of all published artifacts + tasks.register('createMavenCentralBundle', Zip) { + description = "Creates a bundle zip of all artifacts for Maven Central upload" + group = "publishing" + + dependsOn subprojects.collect { it.tasks.matching { it.name == 'publish' } } + + archiveBaseName = "${rootProject.group}-${rootProject.name}" + archiveVersion = project.version + archiveClassifier = 'bundle' + destinationDirectory = layout.buildDirectory.dir('distributions') + + from(stagingRepo) { + include "${rootProject.group.replaceAll('.', '/')}/**/${project.version}/*" + exclude '**/maven-metadata*' + } + + includeEmptyDirs = false + } + + // Task to upload the bundle to Maven Central + tasks.register('publishMavenCentralBundle') { + description = "Uploads the bundle zip to Maven Central Portal" + group = "publishing" + + dependsOn tasks.named('createMavenCentralBundle') + + doLast { + def sonatypeUsername = findProperty('sonatypeUsername') + def sonatypePassword = findProperty('sonatypePassword') + + if (sonatypeUsername == null || sonatypePassword == null) { + throw new GradleException("sonatypeUsername and sonatypePassword properties must be set") + } + + // Create base64 encoded credentials + def credentials = "${sonatypeUsername}:${sonatypePassword}" + def encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes('UTF-8')) + + // Get the bundle file + def bundleTask = tasks.named('createMavenCentralBundle').get() + def bundleFile = bundleTask.archiveFile.get().asFile + + if (!bundleFile.exists()) { + throw new GradleException("Bundle file does not exist: ${bundleFile}") + } + + logger.lifecycle("Uploading bundle: ${bundleFile.name} (${bundleFile.length() / 1024 / 1024} MB)") + + // Upload using HttpURLConnection for multipart/form-data + def url = new URL('https://central.sonatype.com/api/v1/publisher/upload?publishingType=AUTOMATIC') + def connection = url.openConnection() as HttpURLConnection + + try { + connection.setRequestMethod('POST') + connection.setDoOutput(true) + connection.setRequestProperty('Authorization', "Bearer ${encodedCredentials}") + + def boundary = "----GradleBoundary${System.currentTimeMillis()}" + connection.setRequestProperty('Content-Type', "multipart/form-data; boundary=${boundary}") + + connection.outputStream.withWriter('UTF-8') { writer -> + writer.write("--${boundary}\r\n") + writer.write("Content-Disposition: form-data; name=\"bundle\"; filename=\"${bundleFile.name}\"\r\n") + writer.write("Content-Type: application/octet-stream\r\n") + writer.write("\r\n") + writer.flush() + + // Write the file bytes + bundleFile.withInputStream { input -> + connection.outputStream << input + } + + writer.write("\r\n") + writer.write("--${boundary}--\r\n") + writer.flush() + } + + def responseCode = connection.responseCode + def responseMessage = connection.responseMessage + + if (responseCode >= 200 && responseCode < 300) { + def deploymentId = connection.inputStream.text.trim() + logger.lifecycle("Upload successful!") + logger.lifecycle("Deployment ID: ${deploymentId}") + logger.lifecycle("You can check the status at: https://central.sonatype.com/api/v1/publisher/status?id=${deploymentId}") + } else { + def errorResponse = connection.errorStream?.text ?: responseMessage + throw new GradleException("Upload failed with status ${responseCode}: ${errorResponse}") + } + } finally { + connection.disconnect() } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c4e6482b97..4cd963e1b3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -153,7 +153,6 @@ test-compileOnly = [ "autoService", "jsr305" ] download = { id = "de.undercouch.download", version = "5.6.0" } gitversion = { id = "com.palantir.git-version", version = "3.1.0" } jmh = { id = "me.champeau.jmh", version = "0.7.2" } -nexus = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" } protobuf = { id = "com.google.protobuf", version = "0.9.4" } serviceloader = { id = "com.github.harbby.gradle.serviceloader", version = "1.1.8" } shadow = { id = "com.gradleup.shadow", version = "8.3.5" } diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle index 79e4d82ef9..1f800af860 100644 --- a/gradle/publishing.gradle +++ b/gradle/publishing.gradle @@ -113,6 +113,14 @@ publishing { } } } + + if (Boolean.parseBoolean(centralPublish)) { + // Publish this project to the root project's staging repo. This will be + // bundled up by createMavenCentralBundle for publishing + maven { + url = rootProject.layout.buildDirectory.dir("staging-repo") + } + } } } } From e0c7eb0d000efbf8f6fe783cdd32becb69ffb3a6 Mon Sep 17 00:00:00 2001 From: Alec Grieser Date: Wed, 5 Nov 2025 12:34:37 +0000 Subject: [PATCH 2/2] Remove release notes for unpublished 4.8.9.0 release --- docs/sphinx/source/ReleaseNotes.md | 31 ------------------------------ 1 file changed, 31 deletions(-) diff --git a/docs/sphinx/source/ReleaseNotes.md b/docs/sphinx/source/ReleaseNotes.md index 3d0f5b51b5..521b0a62c8 100644 --- a/docs/sphinx/source/ReleaseNotes.md +++ b/docs/sphinx/source/ReleaseNotes.md @@ -7,37 +7,6 @@ As the [versioning guide](Versioning.md) details, it cannot always be determined ## 4.8 -### 4.8.9.0 - - -
- - -

Build/Test/Documentation/Style Improvements (click to expand)

- -
- -* Increase heap size for JReleaser - [PR #3720](https://github.com/FoundationDB/fdb-record-layer/pull/3720) -* Clean up 4.8.4.0 and 4.8.7.0 release notes - [PR #3719](https://github.com/FoundationDB/fdb-record-layer/pull/3719) -* Upload the jreleaser trace logs & config - [PR #3718](https://github.com/FoundationDB/fdb-record-layer/pull/3718) -* Reduce test load to reduce flakiness - [PR #3712](https://github.com/FoundationDB/fdb-record-layer/pull/3712) -* Update the release plugin to use the central publishing API directly - [PR #3710](https://github.com/FoundationDB/fdb-record-layer/pull/3710) - -
- - -**[Full Changelog (4.8.6.0...4.8.9.0)](https://github.com/FoundationDB/fdb-record-layer/compare/4.8.6.0...4.8.9.0)** - -#### Mixed Mode Test Results - -Mixed mode testing run against the following previous versions: - -❌`4.6.4.0`, ❌`4.6.5.0`, ❌`4.7.1.0`, ❌`4.7.2.0`, ✅`4.7.3.0`, ✅`4.8.1.0`, ✅`4.8.2.0`, ✅`4.8.3.0`, ✅`4.8.5.0`, ✅`4.8.6.0` - -[See full test run](https://github.com/FoundationDB/fdb-record-layer/actions/runs/19075271823) - - - ### 4.8.6.0

New Features