diff --git a/SonatypeCentralUpload/build.gradle.kts b/SonatypeCentralUpload/build.gradle.kts index 055fcf8..95e3cc1 100644 --- a/SonatypeCentralUpload/build.gradle.kts +++ b/SonatypeCentralUpload/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("com.gradle.plugin-publish") version "1.2.1" } -version = "1.0.0" +version = "1.0.1" group = "cl.franciscosolis" // Set up the publishing plugin diff --git a/SonatypeCentralUpload/src/functionalTest/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadPluginFunctionalTest.kt b/SonatypeCentralUpload/src/functionalTest/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadPluginFunctionalTest.kt index c4cb4ab..22757c8 100644 --- a/SonatypeCentralUpload/src/functionalTest/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadPluginFunctionalTest.kt +++ b/SonatypeCentralUpload/src/functionalTest/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadPluginFunctionalTest.kt @@ -47,6 +47,8 @@ class SonatypeCentralUploadPluginFunctionalTest { rootProject.name = "SonatypeCentralUploadTest" """.trimIndent()) buildFile.writeText(""" + import cl.franciscosolis.sonatypecentralupload.SonatypeCentralUploadTask + plugins { id("cl.franciscosolis.gradledotenv") version "1.0.1" id("cl.franciscosolis.sonatype-central-upload") @@ -55,17 +57,26 @@ class SonatypeCentralUploadPluginFunctionalTest { group = "cl.franciscosolis" version = "$version" - sonatypeCentralUpload { - username = env["SONATYPE_USERNAME"] ?: "" - password = env["SONATYPE_PASSWORD"] ?: "" - - signingKey = env["SIGNING_KEY"] ?: "" - signingKeyPassphrase = env["SIGNING_PASSWORD"] ?: "" - publicKey = env["PUBLIC_KEY"] ?: "" - - archives = files("${mockJar.absolutePath}", "${mockSourcesJar.absolutePath}", "${mockJavadocJar.absolutePath}") + tasks { + register("randomTask") { + doLast { + println("Random task") + } + } - pom = file("${mockPom.absolutePath}") + named("sonatypeCentralUpload") { + dependsOn(named("randomTask")) + username = env["SONATYPE_USERNAME"] ?: "" + password = env["SONATYPE_PASSWORD"] ?: "" + + signingKey = env["SIGNING_KEY"] ?: "" + signingKeyPassphrase = env["SIGNING_PASSWORD"] ?: "" + publicKey = env["PUBLIC_KEY"] ?: "" + + archives = files("${mockJar.absolutePath}", "${mockSourcesJar.absolutePath}", "${mockJavadocJar.absolutePath}") + + pom = file("${mockPom.absolutePath}") + } } """.trimIndent()) diff --git a/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadExtension.kt b/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadExtension.kt index d5e454a..ef24975 100644 --- a/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadExtension.kt +++ b/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadExtension.kt @@ -3,6 +3,7 @@ package cl.franciscosolis.sonatypecentralupload import org.gradle.api.file.FileCollection import org.gradle.api.provider.Property import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional import java.io.File abstract class SonatypeCentralUploadExtension { @@ -13,12 +14,15 @@ abstract class SonatypeCentralUploadExtension { abstract val password: Property @get:Input + @get:Optional abstract val signingKey: Property @get:Input + @get:Optional abstract val signingKeyPassphrase: Property @get:Input + @get:Optional abstract val publicKey: Property @get:Input diff --git a/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadPlugin.kt b/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadPlugin.kt index 6ca094e..ee62ad3 100644 --- a/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadPlugin.kt +++ b/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadPlugin.kt @@ -1,94 +1,19 @@ package cl.franciscosolis.sonatypecentralupload -import cl.franciscosolis.sonatypecentralupload.utils.* import org.gradle.api.Plugin import org.gradle.api.Project -import java.io.File -import java.nio.file.Files class SonatypeCentralUploadPlugin: Plugin { override fun apply(project: Project) { - project.extensions.create("sonatypeCentralUpload", SonatypeCentralUploadExtension::class.java) - project.tasks.register("sonatypeCentralUpload") { task -> - val extension = project.extensions.getByType(SonatypeCentralUploadExtension::class.java) - val groupFolder = "${project.group}".replace('.', '/').lowercase() - val sonatypeCentralUploadDir = project.file(project.layout.buildDirectory.dir("sonatype-central-upload")) - val uploadDir = project.file(project.layout.buildDirectory.dir("${sonatypeCentralUploadDir.path}/$groupFolder/${project.name.lowercase()}/${project.version}")) - if (extension.username.orNull?.isBlank() == true) { - throw IllegalStateException("'username' is empty. A username is required.") - } - - if (extension.password.orNull?.isBlank() == true) { - throw IllegalStateException("'password' is empty. A password is required.") - } - - if (extension.signingKey.orNull?.isBlank() == true) { - throw IllegalStateException("'signingKey' is empty. A signing key is required.") - } - - if (!extension.archives.isPresent || extension.archives.orNull?.isEmpty == true) { - throw IllegalStateException("'archives' is empty. Archives to upload are required.") - } - - if (!extension.pom.isPresent || extension.pom.orNull == null) { - throw IllegalStateException("'pom' is empty. A pom file is required.") - } - - task.doLast { - // Create upload dir - if(!uploadDir.exists()) { - uploadDir.mkdirs() - } - - // Get all artifacts - val artifacts = extension.archives.orNull ?: emptyList() - - // Copy artifacts to upload dir - for(artifact in artifacts) { - if(!artifact.nameWithoutExtension.startsWith("${project.name.lowercase()}-${project.version}")) { - throw IllegalStateException("Artifact name '${artifact.name}' does not match or does not start with project name '${project.name.lowercase()}-${project.version}'.") - } - val artifactFile = artifact.toPath() - val uploadFile = uploadDir.toPath().resolve(artifactFile.fileName) - Files.copy(artifactFile, uploadFile) - } - - val pomFile = extension.pom.orNull ?: throw NullPointerException("Pom file is null.") - Files.copy(pomFile.toPath(), uploadDir.toPath().resolve("${project.name.lowercase()}-${project.version}.pom")) - - if (extension.publicKey.orNull?.isNotBlank() == true) { - val publicKey = extension.publicKey.orNull ?: "" - val pkToDistribute = if(publicKey.startsWith("-----BEGIN PGP PUBLIC KEY BLOCK-----")) { - publicKey.replace("\\n", "\n") - } else if (File(publicKey).exists()) { - File(uploadDir, "public.key").readText().replace("\\n", "\n") - } else { - throw IllegalStateException("'publicKey' is not a file or a key block.") - } - - sendKeyToServer(pkToDistribute) - } - - // Generate signatures for all files - for(file in uploadDir.listFiles() ?: emptyArray()) { - signFile( - file = file, - signingKey = extension.signingKey.orNull ?: "", - signingPassword = extension.signingKeyPassphrase.orNull ?: "" - ) - } - - // Generate checksums for all files (filter out .asc files) - for (file in (uploadDir.listFiles() ?: emptyArray()).filter { it.extension != "asc" }) { - generateChecksums(file) - } - - // Now we need to zip all the contents of 'sonatype-central-upload' - val zipFile = File(sonatypeCentralUploadDir, "${project.name.lowercase()}-${project.version}.zip") - zipFolder(File(sonatypeCentralUploadDir, groupFolder.split('/').first()), zipFile) - - initPublishingProcess(zipFile, extension.username.orNull ?: "", extension.password.orNull ?: "") - } + val extension: SonatypeCentralUploadExtension = project.extensions.create("sonatypeCentralUpload", SonatypeCentralUploadExtension::class.java) + project.tasks.register("sonatypeCentralUpload", SonatypeCentralUploadTask::class.java) { task -> + task.username.set(extension.username) + task.password.set(extension.password) + task.signingKey.set(extension.signingKey) + task.signingKeyPassphrase.set(extension.signingKeyPassphrase) + task.publicKey.set(extension.publicKey) + task.archives.set(extension.archives) + task.pom.set(extension.pom) } } } diff --git a/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadTask.kt b/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadTask.kt new file mode 100644 index 0000000..89aaa32 --- /dev/null +++ b/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/SonatypeCentralUploadTask.kt @@ -0,0 +1,122 @@ +package cl.franciscosolis.sonatypecentralupload + +import cl.franciscosolis.sonatypecentralupload.utils.* +import org.gradle.api.DefaultTask +import org.gradle.api.file.FileCollection +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.nio.file.Files + + +abstract class SonatypeCentralUploadTask: DefaultTask() { + + @get:Input + abstract val username: Property + + @get:Input + abstract val password: Property + + @get:Input + @get:Optional + abstract val signingKey: Property + + @get:Input + @get:Optional + abstract val signingKeyPassphrase: Property + + @get:Input + @get:Optional + abstract val publicKey: Property + + @get:Input + abstract val archives: Property + + @get:Input + abstract val pom: Property + + @TaskAction + fun run() { + val groupFolder = "${project.group}".replace('.', '/').lowercase() + val sonatypeCentralUploadDir = project.file(project.layout.buildDirectory.dir("sonatype-central-upload")) + val uploadDir = project.file(project.layout.buildDirectory.dir("${sonatypeCentralUploadDir.path}/$groupFolder/${project.name.lowercase()}/${project.version}")) + if (username.orNull?.isBlank() == true) { + throw IllegalStateException("'username' is empty. A username is required.") + } + + if (password.orNull?.isBlank() == true) { + throw IllegalStateException("'password' is empty. A password is required.") + } + + if (signingKey.orNull?.isBlank() == true) { + throw IllegalStateException("'signingKey' is empty. A signing key is required.") + } + + if (!archives.isPresent || archives.orNull?.isEmpty == true) { + throw IllegalStateException("'archives' is empty. Archives to upload are required.") + } + + if (!pom.isPresent || pom.orNull == null) { + throw IllegalStateException("'pom' is empty. A pom file is required.") + } + + // Create upload dir + if(!uploadDir.exists()) { + uploadDir.mkdirs() + } + + // Get all artifacts + val artifacts = archives.orNull ?: emptyList() + + // Copy artifacts to upload dir + for(artifact in artifacts) { + if(!artifact.nameWithoutExtension.startsWith("${project.name.lowercase()}-${project.version}")) { + throw IllegalStateException("Artifact name '${artifact.name}' does not match or does not start with project name '${project.name.lowercase()}-${project.version}'.") + } + val artifactFile = artifact.toPath() + val uploadFile = uploadDir.toPath().resolve(artifactFile.fileName) + Files.copy(artifactFile, uploadFile) + } + + val pomFile = pom.orNull ?: throw NullPointerException("Pom file is null.") + Files.copy(pomFile.toPath(), uploadDir.toPath().resolve("${project.name.lowercase()}-${project.version}.pom")) + + if (publicKey.orNull?.isNotBlank() == true) { + val publicKey = publicKey.orNull ?: "" + val pkToDistribute = if(publicKey.startsWith("-----BEGIN PGP") && publicKey.contains("KEY BLOCK-----")) { + publicKey.replace("\\n", "\n") + } else if (File(publicKey).exists()) { + File(uploadDir, "public.key").readText().replace("\\n", "\n") + } else { + throw IllegalStateException("'publicKey' is not a file or a key block.") + } + + sendKeyToServer(pkToDistribute) + } + + // Generate signatures for all files + for(file in uploadDir.listFiles() ?: emptyArray()) { + signFile( + file = file, + signingKey = signingKey.orNull ?: "", + signingPassword = signingKeyPassphrase.orNull ?: "" + ) + } + + // Generate checksums for all files (filter out .asc files) + for (file in (uploadDir.listFiles() ?: emptyArray()).filter { it.extension != "asc" }) { + generateChecksums(file) + } + + // Now we need to zip all the contents of 'sonatype-central-upload' + val zipFile = File(sonatypeCentralUploadDir, "${project.name.lowercase()}-${project.version}.zip") + zipFolder(File(sonatypeCentralUploadDir, groupFolder.split('/').first()), zipFile) + + initPublishingProcess(zipFile, username.orNull ?: "", password.orNull ?: "") + } + + + +} \ No newline at end of file diff --git a/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/utils/SigningUtil.kt b/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/utils/SigningUtil.kt index 7ef56e0..d53057d 100644 --- a/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/utils/SigningUtil.kt +++ b/SonatypeCentralUpload/src/main/kotlin/cl/franciscosolis/sonatypecentralupload/utils/SigningUtil.kt @@ -16,7 +16,7 @@ import javax.net.ssl.HttpsURLConnection */ fun signFile(file: File, signingKey: String, signingPassword: String): File? = try { val signedFile = File(file.parentFile, "${file.name}.asc") - val keyBytes = if(signingKey.contains("PGP PRIVATE KEY BLOCK")) { + val keyBytes = if(signingKey.startsWith("-----BEGIN PGP") && signingKey.contains("KEY BLOCK-----")) { signingKey.replace("\\n", "\n") .toByteArray() } else if (File(signingKey).exists()) { @@ -49,7 +49,7 @@ fun signFile(file: File, signingKey: String, signingPassword: String): File? = t * Sends the given public key to the key server. * @param key The public key to send. */ -fun sendKeyToServer(key: String) { +fun sendKeyToServer(key: String) = try { val url = URL("https://keyserver.ubuntu.com/pks/add") val connection = url.openConnection() as HttpsURLConnection connection.requestMethod = "POST" @@ -68,4 +68,11 @@ fun sendKeyToServer(key: String) { println("Response message: ${connection.responseMessage}") println("Response body: ${connection.inputStream.bufferedReader().readText()}") } + + connection.disconnect() +} catch (e: Exception) { + if(System.getenv("SONATYPECENTRALUPLOAD_DEBUG") != null) { + e.printStackTrace() + } + System.err.println("Failed to send key to server.") } \ No newline at end of file