From d90d0f3f9f697563d6833c68cbeda8ab181d081d Mon Sep 17 00:00:00 2001 From: Kim Seon Woo <69591622+seonwoo960000@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:52:12 +0900 Subject: [PATCH] Separate publish.gradle.kts from build.gradle.kts --- bindings/java/build.gradle.kts | 244 +---------------------- bindings/java/gradle/publish.gradle.kts | 250 ++++++++++++++++++++++++ 2 files changed, 253 insertions(+), 241 deletions(-) create mode 100644 bindings/java/gradle/publish.gradle.kts diff --git a/bindings/java/build.gradle.kts b/bindings/java/build.gradle.kts index 0def7e18d..4fb7a704e 100644 --- a/bindings/java/build.gradle.kts +++ b/bindings/java/build.gradle.kts @@ -2,7 +2,6 @@ import net.ltgt.gradle.errorprone.CheckSeverity import net.ltgt.gradle.errorprone.errorprone import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.gradle.api.tasks.testing.logging.TestLogEvent -import java.security.MessageDigest plugins { java @@ -16,6 +15,9 @@ plugins { id("com.diffplug.spotless") version "6.13.0" } +// Apply publishing configuration +apply(from = "gradle/publish.gradle.kts") + // Helper function to read properties with defaults fun prop(key: String, default: String? = null): String? = findProperty(key)?.toString() ?: default @@ -39,246 +41,6 @@ tasks.withType { } } -publishing { - publications { - create("mavenJava") { - from(components["java"]) - groupId = prop("projectGroup")!! - artifactId = prop("projectArtifactId")!! - version = prop("projectVersion")!! - - pom { - name.set(prop("pomName")) - description.set(prop("pomDescription")) - url.set(prop("pomUrl")) - - licenses { - license { - name.set(prop("pomLicenseName")) - url.set(prop("pomLicenseUrl")) - } - } - - developers { - developer { - id.set(prop("pomDeveloperId")) - name.set(prop("pomDeveloperName")) - email.set(prop("pomDeveloperEmail")) - } - } - - scm { - connection.set(prop("pomScmConnection")) - developerConnection.set(prop("pomScmDeveloperConnection")) - url.set(prop("pomScmUrl")) - } - } - } - } -} - -signing { - // Make signing required for publishing - setRequired(true) - - // For CI/GitHub Actions: use in-memory keys - val signingKey = providers.environmentVariable("GPG_PRIVATE_KEY").orNull - val signingPassword = providers.environmentVariable("GPG_PASSPHRASE").orNull - - if (signingKey != null && signingPassword != null) { - // CI mode: use in-memory keys - useInMemoryPgpKeys(signingKey, signingPassword) - } else { - // Local mode: use GPG command from system - useGpgCmd() - } - - sign(publishing.publications["mavenJava"]) -} - -// Helper task to generate checksums -val generateChecksums by tasks.registering { - dependsOn("jar", "sourcesJar", "javadocJar", "generatePomFileForMavenJavaPublication") - - val checksumDir = layout.buildDirectory.dir("checksums") - - doLast { - val files = listOf( - tasks.jar.get().archiveFile.get().asFile, - tasks.named("sourcesJar").get().outputs.files.singleFile, - tasks.named("javadocJar").get().outputs.files.singleFile, - layout.buildDirectory.file("publications/mavenJava/pom-default.xml").get().asFile - ) - - checksumDir.get().asFile.mkdirs() - - files.forEach { file -> - if (file.exists()) { - // MD5 - val md5 = MessageDigest.getInstance("MD5") - .digest(file.readBytes()) - .joinToString("") { "%02x".format(it) } - file("${file.absolutePath}.md5").writeText(md5) - - // SHA1 - val sha1 = MessageDigest.getInstance("SHA-1") - .digest(file.readBytes()) - .joinToString("") { "%02x".format(it) } - file("${file.absolutePath}.sha1").writeText(sha1) - } - } - } -} - -// Task to create a bundle zip for Maven Central Portal -val createMavenCentralBundle by tasks.registering(Zip::class) { - group = "publishing" - description = "Creates a bundle zip for Maven Central Portal upload" - - dependsOn("generatePomFileForMavenJavaPublication", "jar", "sourcesJar", "javadocJar", "signMavenJavaPublication", generateChecksums) - - // Ensure signing happens before bundle creation - mustRunAfter("signMavenJavaPublication") - - val groupId = prop("projectGroup")!!.replace(".", "/") - val artifactId = prop("projectArtifactId")!! - val projectVer = version.toString() - - // Validate version is not SNAPSHOT for Maven Central - doFirst { - if (projectVer.contains("SNAPSHOT")) { - throw GradleException( - "Cannot publish SNAPSHOT version to Maven Central. " + - "Please change projectVersion in gradle.properties to a release version (e.g., 0.0.1)" - ) - } - } - - archiveFileName.set("$artifactId-$projectVer-bundle.zip") - destinationDirectory.set(layout.buildDirectory.dir("maven-central")) - - // Maven Central expects files in groupId/artifactId/version/ structure - val basePath = "$groupId/$artifactId/$projectVer" - - // Main JAR + checksums + signature - from(tasks.jar.get().archiveFile) { - into(basePath) - rename { "$artifactId-$projectVer.jar" } - } - from(tasks.jar.get().archiveFile.get().asFile.absolutePath + ".md5") { - into(basePath) - rename { "$artifactId-$projectVer.jar.md5" } - } - from(tasks.jar.get().archiveFile.get().asFile.absolutePath + ".sha1") { - into(basePath) - rename { "$artifactId-$projectVer.jar.sha1" } - } - - // Sources JAR + checksums + signature - from(tasks.named("sourcesJar").get().outputs.files) { - into(basePath) - rename { "$artifactId-$projectVer-sources.jar" } - } - from(tasks.named("sourcesJar").get().outputs.files.singleFile.absolutePath + ".md5") { - into(basePath) - rename { "$artifactId-$projectVer-sources.jar.md5" } - } - from(tasks.named("sourcesJar").get().outputs.files.singleFile.absolutePath + ".sha1") { - into(basePath) - rename { "$artifactId-$projectVer-sources.jar.sha1" } - } - - // Javadoc JAR + checksums + signature - from(tasks.named("javadocJar").get().outputs.files) { - into(basePath) - rename { "$artifactId-$projectVer-javadoc.jar" } - } - from(tasks.named("javadocJar").get().outputs.files.singleFile.absolutePath + ".md5") { - into(basePath) - rename { "$artifactId-$projectVer-javadoc.jar.md5" } - } - from(tasks.named("javadocJar").get().outputs.files.singleFile.absolutePath + ".sha1") { - into(basePath) - rename { "$artifactId-$projectVer-javadoc.jar.sha1" } - } - - // POM + checksums + signature - from(layout.buildDirectory.file("publications/mavenJava/pom-default.xml")) { - into(basePath) - rename { "$artifactId-$projectVer.pom" } - } - from(layout.buildDirectory.file("publications/mavenJava/pom-default.xml").get().asFile.absolutePath + ".md5") { - into(basePath) - rename { "$artifactId-$projectVer.pom.md5" } - } - from(layout.buildDirectory.file("publications/mavenJava/pom-default.xml").get().asFile.absolutePath + ".sha1") { - into(basePath) - rename { "$artifactId-$projectVer.pom.sha1" } - } - - // Signature files - get them from the signing task outputs - doFirst { - val signingTask = tasks.named("signMavenJavaPublication").get() - logger.lifecycle("Signing task outputs: ${signingTask.outputs.files.files}") - } - - // Include signature files generated by the signing plugin - from(tasks.named("signMavenJavaPublication").get().outputs.files) { - into(basePath) - include("*.jar.asc", "pom-default.xml.asc") - exclude("module.json.asc") // Exclude gradle module metadata signature - rename { name -> - // Only rename the POM signature file - // JAR signatures are already correctly named by the signing plugin - if (name == "pom-default.xml.asc") { - "$artifactId-$projectVer.pom.asc" - } else { - name // Keep original name (already correct) - } - } - } -} - -// Task to upload bundle to Maven Central Portal -tasks.register("publishToMavenCentral") { - group = "publishing" - description = "Publishes artifacts to Maven Central Portal" - - // Run publish first to generate signatures, then create bundle - dependsOn("publish") - dependsOn(createMavenCentralBundle) - - // Make sure bundle creation happens after publish - createMavenCentralBundle.get().mustRunAfter("publish") - - doLast { - val username = providers.environmentVariable("MAVEN_CENTRAL_USERNAME").orNull - val password = providers.environmentVariable("MAVEN_CENTRAL_PASSWORD").orNull - val bundleFile = createMavenCentralBundle.get().archiveFile.get().asFile - - require(username != null) { "MAVEN_CENTRAL_USERNAME environment variable must be set" } - require(password != null) { "MAVEN_CENTRAL_PASSWORD environment variable must be set" } - require(bundleFile.exists()) { "Bundle file does not exist: ${bundleFile.absolutePath}" } - - logger.lifecycle("Uploading bundle to Maven Central Portal...") - logger.lifecycle("Bundle: ${bundleFile.absolutePath}") - logger.lifecycle("Size: ${bundleFile.length() / 1024} KB") - - // Use curl for uploading (simple and available on most systems) - exec { - commandLine( - "curl", - "-X", "POST", - "-u", "$username:$password", - "-F", "bundle=@${bundleFile.absolutePath}", - "https://central.sonatype.com/api/v1/publisher/upload?name=${bundleFile.name}&publishingType=AUTOMATIC" - ) - } - - logger.lifecycle("Upload completed. Check https://central.sonatype.com/publishing for status.") - } -} - repositories { mavenCentral() } diff --git a/bindings/java/gradle/publish.gradle.kts b/bindings/java/gradle/publish.gradle.kts new file mode 100644 index 000000000..38f797305 --- /dev/null +++ b/bindings/java/gradle/publish.gradle.kts @@ -0,0 +1,250 @@ +import java.security.MessageDigest +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.plugins.signing.SigningExtension + +// Helper function to read properties with defaults +fun prop(key: String, default: String? = null): String? = + project.findProperty(key)?.toString() ?: default + +// Maven Publishing Configuration +configure { + publications { + create("mavenJava") { + from(components["java"]) + groupId = prop("projectGroup")!! + artifactId = prop("projectArtifactId")!! + version = prop("projectVersion")!! + + pom { + name.set(prop("pomName")) + description.set(prop("pomDescription")) + url.set(prop("pomUrl")) + + licenses { + license { + name.set(prop("pomLicenseName")) + url.set(prop("pomLicenseUrl")) + } + } + + developers { + developer { + id.set(prop("pomDeveloperId")) + name.set(prop("pomDeveloperName")) + email.set(prop("pomDeveloperEmail")) + } + } + + scm { + connection.set(prop("pomScmConnection")) + developerConnection.set(prop("pomScmDeveloperConnection")) + url.set(prop("pomScmUrl")) + } + } + } + } +} + +// GPG Signing Configuration +configure { + // Make signing required for publishing + setRequired(true) + + // For CI/GitHub Actions: use in-memory keys + val signingKey = providers.environmentVariable("GPG_PRIVATE_KEY").orNull + val signingPassword = providers.environmentVariable("GPG_PASSPHRASE").orNull + + if (signingKey != null && signingPassword != null) { + // CI mode: use in-memory keys + useInMemoryPgpKeys(signingKey, signingPassword) + } else { + // Local mode: use GPG command from system + useGpgCmd() + } + + sign(the().publications["mavenJava"]) +} + +// Helper task to generate checksums +val generateChecksums by tasks.registering { + dependsOn("jar", "sourcesJar", "javadocJar", "generatePomFileForMavenJavaPublication") + + val checksumDir = layout.buildDirectory.dir("checksums") + + doLast { + val files = listOf( + tasks.named("jar").get().outputs.files.singleFile, + tasks.named("sourcesJar").get().outputs.files.singleFile, + tasks.named("javadocJar").get().outputs.files.singleFile, + layout.buildDirectory.file("publications/mavenJava/pom-default.xml").get().asFile + ) + + checksumDir.get().asFile.mkdirs() + + files.forEach { file -> + if (file.exists()) { + // MD5 + val md5 = MessageDigest.getInstance("MD5") + .digest(file.readBytes()) + .joinToString("") { "%02x".format(it) } + file("${file.absolutePath}.md5").writeText(md5) + + // SHA1 + val sha1 = MessageDigest.getInstance("SHA-1") + .digest(file.readBytes()) + .joinToString("") { "%02x".format(it) } + file("${file.absolutePath}.sha1").writeText(sha1) + } + } + } +} + +// Task to create a bundle zip for Maven Central Portal +val createMavenCentralBundle by tasks.registering(Zip::class) { + group = "publishing" + description = "Creates a bundle zip for Maven Central Portal upload" + + dependsOn("generatePomFileForMavenJavaPublication", "jar", "sourcesJar", "javadocJar", "signMavenJavaPublication", generateChecksums) + + // Ensure signing happens before bundle creation + mustRunAfter("signMavenJavaPublication") + + val groupId = prop("projectGroup")!!.replace(".", "/") + val artifactId = prop("projectArtifactId")!! + val projectVer = project.version.toString() + + // Validate version is not SNAPSHOT for Maven Central + doFirst { + if (projectVer.contains("SNAPSHOT")) { + throw GradleException( + "Cannot publish SNAPSHOT version to Maven Central. " + + "Please change projectVersion in gradle.properties to a release version (e.g., 0.0.1)" + ) + } + } + + archiveFileName.set("$artifactId-$projectVer-bundle.zip") + destinationDirectory.set(layout.buildDirectory.dir("maven-central")) + + // Maven Central expects files in groupId/artifactId/version/ structure + val basePath = "$groupId/$artifactId/$projectVer" + + // Main JAR + checksums + signature + from(tasks.named("jar").get().outputs.files) { + into(basePath) + rename { "$artifactId-$projectVer.jar" } + } + from(tasks.named("jar").get().outputs.files.singleFile.absolutePath + ".md5") { + into(basePath) + rename { "$artifactId-$projectVer.jar.md5" } + } + from(tasks.named("jar").get().outputs.files.singleFile.absolutePath + ".sha1") { + into(basePath) + rename { "$artifactId-$projectVer.jar.sha1" } + } + + // Sources JAR + checksums + signature + from(tasks.named("sourcesJar").get().outputs.files) { + into(basePath) + rename { "$artifactId-$projectVer-sources.jar" } + } + from(tasks.named("sourcesJar").get().outputs.files.singleFile.absolutePath + ".md5") { + into(basePath) + rename { "$artifactId-$projectVer-sources.jar.md5" } + } + from(tasks.named("sourcesJar").get().outputs.files.singleFile.absolutePath + ".sha1") { + into(basePath) + rename { "$artifactId-$projectVer-sources.jar.sha1" } + } + + // Javadoc JAR + checksums + signature + from(tasks.named("javadocJar").get().outputs.files) { + into(basePath) + rename { "$artifactId-$projectVer-javadoc.jar" } + } + from(tasks.named("javadocJar").get().outputs.files.singleFile.absolutePath + ".md5") { + into(basePath) + rename { "$artifactId-$projectVer-javadoc.jar.md5" } + } + from(tasks.named("javadocJar").get().outputs.files.singleFile.absolutePath + ".sha1") { + into(basePath) + rename { "$artifactId-$projectVer-javadoc.jar.sha1" } + } + + // POM + checksums + signature + from(layout.buildDirectory.file("publications/mavenJava/pom-default.xml")) { + into(basePath) + rename { "$artifactId-$projectVer.pom" } + } + from(layout.buildDirectory.file("publications/mavenJava/pom-default.xml").get().asFile.absolutePath + ".md5") { + into(basePath) + rename { "$artifactId-$projectVer.pom.md5" } + } + from(layout.buildDirectory.file("publications/mavenJava/pom-default.xml").get().asFile.absolutePath + ".sha1") { + into(basePath) + rename { "$artifactId-$projectVer.pom.sha1" } + } + + // Signature files - get them from the signing task outputs + doFirst { + val signingTask = tasks.named("signMavenJavaPublication").get() + logger.lifecycle("Signing task outputs: ${signingTask.outputs.files.files}") + } + + // Include signature files generated by the signing plugin + from(tasks.named("signMavenJavaPublication").get().outputs.files) { + into(basePath) + include("*.jar.asc", "pom-default.xml.asc") + exclude("module.json.asc") // Exclude gradle module metadata signature + rename { name -> + // Only rename the POM signature file + // JAR signatures are already correctly named by the signing plugin + if (name == "pom-default.xml.asc") { + "$artifactId-$projectVer.pom.asc" + } else { + name // Keep original name (already correct) + } + } + } +} + +// Task to upload bundle to Maven Central Portal +tasks.register("publishToMavenCentral") { + group = "publishing" + description = "Publishes artifacts to Maven Central Portal" + + // Run publish first to generate signatures, then create bundle + dependsOn("publish") + dependsOn(createMavenCentralBundle) + + // Make sure bundle creation happens after publish + createMavenCentralBundle.get().mustRunAfter("publish") + + doLast { + val username = providers.environmentVariable("MAVEN_CENTRAL_USERNAME").orNull + val password = providers.environmentVariable("MAVEN_CENTRAL_PASSWORD").orNull + val bundleFile = createMavenCentralBundle.get().archiveFile.get().asFile + + require(username != null) { "MAVEN_CENTRAL_USERNAME environment variable must be set" } + require(password != null) { "MAVEN_CENTRAL_PASSWORD environment variable must be set" } + require(bundleFile.exists()) { "Bundle file does not exist: ${bundleFile.absolutePath}" } + + logger.lifecycle("Uploading bundle to Maven Central Portal...") + logger.lifecycle("Bundle: ${bundleFile.absolutePath}") + logger.lifecycle("Size: ${bundleFile.length() / 1024} KB") + + // Use curl for uploading (simple and available on most systems) + exec { + commandLine( + "curl", + "-X", "POST", + "-u", "$username:$password", + "-F", "bundle=@${bundleFile.absolutePath}", + "https://central.sonatype.com/api/v1/publisher/upload?name=${bundleFile.name}&publishingType=AUTOMATIC" + ) + } + + logger.lifecycle("Upload completed. Check https://central.sonatype.com/publishing for status.") + } +}