diff --git a/README.md b/README.md index e215eae4..4d1ba0a2 100644 --- a/README.md +++ b/README.md @@ -20,61 +20,54 @@ and preparing tasks to upload, close, and release the artifact. This plugin supports both targets that use Sonatype Nexus (such as Maven Central) and targets that do not, such as GitHub Packages. -### Provided tasks +## Configuration -* `sourcesJar`: a `Jar` task preconfigured to collect and pack `allSource` from the `main` source set -* `javadocJar`: a `Jar` task preconfigured to - 1. Detect if a javadoc tasks exists, and in case depend on it, and pack its output folder - 2. Detect if a dokkaJavadoc tasks exists, and to the same as above -* One task for each combination of `SoftwareComponent` and repository, - unless manually deactivated, a `MavenCentral` repository is created by default. -* One task for publishing `All` software components to any target repository -* For every repository with an associated Sonatype Nexus instance, additional tasks are generated to control the - upload into a new staging repository, its closure, and its release. +### Minimal -In short, if you have (for instance) a mixed Java-Kotlin project, -you should find the following tasks: +Add `MAVEN_CENTRAL_USERNAME` and `MAVEN_CENTRAL_PASSWORD` to your environment -* `closeJavaMavenOnMavenCentralNexus` -* `closeKotlinMavenOnMavenCentralNexus` -* `publishJavaMavenPublicationToMavenCentralRepository` -* `publishKotlinMavenPublicationToMavenCentralRepository` -* `publishAllPublicationsToMavenCentralRepository` -* `publishJavaMavenPublicationToMavenLocalRepository` -* `publishKotlinMavenPublicationToMavenLocalRepository` -* `publishAllPublicationsToMavenLocalRepository` -* `releaseJavaMavenOnMavenCentralNexus` -* `releaseKotlinMavenOnMavenCentralNexus` -* `uploadJavaMavenToMavenCentralNexus` -* `uploadKotlinMavenToMavenCentralNexus` - -If you add a custom repository, say `myRepo`, you would also find the following tasks: - -* `publishJavaMavenPublicationToMyRepoRepository` -* `publishKotlinMavenPublicationToMyRepoRepository` -* `publishAllPublicationsToMyRepoRepository` - -and if `myRepo` has configured an URL for an associated Nexus instance, the following ones: - -* `closeJavaMavenOnMyRepoNexus` -* `closeKotlinMavenOnMyRepoNexus` -* `releaseJavaMavenOnMyRepoNexus` -* `releaseKotlinMavenOnMyRepoNexus` -* `uploadJavaMavenToMyRepoNexus` -* `uploadKotlinMavenToMyRepoNexus` +```kotlin +plugins { + id ("org.danilopianini.publish-on-central") version "" +} +group = "your.group.id" // This must be configured for the generated pom.xml to work correctly +publishOnCentral { + projectUrl.set("https://github.com/DanySK/${project.name}") + scmConnection.set("git:git@github.com:DanySK/${project.name}") +} +publishing { + publications { + withType { + pom { + developers { + developer { + name.set("Danilo Pianini") + email.set("danilo.pianini@gmail.com") + url.set("http://www.danilopianini.org/") + } + } + } + } + } +} +signing { + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKey, signingPassword) +} +``` -which is what needs to get called to have your artifacts uploaded on OSSRH Nexus instance. +### Complete -### Importing the plugin ```kotlin plugins { id ("org.danilopianini.publish-on-central") version "" } ``` -The plugin is configured to react to the application of the `java` plugin, and to apply the `maven-publish` and `signing` plugin if they are not applied. -### Configuring the plugin +The plugin is configured to react to the application of the `java` plugin, +and to apply the `maven-publish` and `signing` plugin if they are not applied. ```kotlin // The package name is equal to the project name @@ -83,11 +76,6 @@ group = "your.group.id" // This must be configured for the generated pom.xml to * The plugin comes with defaults that are useful to myself. You should configure it to behave as you please: */ publishOnCentral { - /* - * By default, this plugin aggressively configures all Maven Publications to be compatible with Central. - * If it is not the desired behavior, turn this setting to false. - */ - autoConfigureAllPublications.set(true) // Set to false if you do not want the MavenCentral repository to be automatically configured configureMavenCentral.set(true) // The following values are the default, if they are ok with you, just omit them @@ -132,9 +120,9 @@ publishOnCentral { */ if (project.version.endsWith("-SNAPSHOT")) { // Avoid stable versions being pushed there... mavenCentralSnapshotsRepository() // Imports user and password from the configuration for Maven Central - // mavenCentralSnapshotsRepository() { - // ...but they can be customized as per any other repository - // } + // mavenCentralSnapshotsRepository() { + // ...but they can be customized as per any other repository + // } } } @@ -170,6 +158,57 @@ signing { } ``` +## Tasks + +* `sourcesJar`: a `Jar` task preconfigured to collect and pack `allSource` from the `main` source set +* `javadocJar`: a `Jar` task preconfigured to + 1. Detect if a javadoc tasks exists, and in case depend on it, and pack its output folder + 2. Detect if a dokkaJavadoc tasks exists, and to the same as above +* One publishing task for each combination of `SoftwareComponent` and repository, + unless manually deactivated, a `MavenCentral` repository is created by default. +* One publishing task for publishing `All` software components to any target repository +* For every repository with an associated Sonatype Nexus instance, additional tasks are generated to control the + creation, upload, closure, and release of staging repositories. + +### Non-Nexus publishing + +Launching the `publish[PublicationName]PublicationTo[RepositoryName]Repository` triggers the creation of the required components, +their signing, and the upload on the target repository. + +```mermaid +flowchart LR + jar --o signPublicationNamePublication + sourcesJar --o signPublicationNamePublication + javadocJar --o signPublicationNamePublication + signPublicationNamePublication --o publishPublicationNamePublicationToRepositoryNameRepository + generatePomFileForPublicationNamePublication --o signPublicationNamePublication +``` + +### Nexus publishing + +Nexus publishing is a bit more elaborate. +It requires to select: +1. the operation that must be performed (among simple upload, repository closure, and repository release), and +2. the packages that will be uploaded + +Typical invocations could be: + +* `./gradlew uploadAllPublicationsToMavenCentralNexus` + * Simply uploads all publications on a single staging repository +* `./gradlew uploadAllPublicationsToMavenCentralNexus closeStagingRepositoryOnMavenCentral` + * Uploads all artifacts and closes the repository +* `./gradlew uploadAllPublicationsToMavenCentralNexus releaseStagingRepositoryOnMavenCentral` + * Uploads all artifacts, closes, and releases the repository + +The idea is that the packages to be uploaded must be selected by picking the right set of +`upload[Publication]To[Repo]Nexus` tasks, +and then if further operations are required, either `closeStagingRepositoryOnMavenCentral` or +`releaseStagingRepositoryOnMavenCentral` can be used to close/release. + +## Usage examples + +T.B.D. + ## Contributing to the project I gladly review pull requests and I'm happy to improve the work. diff --git a/src/main/kotlin/org/danilopianini/gradle/mavencentral/Configuration.kt b/src/main/kotlin/org/danilopianini/gradle/mavencentral/Configuration.kt index 3ef5ef5c..946c78fd 100644 --- a/src/main/kotlin/org/danilopianini/gradle/mavencentral/Configuration.kt +++ b/src/main/kotlin/org/danilopianini/gradle/mavencentral/Configuration.kt @@ -1,15 +1,15 @@ package org.danilopianini.gradle.mavencentral -import org.gradle.api.DomainObjectCollection import org.gradle.api.Project import org.gradle.api.provider.Property import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.tasks.PublishToMavenRepository import org.gradle.api.publish.plugins.PublishingPlugin -import org.gradle.jvm.tasks.Jar +import org.gradle.configurationcache.extensions.capitalized import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.property +import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.the import org.gradle.kotlin.dsl.withType import org.gradle.plugins.signing.SigningExtension @@ -29,80 +29,13 @@ internal inline fun Project.propertyWithDefaultProvider(noinline def * - a javadoc jar file */ fun MavenPublication.configureForMavenCentral(extension: PublishOnCentralExtension) { - val project = extension.project configurePomForMavenCentral(extension) - if (pom.packaging == "jar") { - val jarTasks = project.tasks.withType() - // Required artifacts - if (artifacts.none { it.extension == "jar" && it.classifier.isNullOrEmpty() }) { - project.logger.debug("Publication '{}' has no pre-configured classifier-less jar", name) - artifact(jarTasks.findJarTaskWithClassifier("", "jar", this)) - } - if (artifacts.none { it.classifier == "sources" }) { - project.logger.debug("Publication '{}' has no pre-configured source jar", name) - artifact(jarTasks.findJarTaskWithClassifier("sources", "sourcesJar", this)) - } - if (artifacts.none { it.classifier == "javadoc" }) { - project.logger.debug("Publication '{}' has no pre-configured javadoc jar", name) - artifact(jarTasks.findJarTaskWithClassifier("javadoc", "javadocJar", this)) - } - } + val project = extension.project // Signing - project.configure { - sign(this@configureForMavenCentral) - } -} - -private fun DomainObjectCollection.findJarTaskWithClassifier( - classifier: String, - preferredName: String, - publication: MavenPublication, -): Jar { - val withClassifier = filter { - with(it.archiveClassifier.orNull) { - if (classifier.isEmpty()) isNullOrEmpty() else this == classifier - } - } - fun instructions() = """ - |You can either: - | - create a task that generates a jar without the correct classifier for publish-on-central to bind to, or - | - bind yourself a jar with the right classifier as artifact for the publication, or - | - disable the automatic configuration of all Maven publications for Maven Central with: - | publishOnCentral { - | autoConfigureAllPublications.set(false) - | } - | then manually configure the publications you want on Central with - | publishing.publications.withType/* filter the ones you want*/ { - | configureForMavenCentral(publishOnCentral) - | } - """.trimMargin() - check(withClassifier.isNotEmpty()) { - """ - |Publication '${ - publication.name - }' with packaging type '${ - publication.pom.packaging - }' has no jar with ${ - if (classifier.isEmpty()) "no classifier" else "classifier '$classifier'" - }, which is required for Maven Central. - |${instructions()} - """.trimMargin() - } - return when (withClassifier.size) { - 1 -> withClassifier.first() - else -> { - val best = withClassifier.find { it.name == preferredName } - check(best != null) { - """ - |Publication $publication needs a jar with classifier '$classifier', and these tasks are available: - |${withClassifier.map { it.name }} - |Publish-on-central tried to find one named '$preferredName' among them, but there was none. - |${instructions()} - """.trimMargin() - } - best + project.tasks.findByName("sign${name.capitalized()}Publication") + ?: project.configure { + sign(this@configureForMavenCentral) } - } } /** @@ -159,21 +92,50 @@ fun Project.configureRepository(repoToConfigure: Repository) { } private fun Project.configureNexusRepository(repoToConfigure: Repository, nexusUrl: String) { - the().publications.withType().forEach { publication -> - val nexus = NexusStatefulOperation( - project = project, - nexusUrl = nexusUrl, - group = project.group.toString(), - user = repoToConfigure.user, - password = repoToConfigure.password, - timeOut = repoToConfigure.nexusTimeOut, - connectionTimeOut = repoToConfigure.nexusConnectTimeOut, - ) + the().publications.configureEach { + println("Publication ${it.name}: ${it::class}") + } + val nexus = NexusStatefulOperation( + project = project, + nexusUrl = nexusUrl, + group = project.group.toString(), + user = repoToConfigure.user, + password = repoToConfigure.password, + timeOut = repoToConfigure.nexusTimeOut, + connectionTimeOut = repoToConfigure.nexusConnectTimeOut, + ) + val nexusUploadUrl: Property = project.objects.property() + val createStagingRepository = tasks.register("createStagingRepositoryOn${repoToConfigure.name}") { + it.doLast { + project.warnIfCredentialsAreMissing(repoToConfigure) + nexusUploadUrl.set(nexus.repoUrl) + } + it.group = PublishingPlugin.PUBLISH_TASK_GROUP + it.description = "Creates a new Nexus staging repository on ${repoToConfigure.name}." + } + val uploadAllPublications = tasks.register("uploadAllPublicationsTo${repoToConfigure.name}Nexus") { + it.dependsOn(createStagingRepository) + it.group = PublishingPlugin.PUBLISH_TASK_GROUP + it.description = "Uploads all publications to a staging repository on ${repoToConfigure.name}." + } + val closeStagingRepository = tasks.register("closeStagingRepositoryOn${repoToConfigure.name}") { + it.doLast { nexus.close() } + it.dependsOn(createStagingRepository) + it.mustRunAfter(uploadAllPublications) + it.group = PublishingPlugin.PUBLISH_TASK_GROUP + it.description = "Closes the Nexus repository on ${repoToConfigure.name}." + } + tasks.register("releaseStagingRepositoryOn${repoToConfigure.name}") { + it.doLast { nexus.release() } + it.dependsOn(closeStagingRepository) + it.group = PublishingPlugin.PUBLISH_TASK_GROUP + it.description = "Releases the Nexus repo on ${repoToConfigure.name}." + } + the().publications.withType().configureEach { publication -> val publicationName = publication.name.replaceFirstChar(Char::titlecase) - val uploadArtifacts = project.tasks.create( + project.tasks.register( "upload${publicationName}To${repoToConfigure.name}Nexus", - PublishToMavenRepository::class, - ) { publishTask -> + ).configure { publishTask -> publishTask.repository = project.repositories.maven { repo -> repo.name = repoToConfigure.name repo.url = project.uri(repoToConfigure.url) @@ -182,28 +144,19 @@ private fun Project.configureNexusRepository(repoToConfigure: Repository, nexusU it.password = repoToConfigure.password.orNull } } + publishTask.dependsOn(createStagingRepository) + uploadAllPublications.get().dependsOn(publishTask) + closeStagingRepository.get().mustRunAfter(publishTask) + publishTask.publication = publication publishTask.doFirst { warnIfCredentialsAreMissing(repoToConfigure) - publishTask.repository.url = nexus.repoUrl } - publishTask.publication = publication + publishTask.doLast { + publishTask.repository.url = nexusUploadUrl.get() + } publishTask.group = PublishingPlugin.PUBLISH_TASK_GROUP - publishTask.description = "Initializes a new Nexus repository on ${repoToConfigure.name} " + - "and uploads the $publicationName publication." - } - val closeRepository = tasks.create("close${publicationName}On${repoToConfigure.name}Nexus") { - it.doLast { nexus.close() } - it.dependsOn(uploadArtifacts) - it.group = PublishingPlugin.PUBLISH_TASK_GROUP - it.description = "Closes the Nexus repository on ${repoToConfigure.name} with the " + - "$publicationName publication." - } - tasks.create("release${publicationName}On${repoToConfigure.name}Nexus") { - it.doLast { nexus.release() } - it.dependsOn(closeRepository) - it.group = PublishingPlugin.PUBLISH_TASK_GROUP - it.description = "Releases the Nexus repo on ${repoToConfigure.name} " + - "with the $publicationName publication." + publishTask.description = "Uploads the $publicationName publication " + + "to a staging repository on ${repoToConfigure.name}." } } } diff --git a/src/main/kotlin/org/danilopianini/gradle/mavencentral/PublishOnCentral.kt b/src/main/kotlin/org/danilopianini/gradle/mavencentral/PublishOnCentral.kt index 8cde2a27..ba97d462 100644 --- a/src/main/kotlin/org/danilopianini/gradle/mavencentral/PublishOnCentral.kt +++ b/src/main/kotlin/org/danilopianini/gradle/mavencentral/PublishOnCentral.kt @@ -49,20 +49,18 @@ class PublishOnCentral : Plugin { if (publications.none { it.name == name }) { publications.create(name, MavenPublication::class.java) { publication -> publication.from(component) + publication.configureForMavenCentral(extension) createdPublications += publication } project.logger.debug("Created new publication $name") + println("Created new publication $name") } } } } - project.afterEvaluate { - if (extension.autoConfigureAllPublications.orNull == true) { - project.the().publications.withType().configureEach { - it.configureForMavenCentral(extension) - } - } else { - createdPublications.forEach { it.configureForMavenCentral(extension) } + project.the().publications.withType { + if (extension.autoConfigureAllPublications.getOrElse(true)) { + configureForMavenCentral(extension) } } project.afterEvaluate { diff --git a/src/test/resources/org/danilopianini/gradle/test/ktmultiplatform/test.yaml b/src/test/resources/org/danilopianini/gradle/test/ktmultiplatform/test.yaml index 76e2ee5d..3a71c524 100644 --- a/src/test/resources/org/danilopianini/gradle/test/ktmultiplatform/test.yaml +++ b/src/test/resources/org/danilopianini/gradle/test/ktmultiplatform/test.yaml @@ -10,3 +10,14 @@ tests: - 'generateMetadataFileForWasm32Publication' expectation: success: *tasks + - description: "gradle should generate metadata files in kotlin-multiplatform projects" + configuration: + tasks: + - tasks + expectation: + success: tasks + output_contains: + - releaseStagingRepositoryOnMavenCentral + - createStagingRepositoryOnMavenCentral + - closeStagingRepositoryOnMavenCentral + - uploadAllPublicationsToMavenCentralNexus diff --git a/src/test/resources/org/danilopianini/gradle/test/test0/test.yaml b/src/test/resources/org/danilopianini/gradle/test/test0/test.yaml index c6c00175..6e74a58f 100644 --- a/src/test/resources/org/danilopianini/gradle/test/test0/test.yaml +++ b/src/test/resources/org/danilopianini/gradle/test/test0/test.yaml @@ -24,12 +24,12 @@ tests: expectation: success: tasks output_contains: - - publishJavaMavenPublication - publishPluginMavenPublicationToGithubRepository - - publishPluginMavenPublicationToMavenCentralRepository - - releaseJavaMavenOnMavenCentralNexus + - uploadJavaMavenToMavenCentralNexus + - uploadPluginMavenToMavenCentralNexus + - releaseStagingRepositoryOnMavenCentral output_doesnt_contain: - - releaseJavaMavenOnGitHubNexus + - uploadJavaMavenToGithubNexus - description: "sources and javadoc tasks get greated" configuration: tasks: tasks