@@ -41,6 +41,7 @@ plugins {
4141 alias(libs. plugins. protobuf)
4242 alias(libs. plugins. versions)
4343 alias(libs. plugins. spotbugs)
44+ alias(libs. plugins. nexus)
4445 alias(libs. plugins. download)
4546}
4647
@@ -309,109 +310,13 @@ subprojects {
309310// Configure publishing for maven central. This is done in the top-level build.gradle, and then
310311// all of the subprojects configure the artifacts they want published by applying
311312// ${rootDir}/gradle/publishing.gradle in the project gradle. By default, we publish a library
312- // from each package, but this can be configured by adjusting the publishLibrary variable.
313- // Each subproject will place its artifacts into the central staging repository,
314- // which will then be picked up by the bundle task
315- if (Boolean . parseBoolean(publishBuild) && Boolean . parseBoolean(centralPublish)) {
316- var stagingRepo = layout. buildDirectory. dir(' staging-repo' )
317-
318- // Task to create a bundle zip of all published artifacts
319- tasks. register(' createMavenCentralBundle' , Zip ) {
320- description = " Creates a bundle zip of all artifacts for Maven Central upload"
321- group = " publishing"
322-
323- dependsOn subprojects. collect { it. tasks. matching { it. name == ' publish' } }
324-
325- archiveBaseName = " ${ rootProject.group} -${ rootProject.name} "
326- archiveVersion = project. version
327- archiveClassifier = ' bundle'
328- destinationDirectory = layout. buildDirectory. dir(' distributions' )
329-
330- from(stagingRepo) {
331- include " ${ rootProject.group.replaceAll('.', '/')} /**/${ project.version} /*"
332- exclude ' **/maven-metadata*'
333- }
334-
335- includeEmptyDirs = false
336- }
337-
338- // Task to upload the bundle to Maven Central
339- tasks. register(' publishMavenCentralBundle' ) {
340- description = " Uploads the bundle zip to Maven Central Portal"
341- group = " publishing"
342-
343- dependsOn tasks. named(' createMavenCentralBundle' )
344-
345- doLast {
346- def sonatypeUsername = findProperty(' sonatypeUsername' )
347- def sonatypePassword = findProperty(' sonatypePassword' )
348-
349- if (sonatypeUsername == null || sonatypePassword == null ) {
350- throw new GradleException (" sonatypeUsername and sonatypePassword properties must be set" )
351- }
352-
353- // Create base64 encoded credentials
354- def credentials = " ${ sonatypeUsername} :${ sonatypePassword} "
355- def encodedCredentials = Base64 . getEncoder(). encodeToString(credentials. getBytes(' UTF-8' ))
356-
357- // Get the bundle file
358- def bundleTask = tasks. named(' createMavenCentralBundle' ). get()
359- def bundleFile = bundleTask. archiveFile. get(). asFile
360-
361- if (! bundleFile. exists()) {
362- throw new GradleException (" Bundle file does not exist: ${ bundleFile} " )
363- }
364-
365- logger. lifecycle(" Uploading bundle: ${ bundleFile.name} (${ bundleFile.length() / 1024 / 1024} MB)" )
366-
367- // Upload using HttpURLConnection for multipart/form-data
368- def url = new URL (' https://central.sonatype.com/api/v1/publisher/upload?publishingType=AUTOMATIC' )
369- def connection = url. openConnection() as HttpURLConnection
370-
371- try {
372- connection. setRequestMethod(' POST' )
373- connection. setDoOutput(true )
374- connection. setRequestProperty(' Authorization' , " Bearer ${ encodedCredentials} " )
375-
376- def boundary = " ----GradleBoundary${ UUID.randomUUID().toString()} "
377- connection. setRequestProperty(' Content-Type' , " multipart/form-data; boundary=${ boundary} " )
378-
379- // Use a chunked streaming mode to break data up into smaller chunks to avoid needing to load everything into memory
380- connection. setChunkedStreamingMode(8192 )
381-
382- // Use buffered output stream and write directly to avoid loading entire file into memory
383- connection. outputStream. withStream { output ->
384- def CRLF = " \r\n "
385-
386- // Write multipart headers
387- output. write(" --${ boundary}${ CRLF} " . getBytes(' UTF-8' ))
388- output. write(" Content-Disposition: form-data; name=\" bundle\" ; filename=\" ${ bundleFile.name} \" ${ CRLF} " . getBytes(' UTF-8' ))
389- output. write(" Content-Type: application/octet-stream${ CRLF} " . getBytes(' UTF-8' ))
390- output. write(CRLF . getBytes(' UTF-8' ))
391-
392- // Next write the bundle data to the stream
393- bundleFile. withInputStream { InputStream input -> output << input }
394-
395- // Write multipart closing boundary
396- output. write(CRLF . getBytes(' UTF-8' ))
397- output. write(" --${ boundary} --${ CRLF} " . getBytes(' UTF-8' ))
398- output. flush()
399- }
400-
401- def responseCode = connection. responseCode
402- def responseMessage = connection. responseMessage
403-
404- if (responseCode >= 200 && responseCode < 300 ) {
405- def deploymentId = connection. inputStream. text. trim()
406- logger. lifecycle(" Upload successful!" )
407- logger. lifecycle(" Deployment ID: ${ deploymentId} " )
408- logger. lifecycle(" You can check the status at: https://central.sonatype.com/api/v1/publisher/status?id=${ deploymentId} " )
409- } else {
410- def errorResponse = connection. errorStream?. text ?: responseMessage
411- throw new GradleException (" Upload failed with status ${ responseCode} : ${ errorResponse} " )
412- }
413- } finally {
414- connection. disconnect()
313+ // from each package, but this can be configured by adjusting the publishLibrary variable
314+ if (Boolean . parseBoolean(centralPublish)) {
315+ nexusPublishing {
316+ repositories {
317+ sonatype {
318+ // Update the URL now that the OSSRH service has been sunset: https://central.sonatype.org/news/20250326_ossrh_sunset/
319+ nexusUrl. set(uri(" https://ossrh-staging-api.central.sonatype.com/service/local/" ))
415320 }
416321 }
417322 }
0 commit comments