From 017df58da662eb26235085e76830238f64c04559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Prante?= Date: Mon, 22 Jul 2024 15:32:59 +0200 Subject: [PATCH] move RPM plugin package, add new Sonatype Publish plugin --- build.gradle | 6 +- gradle-plugin-rpm/build.gradle | 2 +- .../xbib/gradle/plugin/{ => rpm}/Rpm.groovy | 2 +- .../plugin/{ => rpm}/RpmCopyAction.groovy | 2 +- .../gradle/plugin/{ => rpm}/RpmPlugin.groovy | 2 +- .../plugin/{ => rpm/test}/RpmFullTest.groovy | 3 +- .../test}/RpmPluginIntegrationTest.groovy | 2 +- .../{ => rpm/test}/RpmScriptTest.groovy | 3 +- .../plugin/{ => rpm/test}/RpmSignTest.groovy | 3 +- .../{ => rpm/test}/RpmSimpleTest.groovy | 5 +- .../gradle/plugin/{ => rpm/test}/changelog | 0 .../plugin/{ => rpm/test}/preinstall.sh | 0 gradle-plugin-sonatype-publish/NOTICE.txt | 0 gradle-plugin-sonatype-publish/build.gradle | 42 ++++++++ .../gradle.properties | 2 + .../DefaultSonatypePublishExtension.java | 62 +++++++++++ .../sonatype/publish/DeploymentStatus.java | 5 + .../sonatype/publish/PublishingType.java | 18 ++++ .../publish/SonatypePublishExtension.java | 52 +++++++++ .../publish/SonatypePublishHelper.java | 100 ++++++++++++++++++ .../publish/SonatypePublishPlugin.java | 21 ++++ .../sonatype/publish/SonatypePublishTask.java | 87 +++++++++++++++ .../SonatypePublishPluginIntegrationTest.java | 77 ++++++++++++++ gradle.properties | 2 - gradle/test/junit5.gradle | 1 + settings.gradle | 3 +- 26 files changed, 484 insertions(+), 18 deletions(-) rename gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/{ => rpm}/Rpm.groovy (99%) rename gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/{ => rpm}/RpmCopyAction.groovy (99%) rename gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/{ => rpm}/RpmPlugin.groovy (88%) rename gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/{ => rpm/test}/RpmFullTest.groovy (99%) rename gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/{ => rpm/test}/RpmPluginIntegrationTest.groovy (97%) rename gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/{ => rpm/test}/RpmScriptTest.groovy (98%) rename gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/{ => rpm/test}/RpmSignTest.groovy (96%) rename gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/{ => rpm/test}/RpmSimpleTest.groovy (97%) rename gradle-plugin-rpm/src/test/resources/org/xbib/gradle/plugin/{ => rpm/test}/changelog (100%) rename gradle-plugin-rpm/src/test/resources/org/xbib/gradle/plugin/{ => rpm/test}/preinstall.sh (100%) create mode 100644 gradle-plugin-sonatype-publish/NOTICE.txt create mode 100644 gradle-plugin-sonatype-publish/build.gradle create mode 100644 gradle-plugin-sonatype-publish/gradle.properties create mode 100644 gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/DefaultSonatypePublishExtension.java create mode 100644 gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/DeploymentStatus.java create mode 100644 gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/PublishingType.java create mode 100644 gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishExtension.java create mode 100644 gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishHelper.java create mode 100644 gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishPlugin.java create mode 100644 gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishTask.java create mode 100644 gradle-plugin-sonatype-publish/src/test/java/org/xbib/gradle/plugin/sonatype/publish/test/SonatypePublishPluginIntegrationTest.java diff --git a/build.gradle b/build.gradle index 16a249e..2a6acf4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,14 @@ -group = 'org.xbib.gradle.plugin' - wrapper { gradleVersion = libs.versions.gradle.get() distributionType = Wrapper.DistributionType.BIN } ext { - user = 'jprante' - name = 'gradle-plugins' + name = rootProject.name description = 'Gradle plugins' inceptionYear = '2021' + user = 'jprante' url = 'https://github.com/' + user + '/' + name scmUrl = 'https://github.com/' + user + '/' + name scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git' diff --git a/gradle-plugin-rpm/build.gradle b/gradle-plugin-rpm/build.gradle index ae02387..98b87e9 100644 --- a/gradle-plugin-rpm/build.gradle +++ b/gradle-plugin-rpm/build.gradle @@ -35,7 +35,7 @@ if (project.hasProperty('gradle.publish.key')) { version = project.version description = 'Java implementation for RPM packaging' displayName = 'Java implementation for RPM packaging' - implementationClass = 'org.xbib.gradle.plugin.RpmPlugin' + implementationClass = 'org.xbib.gradle.plugin.rpm.RpmPlugin' tags.set(['rpm']) } } diff --git a/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/Rpm.groovy b/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/rpm/Rpm.groovy similarity index 99% rename from gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/Rpm.groovy rename to gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/rpm/Rpm.groovy index 241b8d2..54a27ab 100644 --- a/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/Rpm.groovy +++ b/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/rpm/Rpm.groovy @@ -1,4 +1,4 @@ -package org.xbib.gradle.plugin +package org.xbib.gradle.plugin.rpm import org.gradle.api.internal.file.copy.CopyAction import org.gradle.api.tasks.Input diff --git a/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/RpmCopyAction.groovy b/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/rpm/RpmCopyAction.groovy similarity index 99% rename from gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/RpmCopyAction.groovy rename to gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/rpm/RpmCopyAction.groovy index 8f16538..fea75ad 100644 --- a/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/RpmCopyAction.groovy +++ b/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/rpm/RpmCopyAction.groovy @@ -1,4 +1,4 @@ -package org.xbib.gradle.plugin +package org.xbib.gradle.plugin.rpm import groovy.util.logging.Log import org.gradle.api.Project diff --git a/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/RpmPlugin.groovy b/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/rpm/RpmPlugin.groovy similarity index 88% rename from gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/RpmPlugin.groovy rename to gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/rpm/RpmPlugin.groovy index f78f877..f9191ed 100644 --- a/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/RpmPlugin.groovy +++ b/gradle-plugin-rpm/src/main/groovy/org/xbib/gradle/plugin/rpm/RpmPlugin.groovy @@ -1,4 +1,4 @@ -package org.xbib.gradle.plugin +package org.xbib.gradle.plugin.rpm import org.gradle.api.Plugin import org.gradle.api.Project diff --git a/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmFullTest.groovy b/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmFullTest.groovy similarity index 99% rename from gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmFullTest.groovy rename to gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmFullTest.groovy index 4bc40ab..02d87ab 100644 --- a/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmFullTest.groovy +++ b/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmFullTest.groovy @@ -1,9 +1,10 @@ -package org.xbib.gradle.plugin +package org.xbib.gradle.plugin.rpm.test import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.xbib.gradle.plugin.rpm.Rpm import org.xbib.rpm.RpmReader import org.xbib.rpm.RpmReaderResult import org.xbib.rpm.format.Format diff --git a/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmPluginIntegrationTest.groovy b/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmPluginIntegrationTest.groovy similarity index 97% rename from gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmPluginIntegrationTest.groovy rename to gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmPluginIntegrationTest.groovy index 3360df8..0ced2c4 100644 --- a/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmPluginIntegrationTest.groovy +++ b/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmPluginIntegrationTest.groovy @@ -1,4 +1,4 @@ -package org.xbib.gradle.plugin +package org.xbib.gradle.plugin.rpm.test import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner diff --git a/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmScriptTest.groovy b/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmScriptTest.groovy similarity index 98% rename from gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmScriptTest.groovy rename to gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmScriptTest.groovy index 6fe77d3..5209d1d 100644 --- a/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmScriptTest.groovy +++ b/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmScriptTest.groovy @@ -1,9 +1,10 @@ -package org.xbib.gradle.plugin +package org.xbib.gradle.plugin.rpm.test import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.xbib.gradle.plugin.rpm.Rpm import org.xbib.rpm.RpmReader import org.xbib.rpm.changelog.ChangelogParser import org.xbib.rpm.format.Format diff --git a/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmSignTest.groovy b/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmSignTest.groovy similarity index 96% rename from gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmSignTest.groovy rename to gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmSignTest.groovy index 907e823..0b95972 100644 --- a/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmSignTest.groovy +++ b/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmSignTest.groovy @@ -1,9 +1,10 @@ -package org.xbib.gradle.plugin +package org.xbib.gradle.plugin.rpm.test import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.xbib.gradle.plugin.rpm.Rpm import org.xbib.rpm.RpmReader import org.xbib.rpm.signature.SignatureTag import java.nio.file.Paths diff --git a/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmSimpleTest.groovy b/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmSimpleTest.groovy similarity index 97% rename from gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmSimpleTest.groovy rename to gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmSimpleTest.groovy index 1716dc3..d8c1e83 100644 --- a/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/RpmSimpleTest.groovy +++ b/gradle-plugin-rpm/src/test/groovy/org/xbib/gradle/plugin/rpm/test/RpmSimpleTest.groovy @@ -1,17 +1,16 @@ -package org.xbib.gradle.plugin +package org.xbib.gradle.plugin.rpm.test import groovy.util.logging.Log import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test +import org.xbib.gradle.plugin.rpm.Rpm import org.xbib.rpm.RpmReaderResult import org.xbib.rpm.RpmReader import org.xbib.rpm.format.Format import java.nio.file.Paths -import java.util.logging.Level -import java.util.logging.Logger import static org.hamcrest.MatcherAssert.assertThat import static org.hamcrest.CoreMatchers.* diff --git a/gradle-plugin-rpm/src/test/resources/org/xbib/gradle/plugin/changelog b/gradle-plugin-rpm/src/test/resources/org/xbib/gradle/plugin/rpm/test/changelog similarity index 100% rename from gradle-plugin-rpm/src/test/resources/org/xbib/gradle/plugin/changelog rename to gradle-plugin-rpm/src/test/resources/org/xbib/gradle/plugin/rpm/test/changelog diff --git a/gradle-plugin-rpm/src/test/resources/org/xbib/gradle/plugin/preinstall.sh b/gradle-plugin-rpm/src/test/resources/org/xbib/gradle/plugin/rpm/test/preinstall.sh similarity index 100% rename from gradle-plugin-rpm/src/test/resources/org/xbib/gradle/plugin/preinstall.sh rename to gradle-plugin-rpm/src/test/resources/org/xbib/gradle/plugin/rpm/test/preinstall.sh diff --git a/gradle-plugin-sonatype-publish/NOTICE.txt b/gradle-plugin-sonatype-publish/NOTICE.txt new file mode 100644 index 0000000..e69de29 diff --git a/gradle-plugin-sonatype-publish/build.gradle b/gradle-plugin-sonatype-publish/build.gradle new file mode 100644 index 0000000..9a93167 --- /dev/null +++ b/gradle-plugin-sonatype-publish/build.gradle @@ -0,0 +1,42 @@ +plugins { + id 'java-gradle-plugin' + alias(libs.plugins.publish) +} + +apply plugin: 'java-gradle-plugin' +apply plugin: 'com.gradle.plugin-publish' + +apply from: rootProject.file('gradle/compile/java.gradle') +apply from: rootProject.file('gradle/test/junit5.gradle') + +dependencies { + api gradleApi() + testImplementation gradleTestKit() +} + +publishing { + repositories { + maven { + name = 'localRepository' + url = 'build/local-repository' + } + } +} + +if (project.hasProperty('gradle.publish.key')) { + gradlePlugin { + website = 'https://xbib.org/joerg/gradle-plugins/src/branch/main/gradle-plugin-sonatype-publish' + vcsUrl = 'https://xbib.org/joerg/gradle-plugins' + plugins { + sonatypePublishPlugin { + id = 'org.xbib.gradle.plugin.sonatype.publish' + group = project.group + version = project.version + description = 'Sonatype publishing plugin' + displayName = 'Sonatype publishing plugin' + implementationClass = 'org.xbib.gradle.plugin.sonatype.publish.SonatypePublishPlugin' + tags.set(['sonatype', 'publish', 'mavencentral']) + } + } + } +} diff --git a/gradle-plugin-sonatype-publish/gradle.properties b/gradle-plugin-sonatype-publish/gradle.properties new file mode 100644 index 0000000..4fe7bf0 --- /dev/null +++ b/gradle-plugin-sonatype-publish/gradle.properties @@ -0,0 +1,2 @@ +name = gradle-plugin-sonatype-publish +version = 1.0.0 diff --git a/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/DefaultSonatypePublishExtension.java b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/DefaultSonatypePublishExtension.java new file mode 100644 index 0000000..f383b6c --- /dev/null +++ b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/DefaultSonatypePublishExtension.java @@ -0,0 +1,62 @@ +package org.xbib.gradle.plugin.sonatype.publish; + +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.Property; + +import static org.xbib.gradle.plugin.sonatype.publish.SonatypePublishHelper.STATUS_URL; +import static org.xbib.gradle.plugin.sonatype.publish.SonatypePublishHelper.UPLOAD_URL; + +public class DefaultSonatypePublishExtension implements SonatypePublishExtension { + + private static final Integer MAX_WAIT_SECONDS = 60; + + private final Property uploadUrl; + + private final Property publishingType; + + private final Property statusUrl; + + private final Property authToken; + + private final DirectoryProperty repoDir; + + private final Property maxWait; + + public DefaultSonatypePublishExtension(ObjectFactory objectFactory) { + uploadUrl = objectFactory.property(String.class).convention(UPLOAD_URL); + statusUrl = objectFactory.property(String.class).convention(STATUS_URL); + publishingType = objectFactory.property(String.class).convention(PublishingType.AUTOMATIC.name()); + authToken = objectFactory.property(String.class); + repoDir = objectFactory.directoryProperty(); + maxWait = objectFactory.property(Integer.class).convention(MAX_WAIT_SECONDS); + } + + @Override + public Property getUploadUrl() { + return uploadUrl; + } + + @Override + public Property getPublishingType() { return publishingType; } + + @Override + public Property getStatusUrl() { + return statusUrl; + } + + @Override + public Property getAuthToken() { + return authToken; + } + + @Override + public DirectoryProperty getRepoDir() { + return repoDir; + } + + @Override + public Property getMaxWait() { + return maxWait; + } +} diff --git a/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/DeploymentStatus.java b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/DeploymentStatus.java new file mode 100644 index 0000000..834f5c7 --- /dev/null +++ b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/DeploymentStatus.java @@ -0,0 +1,5 @@ +package org.xbib.gradle.plugin.sonatype.publish; + +enum DeploymentStatus { + FAILED, PUBLISHING, PUBLISHED, PENDING, VALIDATED; +} diff --git a/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/PublishingType.java b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/PublishingType.java new file mode 100644 index 0000000..bb53b6f --- /dev/null +++ b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/PublishingType.java @@ -0,0 +1,18 @@ +package org.xbib.gradle.plugin.sonatype.publish; + +/** + * Determines whether to publish the artifact immediately, or to hold it back behind Publish button + * in the Sonatype Central Portal panel. + */ +public enum PublishingType { + /** + * The Publish button will be pressed for you immediately after a successful validation. This is + * the default. + */ + AUTOMATIC, + + /** + * The Publish button will be there for you to press in the Sonatype Central Portal panel. + */ + USER_MANAGED, +} diff --git a/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishExtension.java b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishExtension.java new file mode 100644 index 0000000..6579db0 --- /dev/null +++ b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishExtension.java @@ -0,0 +1,52 @@ +package org.xbib.gradle.plugin.sonatype.publish; + +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.provider.Property; + +public interface SonatypePublishExtension { + + /** + * Upload URL for uploading a deployment bundle. + * + * @return URL string + */ + Property getUploadUrl(); + + /** + * Whether to publish automatically or manually after a successful upload. + * + * @return Publishing type + */ + Property getPublishingType(); + + /** + * The URL for retrieving status of a deployment. + * + * @return URL string + */ + Property getStatusUrl(); + + /** + * The authorization toke for calling central portal APIs + * + * @return Token string + */ + Property getAuthToken(); + + /** + * The repository directory for zipping the bundle. + * It is usually a local directory published by the Maven publish plugin. + * + * @return Repository directory + */ + DirectoryProperty getRepoDir(); + + /** + * Max wait time for status API to get 'PUBLISHING' or 'PUBLISHED' status when the + * publishing type is 'AUTOMATIC', or additionally 'VALIDATED' when the publishing type is + * 'USER_MANAGED'. + * + * @return Duration in seconds + */ + Property getMaxWait(); +} \ No newline at end of file diff --git a/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishHelper.java b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishHelper.java new file mode 100644 index 0000000..7d3c2f5 --- /dev/null +++ b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishHelper.java @@ -0,0 +1,100 @@ +package org.xbib.gradle.plugin.sonatype.publish; + +import groovy.json.JsonSlurper; +import org.gradle.api.GradleException; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpRequest.BodyPublisher; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +import java.nio.file.Path; +import java.util.Map; +import java.util.UUID; + +import static java.net.http.HttpRequest.BodyPublishers.noBody; +import static java.net.http.HttpRequest.BodyPublishers.ofFile; +import static java.net.http.HttpRequest.BodyPublishers.ofString; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class SonatypePublishHelper { + + private static final Logger logger = Logging.getLogger(SonatypePublishHelper.class); + + private static final String CRLF = "\r\n"; + + static final String UPLOAD_URL = "https://central.sonatype.com/api/v1/publisher/upload"; + + static final String STATUS_URL = "https://central.sonatype.com/api/v1/publisher/status"; + + static final String CHECKING_URL = "https://central.sonatype.com/publishing/deployments"; + + private final HttpClient httpClient; + + public SonatypePublishHelper() { + httpClient = HttpClient.newHttpClient(); + } + + public String uploadBundle(String url, + String publishingType, + String token, + Path uploadFile) throws IOException, InterruptedException { + String boundary = UUID.randomUUID().toString().replace("-", ""); + BodyPublisher filePartPublisher = getFilePartPublisher(boundary, uploadFile); + HttpRequest request = HttpRequest.newBuilder(URI.create(url + "?publishingType=" + publishingType)) + .header("Authorization", "Bearer " + token) + .header("Content-Type", "multipart/form-data; boundary=" + boundary) + .POST(filePartPublisher) + .build(); + logger.info("request = " + request); + HttpResponse response = httpClient.send(request, BodyHandlers.ofString(UTF_8)); + int statusCode = response.statusCode(); + String body = response.body(); + if (statusCode == 201) { + logger.lifecycle("Upload success, response body: {}", body); + return body; + } else { + throw new GradleException(String.format("upload failed, status code: %d, response body: %s", statusCode, body)); + } + } + + public String getDeploymentStatus(String url, + String token, + String deploymentId) throws IOException, InterruptedException { + HttpRequest request = HttpRequest.newBuilder(URI.create(url + "?id=" + deploymentId)) + .header("Authorization", "Bearer " + token) + .POST(noBody()) + .build(); + logger.info("request = " + request); + HttpResponse response = httpClient.send(request, BodyHandlers.ofString(UTF_8)); + int statusCode = response.statusCode(); + String body = response.body(); + if (statusCode == 200) { + logger.lifecycle("checking deployment status, response body: {}", body); + @SuppressWarnings("unchecked") + Map jsonObject = (Map) new JsonSlurper().parseText(body); + return (String) jsonObject.get("deploymentState"); + } else { + throw new GradleException(String.format("status failed, status code: %d, response body: %s", statusCode, body)); + } + } + + private static BodyPublisher getFilePartPublisher(String boundary, Path file) throws FileNotFoundException { + String boundaryStart = CRLF + "--" + boundary + CRLF; + String boundaryEnd = CRLF + "--" + boundary + "--"; + String partMeta = getFilePartMeta(boundaryStart, file.getFileName()); + return HttpRequest.BodyPublishers.concat(ofString(partMeta), ofFile(file), ofString(boundaryEnd)); + } + + private static String getFilePartMeta(String boundaryStart, Path fileName) { + return boundaryStart + + "Content-Disposition: form-data; name=\"bundle\"; filename=\"" + fileName + "\"" + CRLF + + "Content-Type: application/octet-stream" + CRLF + + CRLF; + } +} diff --git a/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishPlugin.java b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishPlugin.java new file mode 100644 index 0000000..ae0df32 --- /dev/null +++ b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishPlugin.java @@ -0,0 +1,21 @@ +package org.xbib.gradle.plugin.sonatype.publish; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.bundling.Zip; + +public class SonatypePublishPlugin implements Plugin { + + @Override + public void apply(Project project) { + SonatypePublishExtension extension = project.getExtensions() + .create(SonatypePublishExtension.class, "sonatypePublish", DefaultSonatypePublishExtension.class, project.getObjects()); + Zip zipTask = project.getTasks().register("bundleForPublish", Zip.class).get(); + zipTask.setGroup("publishing"); + zipTask.from(extension.getRepoDir()); + SonatypePublishTask sonatypePublishTask = project.getTasks().register("publishToSonatype", SonatypePublishTask.class).get(); + sonatypePublishTask.setGroup("publishing"); + sonatypePublishTask.dependsOn(zipTask); + sonatypePublishTask.setUploadFile(zipTask.getArchiveFile()); + } +} diff --git a/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishTask.java b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishTask.java new file mode 100644 index 0000000..798d8d7 --- /dev/null +++ b/gradle-plugin-sonatype-publish/src/main/java/org/xbib/gradle/plugin/sonatype/publish/SonatypePublishTask.java @@ -0,0 +1,87 @@ +package org.xbib.gradle.plugin.sonatype.publish; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.InvalidUserDataException; +import org.gradle.api.file.RegularFile; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.TaskAction; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import static org.xbib.gradle.plugin.sonatype.publish.DeploymentStatus.FAILED; +import static org.xbib.gradle.plugin.sonatype.publish.DeploymentStatus.PUBLISHED; +import static org.xbib.gradle.plugin.sonatype.publish.DeploymentStatus.PUBLISHING; +import static org.xbib.gradle.plugin.sonatype.publish.DeploymentStatus.VALIDATED; +import static org.xbib.gradle.plugin.sonatype.publish.PublishingType.USER_MANAGED; +import static org.xbib.gradle.plugin.sonatype.publish.SonatypePublishHelper.CHECKING_URL; + +public abstract class SonatypePublishTask extends DefaultTask { + + private final SonatypePublishExtension extension; + + private final SonatypePublishHelper sonatypePublishHelper; + + @SuppressWarnings("this-escape") + public SonatypePublishTask() { + this.sonatypePublishHelper = new SonatypePublishHelper(); + SonatypePublishExtension extension = getProject().getExtensions().findByType(SonatypePublishExtension.class); + if (extension == null) { + extension = getProject().getExtensions().create(SonatypePublishExtension.class, "sonatypePublish", + DefaultSonatypePublishExtension.class, getProject().getObjects()); + } + this.extension = Objects.requireNonNull(extension); + } + + @TaskAction + public void executeTask() throws InterruptedException, IOException { + if (!extension.getAuthToken().isPresent()) { + throw new InvalidUserDataException("auth token is not provided for Sonatype publishing"); + } + RegularFileProperty regularFileProperty = getProject().getObjects().fileProperty(); + if (!regularFileProperty.isPresent()) { + throw new InvalidUserDataException("no upload file"); + } + PublishingType type = PublishingType.valueOf(extension.getPublishingType().get().toUpperCase(Locale.ROOT)); + Path deployment = regularFileProperty.get().getAsFile().toPath(); + getLogger().lifecycle("Sonatype publishing deployment = {}", deployment); + String deploymentId = sonatypePublishHelper.uploadBundle(extension.getUploadUrl().get(), + extension.getPublishingType().get(), extension.getAuthToken().get(), deployment); + getLogger().lifecycle("Sonatype publishing deployment ID = {}", deploymentId); + int seconds = 10; + int count = 0; + int checkCount = extension.getMaxWait().get() / seconds; + checkCount = checkCount <= 0 ? 1 : checkCount; + while (count < checkCount) { + TimeUnit.SECONDS.sleep(seconds); + DeploymentStatus deploymentStatus = DeploymentStatus.valueOf(sonatypePublishHelper.getDeploymentStatus(extension.getStatusUrl().get(), + extension.getAuthToken().get(), deploymentId).toUpperCase(Locale.ROOT)); + boolean success = (type.equals(USER_MANAGED) && VALIDATED.equals(deploymentStatus)) + || PUBLISHING.equals(deploymentStatus) + || PUBLISHED.equals(deploymentStatus); + if (success) { + getLogger().lifecycle("Sonatype publishing successful. Status = {}", deploymentStatus); + return; + } + else if (FAILED.equals(deploymentStatus)) { + throw new GradleException(String.format("Deployment failed: %s, please visit %s and check your deployment", + deploymentStatus.name(), CHECKING_URL)); + } else { + ++count; + } + if (count == checkCount) { + throw new GradleException(String.format("Deployment timed out, status is: %s, please visit %s check your deployment", + deploymentStatus.name(), CHECKING_URL)); + } + } + } + + public void setUploadFile(Provider regularFile) { + getProject().getObjects().fileProperty().value(regularFile); + } +} diff --git a/gradle-plugin-sonatype-publish/src/test/java/org/xbib/gradle/plugin/sonatype/publish/test/SonatypePublishPluginIntegrationTest.java b/gradle-plugin-sonatype-publish/src/test/java/org/xbib/gradle/plugin/sonatype/publish/test/SonatypePublishPluginIntegrationTest.java new file mode 100644 index 0000000..99ee655 --- /dev/null +++ b/gradle-plugin-sonatype-publish/src/test/java/org/xbib/gradle/plugin/sonatype/publish/test/SonatypePublishPluginIntegrationTest.java @@ -0,0 +1,77 @@ +package org.xbib.gradle.plugin.sonatype.publish.test; + +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.BuildTask; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.gradle.testkit.runner.TaskOutcome.FAILED; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class SonatypePublishPluginIntegrationTest { + + @TempDir + Path testProjectDir; + + private Path settingsFile; + + private Path buildFile; + + @BeforeEach + public void setup() { + settingsFile = testProjectDir.resolve("settings.gradle"); + buildFile = testProjectDir.resolve("build.gradle"); + } + + @Test + public void testSonatypePublish() throws IOException { + writeFile(settingsFile, "rootProject.name = 'sonatype-publish-test'"); + String buildFileContent = """ +plugins { + id 'org.xbib.gradle.plugin.sonatype.publish' +} + +import org.xbib.gradle.plugin.sonatype.publish.SonatypePublishTask + +apply plugin: 'java-library' +apply plugin: 'maven-publish' +apply plugin: 'signing' +apply plugin: 'org.xbib.gradle.plugin.sonatype.publish' + +sonatypePublish { + uploadUrl = 'http://localhost' + repoDir = layout.buildDirectory.dir('repos/bundles') + authToken = 'dummy' + publishingType = 'AUTOMATIC' + maxWait = 10 +} + +"""; + writeFile(buildFile, buildFileContent); + assertThrows(Exception.class, ()-> { + BuildResult buildResult = GradleRunner.create() + .withProjectDir(testProjectDir.toFile()) + .withArguments(":publish", ":publishToSonatype") + .withPluginClasspath() + .forwardOutput() + .build(); + BuildTask buildTask = buildResult.task(":publishToSonatype"); + // "no upload file" + assertEquals(FAILED, buildTask.getOutcome()); + }); + } + + private void writeFile(Path destination, String content) throws IOException { + try (BufferedWriter output = Files.newBufferedWriter(destination)) { + output.write(content); + } + } +} diff --git a/gradle.properties b/gradle.properties index 986011a..b2b7136 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,3 @@ group = org.xbib.gradle.plugin name = gradle-plugins - -# version reflects gradle version plus a patch level version version = 8.7.0 diff --git a/gradle/test/junit5.gradle b/gradle/test/junit5.gradle index 9e68ad0..16c78f5 100644 --- a/gradle/test/junit5.gradle +++ b/gradle/test/junit5.gradle @@ -13,6 +13,7 @@ test { failFast = true testLogging { events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' + showStandardStreams = true } afterSuite { desc, result -> if (!desc.parent) { diff --git a/settings.gradle b/settings.gradle index e94706a..2afceea 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,7 +9,7 @@ dependencyResolutionManagement { // Attention: it is impossible to develop a gradle plugin with groovy 4! // The gradle plugin publish plugin enforces java-gradle-plugin, // and java-gradle-plugin enforces the embedded groovy of gradle on the compile classpath. - // we keep this here as reference when Gradle switsches to Groovy 4+ + // we keep this here as reference when Gradle switches to Groovy 4+ //library('groovy-bom', 'org.apache.groovy', 'groovy-bom').versionRef('groovy') //library('groovy', 'org.apache.groovy', 'groovy').versionRef('groovy') library('asm', 'org.ow2.asm', 'asm').versionRef('asm') @@ -57,3 +57,4 @@ include 'gradle-plugin-jlink' include 'gradle-plugin-jpackage' include 'gradle-plugin-rpm' include 'gradle-plugin-shadow' +include 'gradle-plugin-sonatype-publish'