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/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

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") + } + } } } }