diff --git a/.github/workflows/deploy-snapshot.yml b/.github/workflows/deploy-snapshot.yml index 6672f84..c918168 100644 --- a/.github/workflows/deploy-snapshot.yml +++ b/.github/workflows/deploy-snapshot.yml @@ -35,7 +35,7 @@ jobs: run: ./gradlew build - name: Publish package - run: ./gradlew publishAllPublicationsToSonatypeSnapshotRepository + run: ./gradlew publishSnapshotToCentral env: SONATYPE_USER: ${{ secrets.SONATYPE_USER }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} diff --git a/.github/workflows/publish-maven-central.yml b/.github/workflows/publish-maven-central.yml index 5d467d3..9d43b22 100644 --- a/.github/workflows/publish-maven-central.yml +++ b/.github/workflows/publish-maven-central.yml @@ -19,10 +19,10 @@ jobs: - name: Build run: ./gradlew build - - name: Publish package - run: ./gradlew publishAllPublicationsToMavenCentralRepository + - name: Upload to Sonatype Central + run: ./gradlew uploadToSonatypeCentral env: - SONATYPE_USER: ${{ secrets.SONATYPE_USER }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} + SONATYPE_USER: ${{ secrets.CENTRAL_SONATYPE_USERNAME }} + SONATYPE_PASSWORD: ${{ secrets.CENTRAL_SONATYPE_PASSWORD }} DEXMAKER_GPG_PRIVATE_KEY: ${{ secrets.DEXMAKER_GPG_PRIVATE_KEY }} DEXMAKER_GPG_PRIVATE_KEY_PASSWORD: ${{ secrets.DEXMAKER_GPG_PRIVATE_KEY_PASSWORD }} diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle index 9d504ae..4932a1e 100644 --- a/gradle/publishing.gradle +++ b/gradle/publishing.gradle @@ -1,11 +1,17 @@ apply plugin: 'maven-publish' apply plugin: 'signing' +apply from: "$rootDir/gradle/sonatype-central.gradle" java { withSourcesJar() withJavadocJar() } +// Task to publish to local repository for bundle creation +tasks.register("publishToLocalMaven", Task) { + dependsOn "publishMavenPublicationToLocalRepository" +} + publishing { publications { maven(MavenPublication) { @@ -38,16 +44,8 @@ publishing { def sonatypeUsername = System.getenv("SONATYPE_USER") def sonatypePassword = System.getenv("SONATYPE_PASSWORD") maven { - name = "sonatypeSnapshot" - url = "https://oss.sonatype.org/content/repositories/snapshots" - credentials { - username = sonatypeUsername - password = sonatypePassword - } - } - maven { - name = "mavenCentral" - url = "https://oss.sonatype.org/service/local/staging/deploy/maven2" + name = "centralSnapshot" + url = "https://central.sonatype.com/api/v1/publisher/snapshots" credentials { username = sonatypeUsername password = sonatypePassword diff --git a/gradle/publishing_aar.gradle b/gradle/publishing_aar.gradle index 0864833..39b1b7a 100644 --- a/gradle/publishing_aar.gradle +++ b/gradle/publishing_aar.gradle @@ -1,5 +1,6 @@ apply plugin: 'maven-publish' apply plugin: 'signing' +apply from: "$rootDir/gradle/sonatype-central.gradle" android { // Explicitly set the NDK release to ensure we use the latest stable, as @@ -20,6 +21,12 @@ android { } } +// Task to publish to local repository for bundle creation +tasks.register("publishToLocalMaven", Task) { + dependsOn "publishMavenPublicationToLocalRepository" +} + + // AGP creates the components in afterEvaluate, so we need to use it too // https://developer.android.com/studio/build/maven-publish-plugin afterEvaluate { @@ -55,16 +62,8 @@ afterEvaluate { def sonatypeUsername = System.getenv("SONATYPE_USER") def sonatypePassword = System.getenv("SONATYPE_PASSWORD") maven { - name = "sonatypeSnapshot" - url = "https://oss.sonatype.org/content/repositories/snapshots" - credentials { - username = sonatypeUsername - password = sonatypePassword - } - } - maven { - name = "mavenCentral" - url = "https://oss.sonatype.org/service/local/staging/deploy/maven2" + name = "centralSnapshot" + url = "https://central.sonatype.com/api/v1/publisher/snapshots" credentials { username = sonatypeUsername password = sonatypePassword diff --git a/gradle/sonatype-central.gradle b/gradle/sonatype-central.gradle new file mode 100644 index 0000000..626e7dc --- /dev/null +++ b/gradle/sonatype-central.gradle @@ -0,0 +1,99 @@ +// Common Sonatype Central publishing functionality +// This script can be applied by both publishing.gradle and publishing_aar.gradle + +// Custom task to upload deployment bundle to Sonatype Central API +tasks.register("uploadToSonatypeCentral") { + dependsOn publishToLocalMaven + doLast { + def sonatypeUsername = System.getenv("SONATYPE_USER") + def sonatypePassword = System.getenv("SONATYPE_PASSWORD") + + if (!sonatypeUsername || !sonatypePassword) { + throw new GradleException("SONATYPE_USER and SONATYPE_PASSWORD environment variables must be set") + } + + // Create deployment bundle (zip file containing all artifacts) + def bundleFile = new File(project.buildDir, "distributions/deployment-bundle.zip") + bundleFile.parentFile.mkdirs() + + ant.zip(destfile: bundleFile) { + fileset(dir: "${project.buildDir}/repo") { + include(name: "**/*") + } + } + + // Create base64 encoded credentials + def credentials = "${sonatypeUsername}:${sonatypePassword}" + def encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes()) + + // Upload bundle to Sonatype Central API + def connection = new URL("https://central.sonatype.com/api/v1/publisher/upload").openConnection() + connection.setRequestMethod("POST") + connection.setRequestProperty("Authorization", "Bearer ${encodedCredentials}") + connection.setRequestProperty("Content-Type", "multipart/form-data") + connection.setDoOutput(true) + + // Set up multipart form data + def boundary = "----WebKitFormBoundary" + System.currentTimeMillis() + connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary) + + def outputStream = connection.getOutputStream() + def writer = new PrintWriter(new OutputStreamWriter(outputStream, "UTF-8"), true) + + // Add file part + writer.append("--" + boundary).append("\r\n") + writer.append("Content-Disposition: form-data; name=\"bundle\"; filename=\"" + bundleFile.name + "\"").append("\r\n") + writer.append("Content-Type: application/zip").append("\r\n") + writer.append("\r\n") + writer.flush() + + // Write file content + bundleFile.withInputStream { inputStream -> + outputStream << inputStream + } + outputStream.flush() + + writer.append("\r\n") + writer.append("--" + boundary + "--").append("\r\n") + writer.close() + + // Get response + def responseCode = connection.getResponseCode() + if (responseCode == 200 || responseCode == 201) { + println "Bundle uploaded successfully to Sonatype Central" + def response = connection.getInputStream().getText() + println "Response: ${response}" + } else { + def errorResponse = connection.getErrorStream()?.getText() ?: "No error details available" + throw new GradleException("Failed to upload bundle. Response code: ${responseCode}, Error: ${errorResponse}") + } + } +} + +// Task to publish snapshots to Sonatype Central Portal +tasks.register("publishSnapshotToCentral") { + description = "Publishes snapshot artifacts to Sonatype Central Portal" + group = "publishing" + + doFirst { + // Ensure version ends with -SNAPSHOT + if (!project.version.toString().endsWith('-SNAPSHOT')) { + throw new GradleException("Version must end with -SNAPSHOT to publish as snapshot. Current version: ${project.version}") + } + } + + dependsOn "publishAllPublicationsToCentralSnapshotRepository" +} + +// Task to publish artifacts to local Maven repository +tasks.register("publishToLocalMavenRepository") { + description = "Publishes all artifacts to local Maven repository (~/.m2/repository)" + group = "publishing" + + dependsOn "publishToMavenLocal" + + doLast { + println "Artifacts published to local Maven repository at: ${System.getProperty('user.home')}/.m2/repository" + println "You can now use the artifacts in other local projects with version: ${project.version}" + } +}