From caa091977e6007cb562f1575a537e4947e8df1aa Mon Sep 17 00:00:00 2001 From: Danilo Pianini Date: Fri, 26 Nov 2021 14:42:01 +0100 Subject: [PATCH] feat!: add support for Nexus, switch to the new CI (#158) * refactor: add MavenCentralSnapshots back * doc: add missing documentation * refactor!: restrucure the plugin internally * build: drop unused library * style: remove blank line * refactor!: further refactoring + doc * fix: pass down the password appropriately * doc: update the README.md file * build: use the latest version of itself * ci: try to switch to semantic release * ci: update build-check-deploy-gradle-action * ci: use a custom checkout --- .github/workflows/build-and-deploy.yml | 71 +++------- .releaserc.yml | 13 ++ README.md | 102 ++++++++----- build.gradle.kts | 13 +- gradle/libs.versions.toml | 2 +- package.json | 10 ++ renovate.json | 19 +++ .../Configuration.kt | 125 ++++++++++++++++ .../PublishOnCentral.kt | 15 +- .../PublishOnCentralExtension.kt | 134 ++++++++++-------- .../Repository.kt | 111 +++------------ .../gradle/mavencentral/test/Tests.kt | 5 +- 12 files changed, 357 insertions(+), 263 deletions(-) create mode 100644 .releaserc.yml create mode 100644 package.json create mode 100644 src/main/kotlin/org.danilopianini.gradle.mavencentral/Configuration.kt diff --git a/.github/workflows/build-and-deploy.yml b/.github/workflows/build-and-deploy.yml index 531e7474..0c3262a9 100644 --- a/.github/workflows/build-and-deploy.yml +++ b/.github/workflows/build-and-deploy.yml @@ -20,61 +20,22 @@ jobs: matrix: os: [windows, macos, ubuntu] runs-on: ${{ matrix.os }}-latest - env: - TERM: dumb steps: - # Checkout the repository - name: Checkout - uses: danysk/action-checkout@0.1.0 - - name: Configure the Windows Pagefile - if: ${{ runner.os == 'Windows' }} - uses: al-cheb/configure-pagefile-action@v1.2 - - name: Cache Gradle packages - uses: actions/cache@v2 + uses: DanySK/action-checkout@0.1.0 + - uses: DanySK/build-check-deploy-gradle-action@1.0.4 with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - ~/.gradle/jdks - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - restore-keys: | - ${{ runner.os }}-gradle- - - name: Build - shell: bash - run: ./gradlew assemble - - name: Check - shell: bash - run: ./gradlew check --parallel - - name: CodeCov - if: ${{ runner.os == 'Linux' }} - uses: codecov/codecov-action@v2 - - name: Deploy - if: ${{ runner.os == 'Linux' && github.event_name == 'push' }} - shell: bash - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} - GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} - MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} - MAVEN_CENTRAL_USERNAME: danysk - ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_KEY }} - ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} - SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} - run: | - ./gradlew publishPluginMavenPublicationToMavenCentralRepository publishKotlinMavenPublicationToMavenCentralRepository - ./gradlew publishPluginMavenPublicationToGithubRepository || true - ./gradlew publishPlugins -Pgradle.publish.key=$GRADLE_PUBLISH_KEY -Pgradle.publish.secret=$GRADLE_PUBLISH_SECRET || true - - name: Turn off the Gradle Daemon - shell: bash - run: ./gradlew --stop - - name: Cleanup the Gradle cache - if: ${{ runner.os != 'Windows' }} - shell: bash - run: | - rm -f ~/.gradle/caches/modules-2/modules-2.lock - rm -f ~/.gradle/caches/modules-2/gc.properties - - name: Force the Gradle cleanup on Windows - if: ${{ runner.os == 'Windows' }} - run: | - Remove-Item -Force c:\Users\runneradmin\.gradle\caches\modules-2\modules-2.lock - Remove-Item -Force c:\Users\runneradmin\.gradle\caches\modules-2\gc.properties + deploy-command: | + npm install + npx semantic-release + gradle-termination-command: ./gradlew --stop + java-distribution: temurin + java-version: '17' + should-run-codecov: ${{ runner.os == 'Linux' }} + should-deploy: ${{ runner.os == 'Linux' && github.event_name == 'push' }} + github-token: ${{ github.token }} + gradle-publish-secret: ${{ secrets.GRADLE_PUBLISH_SECRET }} + gradle-publish-key: ${{ secrets.GRADLE_PUBLISH_KEY }} + maven-central-password: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + signing-key: ${{ secrets.SIGNING_KEY }} + signing-password: ${{ secrets.SIGNING_PASSWORD }} diff --git a/.releaserc.yml b/.releaserc.yml new file mode 100644 index 00000000..80838cb2 --- /dev/null +++ b/.releaserc.yml @@ -0,0 +1,13 @@ +tagFormat: "${version}" +plugins: + - "@semantic-release/commit-analyzer" + - "@semantic-release/release-notes-generator" + - "@semantic-release/changelog" + - + - "@semantic-release/exec" + - publishCmd: | + ./gradlew releaseKotlin + ./gradlew publishPlugins -Pgradle.publish.key=$GRADLE_PUBLISH_KEY -Pgradle.publish.secret=$GRADLE_PUBLISH_SECRET + ./gradlew publishPluginMavenPublicationToGithubRepository || true + - "@semantic-release/github" + - "@semantic-release/git" diff --git a/README.md b/README.md index 9a7e1414..0d44e031 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,24 @@ # publish-on-central A Gradle plugin for streamlined publishing on Maven Central +(and other Maven / Nexus repositories). +This plugin is meant to provide an even easier configuration than +[`io.github.gradle-nexus:publish-plugin`](https://github.com/gradle-nexus/publish-plugin) +(from which this plugin depends), +with the goal of supporting highly automated workflows with minimal configuration. ## Rationale Publishing on Maven Central requires too much configuration? Well, I agree. -This plugin is here to simplify your life by automatically configuring (when applied) the Java plugin and the Publish -Plugin to create source and javadoc jars, sign them for you and send them to OSSRH's Sonatype Nexus. +This plugin is here to simplify your life by automatically +scanning all the software components produced by any plugin, +configuring a corresponding publication, +filling all the information required by OSSRH, +configuring tasks for generating javadoc and source jar files, +activating the signing plugin, +and preparing tasks to upload, close, and release the artifact. -## Usage +This plugin supports both targets that use Sonatype Nexus (such as Maven Central) +and targets that do not, such as GitHub Packages. ### Provided tasks @@ -15,19 +26,27 @@ Plugin to create source and javadoc jars, sign them for you and send them to OSS * `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 repositories. - The `MavenCentral` and `MavenCentralSnapshots` are predefined, other repositories can be added. +* 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. In short, if you have (for instance) a mixed Java-Kotlin project, you should find the following tasks: +* `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: @@ -35,13 +54,20 @@ If you add a custom repository, say `myRepo`, you would also find the following * `publishKotlinMavenPublicationToMyRepoRepository` * `publishAllPublicationsToMyRepoRepository` +and if `myRepo` has configured an URL for an associated Nexus instance, the following ones: + +* `publishJavaMavenPublicationToMyRepoRepository` +* `publishKotlinMavenPublicationToMyRepoRepository` +* `publishAllPublicationsToMyRepoRepository` + + which is what needs to get called to have your artifacts uploaded on OSSRH Nexus instance. ### Importing the plugin ```kotlin plugins { - id ("org.danilopianini.publish-on-central") version "0.5.1" + 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. @@ -49,56 +75,62 @@ The plugin is configured to react to the application of the `java` plugin, and t ### Configuring the plugin ```kotlin +// The package name is equal to the project name group = "your.group.id" // This must be configured for the generated pom.xml to work correctly /* * The plugin comes with defaults that are useful to myself. You should configure it to behave as you please: */ publishOnCentral { + // 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 - projectDescription = "No description provided" - projectLongName = project.name - licenseName = "Apache License, Version 2.0" - licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0" - projectUrl = "https://github.com/DanySK/${project.name}" - scmConnection = "git:git@github.com:DanySK/${project.name}" + projectDescription.set("No description provided") + projectLongName.set(project.name) + licenseName.set("Apache License, Version 2.0") + licenseUrl.set("http://www.apache.org/licenses/LICENSE-2.0") + projectUrl.set("https://github.com/DanySK/${project.name}") + scmConnection.set("git:git@github.com:DanySK/${project.name}") /* - * The plugin is pre-configured to fetch credentials for Maven Central from the environment - * Username from: MAVEN_CENTRAL_USERNAME - * Password from: MAVEN_CENTRAL_PASSWORD - * - * In case of failure, it falls back to properties mavenCentralUsername and mavenCentralPassword respectively + * The plugin is pre-configured to fetch credentials for Maven Central from the context in the following order: + * 1. Environment variables MAVEN_CENTRAL_USERNAME and MAVEN_CENTRAL_PASSWORD + * 2. Project properties mavenCentralUsername and mavenCentralPassword + * 3. Project properties sonatypeUsername and sonatypePassword + * 4. Project properties ossrhUsername and ossrhPassword + * + * They can be further customized through values or providers: */ + mavenCentral.user.set("...") + mavenCentral.password.set(provider { "..." }) + /* - * This publication can be sent to other destinations, e.g. GitHub + * The publications can be sent to other destinations, e.g. GitHub * The task name would be 'publishAllPublicationsToGitHubRepository' */ repository("https://maven.pkg.github.com/OWNER/REPOSITORY", "GitHub") { - user = System.getenv("GITHUB_USERNAME") - password = System.getenv("GITHUB_TOKEN") + user.set(System.getenv("GITHUB_USERNAME")) + password.set(System.getenv("GITHUB_TOKEN")) + } + + /* + * Here is an example of a repository with a custom Nexus instance + */ + repository("https://some/valid/repo/with/nexus", "MyNexus") { + user.set(mavenCentral.user) // mavenCentral is accessible for + password.set(System.getenv("GITHUB_TOKEN")) + nexusUrl = "https://some/valid/nexus/instance" + // nexusTimeOut and nexusConnectionTimeOut can be configured, too. } /* * A simplified handler is available for publishing on the Snapshots repository of Maven Central */ if (project.version.endsWith("-SNAPSHOT")) { // Avoid stable versions being pushed there... - mavenCentralSnapshotRepository() // Imports user and password from the configuration for Maven Central - // mavenCentralSnapshotRepository() { + mavenCentralSnapshotsRepository() // Imports user and password from the configuration for Maven Central + // mavenCentralSnapshotsRepository() { // ...but they can be customized as per any other repository // } } - /* - * You may also want to configure publications created by other plugins - * like the one that goes on Central. Typically, for instance, for publishing - * Gradle plugins to Maven Central. - * It can be done as follows. - */ - publishing { - publications { - withType { - configurePomForMavenCentral() - } - } - } } + /* * Developers and contributors must be added manually */ diff --git a/build.gradle.kts b/build.gradle.kts index f019d430..a009623f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -93,18 +93,17 @@ gradlePlugin { } publishOnCentral { - projectDescription = projectDetails - projectLongName = fullName - projectUrl = websiteUrl - scmConnection = "git:git@github.com:DanySK/$name" + projectDescription.set(projectDetails) + projectLongName.set(fullName) + projectUrl.set(websiteUrl) + scmConnection.set("git:git@github.com:DanySK/$name") repository("https://maven.pkg.github.com/DanySK/$name".toLowerCase()) { - user = "danysk" - password = System.getenv("GITHUB_TOKEN") + user.set("danysk") + password.set("GITHUB_TOKEN") } publishing { publications { withType { - configurePomForMavenCentral() pom { developers { developer { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 943472ca..083128eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,5 +21,5 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-qa = { id = "org.danilopianini.gradle-kotlin-qa", version = "0.3.2" } multiJvmTesting = { id = "org.danilopianini.multi-jvm-test-plugin", version = "0.2.0" } nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nexus-publish" } -publishOnCentral = { id = "org.danilopianini.publish-on-central", version = "0.6.1" } +publishOnCentral = { id = "org.danilopianini.publish-on-central", version = "0.6.1-dev18-5366bca" } taskTree = { id = "com.dorongold.task-tree", version = "2.1.0" } diff --git a/package.json b/package.json new file mode 100644 index 00000000..71c30914 --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "devDependencies": { + "@semantic-release/changelog": "6.0.1", + "@semantic-release/exec": "6.0.2", + "@semantic-release/git": "10.0.1" + }, + "engines": { + "node": "16.13" + } +} diff --git a/renovate.json b/renovate.json index 7049055f..2e25d975 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,24 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "local>DanySK/renovate-config" + ], + "packageRules": [ + { + "description": "Trigger a minor update on some dependencies", + "matchPackageNames": [ + "io.github.gradle-nexus.publish-plugin", + "io.github.gradle-nexus:publish-plugin" + ], + "semanticCommitType": "feat" + }, + { + "description": "Trigger a patch update on some dependencies", + "matchPackageNames": [ + "gradle", + "org.jetbrains.kotlin.jvm", + "org.jetbrains.dokka" + ], + "semanticCommitType": "fix" + } ] } diff --git a/src/main/kotlin/org.danilopianini.gradle.mavencentral/Configuration.kt b/src/main/kotlin/org.danilopianini.gradle.mavencentral/Configuration.kt new file mode 100644 index 00000000..9ba21846 --- /dev/null +++ b/src/main/kotlin/org.danilopianini.gradle.mavencentral/Configuration.kt @@ -0,0 +1,125 @@ +package org.danilopianini.gradle.mavencentral + +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.kotlin.dsl.configure +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.the +import org.gradle.kotlin.dsl.withType +import java.net.URI + +internal inline fun Project.propertyWithDefault(default: T?): Property = + objects.property(T::class.java).apply { convention(default) } + +internal inline fun Project.propertyWithDefaultProvider(noinline default: () -> T?): Property = + objects.property(T::class.java).apply { convention(provider(default)) } + +/** + * Configures the pom.xml file of a [MavenPublication] with the information specified in this configuration. + */ +fun MavenPublication.configurePomForMavenCentral(extension: PublishOnCentralExtension) { + pom { pom -> + with(pom) { + name.set(extension.projectLongName) + description.set(extension.projectDescription) + packaging = "jar" + url.set(extension.projectUrl) + licenses { + it.license { license -> + license.name.set(extension.licenseName) + license.url.set(extension.licenseUrl) + } + } + scm { scm -> + scm.url.set(extension.projectUrl) + scm.connection.set(extension.scmConnection) + scm.developerConnection.set(extension.scmConnection) + } + } + } +} + +/** + * Reifies this repository setup onto every [PublishingExtension] configuration of the provided [project]. + */ +fun Project.configureRepository(repoToConfigure: Repository) { + extensions.configure(PublishingExtension::class) { publishing -> + publishing.repositories { repository -> + repository.maven { mavenArtifactRepository -> + mavenArtifactRepository.name = repoToConfigure.name + mavenArtifactRepository.url = URI(repoToConfigure.url) + mavenArtifactRepository.credentials { credentials -> + credentials.username = repoToConfigure.user.orNull + credentials.password = repoToConfigure.password.orNull + credentials.username ?: project.logger.warn( + "No username configured for repository {} at {}.", + repoToConfigure.name, + repoToConfigure.url, + ) + credentials.password ?: project.logger.warn( + "No password configured for user {} on repository {} at {}.", + repoToConfigure.user, + repoToConfigure.name, + repoToConfigure.url, + ) + } + } + } + } + if (repoToConfigure.nexusUrl != null) { + configureNexusRepository(repoToConfigure, repoToConfigure.nexusUrl) + } +} + +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, + ) + val publicationName = publication.name.replaceFirstChar(Char::titlecase) + val uploadArtifacts = project.tasks.create( + "upload${publicationName}To${repoToConfigure.name}Nexus", + PublishToMavenRepository::class, + ) { publishTask -> + publishTask.repository = project.repositories.maven { repo -> + repo.name = repoToConfigure.name + repo.url = project.uri(repoToConfigure.url) + repo.credentials { + it.username = repoToConfigure.user.orNull + it.password = repoToConfigure.password.orNull + } + } + publishTask.doFirst { + publishTask.repository.url = nexus.repoUrl + } + publishTask.publication = publication + 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." + } + } +} diff --git a/src/main/kotlin/org.danilopianini.gradle.mavencentral/PublishOnCentral.kt b/src/main/kotlin/org.danilopianini.gradle.mavencentral/PublishOnCentral.kt index d937995c..63245feb 100644 --- a/src/main/kotlin/org.danilopianini.gradle.mavencentral/PublishOnCentral.kt +++ b/src/main/kotlin/org.danilopianini.gradle.mavencentral/PublishOnCentral.kt @@ -9,6 +9,8 @@ import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.maven.plugins.MavenPublishPlugin import org.gradle.api.tasks.javadoc.Javadoc +import org.gradle.kotlin.dsl.the +import org.gradle.kotlin.dsl.withType import org.gradle.plugins.signing.SigningExtension import org.gradle.plugins.signing.SigningPlugin @@ -21,7 +23,6 @@ class PublishOnCentral : Plugin { * The name of the publication to be created. */ private const val publicationName = "Maven" - private inline fun Project.extension(): T = project.extensions.getByType(T::class.java) private inline fun Project.createExtension(name: String, vararg args: Any?): T = project.extensions.create(name, T::class.java, *args) @@ -51,9 +52,6 @@ class PublishOnCentral : Plugin { project.logger.debug("Created new publication $name") publication.artifact(sourcesJarTask) publication.artifact(javadocJarTask) - with(extension) { - publication.configurePomForMavenCentral() - } // Signing project.configure { sign(publication) @@ -65,7 +63,14 @@ class PublishOnCentral : Plugin { project.components.whenObjectAdded(::createPublications) } project.afterEvaluate { - Repository.mavenCentral.configureForProject(project) + project.the().publications.withType().forEach { + it.configurePomForMavenCentral(extension) + } + } + project.afterEvaluate { + if (extension.configureMavenCentral.getOrElse(true)) { + project.configureRepository(extension.mavenCentral) + } } project.plugins.withType(JavaPlugin::class.java) { _ -> project.tasks.withType(JavadocJar::class.java) { javadocJar -> diff --git a/src/main/kotlin/org.danilopianini.gradle.mavencentral/PublishOnCentralExtension.kt b/src/main/kotlin/org.danilopianini.gradle.mavencentral/PublishOnCentralExtension.kt index 8b0a212d..bfddd72d 100644 --- a/src/main/kotlin/org.danilopianini.gradle.mavencentral/PublishOnCentralExtension.kt +++ b/src/main/kotlin/org.danilopianini.gradle.mavencentral/PublishOnCentralExtension.kt @@ -2,73 +2,70 @@ package org.danilopianini.gradle.mavencentral import org.gradle.api.Project import org.gradle.api.provider.Property -import org.gradle.api.publish.maven.MavenPublication - -private inline fun Project.propertyWithDefault(default: T): Property = - objects.property(T::class.java).apply { convention(default) } - -internal class PublishOnCentralConfiguration(project: Project) { - val projectLongName: Property = project.propertyWithDefault(project.name) - - val projectDescription: Property = project.propertyWithDefault("No description provided") - - val licenseName: Property = project.propertyWithDefault("Apache License, Version 2.0") - - val licenseUrl: Property = project.propertyWithDefault("http://www.apache.org/licenses/LICENSE-2.0") - - val scmConnection: Property = project.propertyWithDefault("git:git@github.com:DanySK/${project.name}") - - val projectUrl: Property = project.propertyWithDefault("https://github.com/DanySK/${project.name}") -} +import org.gradle.kotlin.dsl.property +import java.time.Duration /** * The extension in charge of configuring the publish-on-central plugin on the target [project]. */ open class PublishOnCentralExtension(val project: Project) { - internal val configuration = PublishOnCentralConfiguration(project) + /** + * Easier access to the default Maven Central configuration. + */ + val mavenCentral: Repository = Repository( + Repository.mavenCentralName, + url = Repository.mavenCentralURL, + user = project.propertyWithDefaultProvider { + System.getenv("MAVEN_CENTRAL_USERNAME") + ?: project.properties["mavenCentralUsername"]?.toString() + ?: project.properties["sonatypeUsername"]?.toString() + ?: project.properties["ossrhUsername"]?.toString() + }, + password = project.propertyWithDefaultProvider { + System.getenv("MAVEN_CENTRAL_PASSWORD") + ?: project.properties["mavenCentralPassword"]?.toString() + ?: project.properties["sonatypePassword"]?.toString() + ?: project.properties["ossrhPassword"]?.toString() + }, + nexusUrl = Repository.mavenCentralNexusUrl, + ) /** * The full project name. */ - var projectLongName: String - get() = configuration.projectLongName.get() - set(value) = configuration.projectLongName.set(value) + val projectLongName: Property = project.propertyWithDefault(project.name) + + /** + * A property, defaulting to true, that is used to disable the default configuration for Maven Central. + * To be used in case of deployment towards only targets other than Maven Central. + */ + val configureMavenCentral: Property = project.propertyWithDefault(true) /** * A description of the project. */ - var projectDescription: String - get() = configuration.projectDescription.get() - set(value) = configuration.projectDescription.set(value) + var projectDescription: Property = project.propertyWithDefault("No description provided") /** * The project's license name. */ - var licenseName: String - get() = configuration.licenseName.get() - set(value) = configuration.licenseName.set(value) + var licenseName: Property = project.propertyWithDefault("Apache License, Version 2.0") /** * The license URL connection of the project. */ - var licenseUrl: String - get() = configuration.licenseUrl.get() - set(value) = configuration.licenseUrl.set(value) + var licenseUrl: Property = project.propertyWithDefault("http://www.apache.org/licenses/LICENSE-2.0") /** * The SCM connection of the project. */ - var scmConnection: String - get() = configuration.scmConnection.get() - set(value) = configuration.scmConnection.set(value) + var scmConnection: Property = project.propertyWithDefault("git:git@github.com:DanySK/${project.name}") /** * The URL of the project. */ - var projectUrl: String - get() = configuration.projectUrl.get() - set(value) = configuration.projectUrl.set(value) + var projectUrl: Property = project.propertyWithDefault("https://github.com/DanySK/${project.name}") /** * Utility to configure a new Maven repository as target. @@ -78,34 +75,29 @@ open class PublishOnCentralExtension(val project: Project) { name: String = repositoryNameFromURL(url), configurator: MavenRepositoryDescriptor.() -> Unit = { } ) { - val repoDescriptor = MavenRepositoryDescriptor(name).apply(configurator) - Repository(name, url, { repoDescriptor.user }, { repoDescriptor.password }) - .configureForProject(project) + val repoDescriptor = MavenRepositoryDescriptor(project, name).apply(configurator) + val repo = Repository( + repoDescriptor.name, + url, + repoDescriptor.user, + repoDescriptor.password, + repoDescriptor.nexusUrl, + repoDescriptor.nexusTimeOut, + repoDescriptor.nexusConnectionTimeout, + ) + project.afterEvaluate { it.configureRepository(repo) } } /** - * Configures the pom.xml file of a [MavenPublication] with the information specified in this configuration. + * Utility to pre-configure a deployment towards the Maven Central Snapshots repository. */ - fun MavenPublication.configurePomForMavenCentral() { - pom { pom -> - with(pom) { - name.set(projectLongName) - description.set(projectDescription) - packaging = "jar" - url.set(projectUrl) - licenses { - it.license { license -> - license.name.set(licenseName) - license.url.set(licenseUrl) - } - } - scm { scm -> - scm.url.set(projectUrl) - scm.connection.set(scmConnection) - scm.developerConnection.set(scmConnection) - } - } - } + @JvmOverloads fun mavenCentralSnapshotsRepository( + name: String = "MavenCentralSnapshots", + configurator: MavenRepositoryDescriptor.() -> Unit = { }, + ) = repository(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/", name = name) { + user.set(mavenCentral.user) + password.set(mavenCentral.password) + apply(configurator) } companion object { @@ -123,15 +115,31 @@ open class PublishOnCentralExtension(val project: Project) { * Requires a [name], and optionally authentication in form of [user] and [password]. */ class MavenRepositoryDescriptor internal constructor( + project: Project, var name: String, ) { /** * The username. */ - var user: String? = null + val user: Property = project.objects.property() /** * The password. */ - var password: String? = null + val password: Property = project.objects.property() + + /** + * The Nexus URL, if installed. + */ + var nexusUrl: String? = null + + /** + * The Nexus timeout. + */ + var nexusTimeOut: Duration = Duration.ofMinutes(1) + + /** + * The Nexus connection timeout. + */ + var nexusConnectionTimeout: Duration = nexusTimeOut } diff --git a/src/main/kotlin/org.danilopianini.gradle.mavencentral/Repository.kt b/src/main/kotlin/org.danilopianini.gradle.mavencentral/Repository.kt index b414ba1e..bb7c90e2 100644 --- a/src/main/kotlin/org.danilopianini.gradle.mavencentral/Repository.kt +++ b/src/main/kotlin/org.danilopianini.gradle.mavencentral/Repository.kt @@ -1,15 +1,7 @@ package org.danilopianini.gradle.mavencentral import org.gradle.api.Project -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.kotlin.dsl.configure -import org.gradle.kotlin.dsl.create -import org.gradle.kotlin.dsl.the -import org.gradle.kotlin.dsl.withType -import java.net.URI +import org.gradle.api.provider.Property import java.time.Duration /** @@ -22,8 +14,8 @@ import java.time.Duration data class Repository( val name: String, val url: String, - val user: Project.() -> String?, - val password: Project.() -> String?, + val user: Property, + val password: Property, val nexusUrl: String? = null, val nexusTimeOut: Duration = Duration.ofMinutes(3), val nexusConnectTimeOut: Duration = Duration.ofMinutes(3), @@ -36,92 +28,21 @@ data class Repository( override fun toString() = "$name at $url" - /** - * Reifies this repository setup onto every [PublishingExtension] configuration of the provided [project]. - */ - fun configureForProject(project: Project) { - project.extensions.configure(PublishingExtension::class) { publishing -> - publishing.repositories { repository -> - repository.maven { mavenArtifactRepository -> - mavenArtifactRepository.name = name - mavenArtifactRepository.url = URI(url) - mavenArtifactRepository.credentials { credentials -> - credentials.username = project.user() - credentials.password = project.password() - credentials.username - ?: project.logger.warn("No username configured for $name at $url.") - credentials.password - ?: project.logger.warn("No password configured for $name at $url.") - } - } - } - } - if (nexusUrl != null) { - project.afterEvaluate { - project.the().publications.withType().forEach { publication -> - val nexus = NexusStatefulOperation( - project = project, - nexusUrl = nexusUrl, - group = project.group.toString(), - user = project.provider { user(project) }, - password = project.provider { password(project) }, - timeOut = nexusTimeOut, - connectionTimeOut = nexusConnectTimeOut, - ) - val publicationName = publication.name.replaceFirstChar(Char::titlecase) - val uploadArtifacts = project.tasks.create( - "upload${publicationName}To${name}Nexus", - PublishToMavenRepository::class, - ) { publishTask -> - publishTask.repository = project.repositories.maven { repo -> - repo.name = name - repo.url = project.uri(url) - repo.credentials { - it.username = project.user() - it.password = project.password() - } - } - publishTask.doFirst { - publishTask.repository.url = nexus.repoUrl - } - publishTask.publication = publication - publishTask.group = PublishingPlugin.PUBLISH_TASK_GROUP - publishTask.description = "Initializes a new Nexus repository on $name and uploads the " + - "$publicationName publication." - } - val closeRepository = project.tasks.create("close${publicationName}On${name}Nexus") { - it.doLast { nexus.close() } - it.dependsOn(uploadArtifacts) - it.group = PublishingPlugin.PUBLISH_TASK_GROUP - it.description = "Closes the Nexus repository on $name with the $publicationName publication." - } - project.tasks.create("release${publicationName}On${name}Nexus") { - it.doLast { nexus.release() } - it.dependsOn(closeRepository) - it.group = PublishingPlugin.PUBLISH_TASK_GROUP - it.description = "Releases the Nexus repo on $name with the $publicationName publication." - } - } - } - } - } - companion object { + + /** + * The default name of the Maven Central repository. + */ + const val mavenCentralName = "MavenCentral" + + /** + * The default URL of Maven Central. + */ + const val mavenCentralURL = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + /** - * The pre-configured Maven Central repository. + * The Sonatype Nexus instance URL of Maven Central. */ - val mavenCentral = Repository( - "MavenCentral", - url = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/", - user = { - System.getenv("MAVEN_CENTRAL_USERNAME") - ?: this.properties["mavenCentralUsername"]?.toString() - }, - password = { - System.getenv("MAVEN_CENTRAL_PASSWORD") - ?: project.properties["mavenCentralUsername"].toString() - }, - nexusUrl = "https://s01.oss.sonatype.org/service/local/", - ) + const val mavenCentralNexusUrl = "https://s01.oss.sonatype.org/service/local/" } } diff --git a/src/test/kotlin/org/danilopianini/gradle/mavencentral/test/Tests.kt b/src/test/kotlin/org/danilopianini/gradle/mavencentral/test/Tests.kt index 93cf8b5b..7d4e5af7 100644 --- a/src/test/kotlin/org/danilopianini/gradle/mavencentral/test/Tests.kt +++ b/src/test/kotlin/org/danilopianini/gradle/mavencentral/test/Tests.kt @@ -27,10 +27,11 @@ class Tests : StringSpec({ id("org.danilopianini.publish-on-central") } publishOnCentral { - projectDescription = "test" + projectDescription.set("test") repository("https://maven.pkg.github.com/OWNER/REPOSITORY") { name = "github" - user = "test" + user.set("test") + password.set("pwd") } } """.trimIndent()