From e618400af208c624a45064d97f144ecc65c654ea Mon Sep 17 00:00:00 2001 From: Andrew Aylett Date: Sun, 12 Jan 2025 15:48:23 +0000 Subject: [PATCH] Allow applying the plugin to a non-root project This introduces the idea of `independentProjects`, defaulting to false, and limiting application of the plugin to just the project applied to if it's true. By itself, this change should allow projects with a single subproject (such as is templated by `gradle init`) to apply the plugin to that project only. It's also laying a little ground-work for cross-project synchronisation of staging repositories that's compatible with isolated projects. --- .../BaseNexusPublishPluginTests.kt | 61 +++++++++++ .../publishplugin/NexusPublishExtension.kt | 4 + .../publishplugin/NexusPublishPlugin.kt | 101 ++++++++++++------ .../internal/StagingRepository.kt | 2 +- 4 files changed, 132 insertions(+), 36 deletions(-) diff --git a/src/compatTest/kotlin/io/github/gradlenexus/publishplugin/BaseNexusPublishPluginTests.kt b/src/compatTest/kotlin/io/github/gradlenexus/publishplugin/BaseNexusPublishPluginTests.kt index 8a9c1232..b60256b6 100644 --- a/src/compatTest/kotlin/io/github/gradlenexus/publishplugin/BaseNexusPublishPluginTests.kt +++ b/src/compatTest/kotlin/io/github/gradlenexus/publishplugin/BaseNexusPublishPluginTests.kt @@ -279,6 +279,67 @@ abstract class BaseNexusPublishPluginTests { artifactList.forEach { assertUploadedToStagingRepo("/org/example/sample/0.0.1/$it") } } + @Test + fun `publishes to Nexus from sub-project`() { + projectDir.resolve("settings.gradle").write( + """ + rootProject.name = 'super' + include('sample') + """ + ) + projectDir.resolve("sample/build.gradle").write( + """ + plugins { + id('java-library') + id('$publishPluginId') + id('io.github.gradle-nexus.publish-plugin') + } + group = 'org.example' + version = '0.0.1' + publishing { + publications { + $publishPluginContent + } + } + nexusPublishing { + independentProjects = true + repositories { + myNexus { + publicationType = io.github.gradlenexus.publishplugin.NexusRepository.PublicationType.$publicationTypeName + nexusUrl = uri('${server.baseUrl()}') + snapshotRepositoryUrl = uri('${server.baseUrl()}/snapshots/') + allowInsecureProtocol = true + username = 'username' + password = 'password' + } + someOtherNexus { + nexusUrl = uri('http://example.org') + snapshotRepositoryUrl = uri('http://example.org/snapshots/') + } + } + } + """ + ) + + stubStagingProfileRequest("/staging/profiles", mapOf("id" to STAGING_PROFILE_ID, "name" to "org.example")) + stubCreateStagingRepoRequest("/staging/profiles/$STAGING_PROFILE_ID/start", STAGED_REPOSITORY_ID) + expectArtifactUploads("/staging/deployByRepositoryId/$STAGED_REPOSITORY_ID") + + val result = run("publishToMyNexus") + + assertSuccess(result, ":sample:initializeMyNexusStagingRepository") + assertNotConsidered(result, ":initializeMyNexusStagingRepository") + assertThat(result.output) + .containsOnlyOnce("Created staging repository '$STAGED_REPOSITORY_ID' at ${server.baseUrl()}/repositories/$STAGED_REPOSITORY_ID/content/") + assertNotConsidered(result, ":sample:initializeSomeOtherNexusStagingRepository") + assertNotConsidered(result, ":initializeSomeOtherNexusStagingRepository") + server.verify( + WireMock.postRequestedFor(WireMock.urlEqualTo("/staging/profiles/$STAGING_PROFILE_ID/start")) + .withRequestBody(WireMock.matchingJsonPath("\$.data[?(@.description == 'org.example:sample:0.0.1')]")) + ) + artifactList.forEach { assertUploadedToStagingRepo("/org/example/sample/0.0.1/$it") } + } + @Test fun `can be used with lazily applied Gradle Plugin Development Plugin`() { projectDir.resolve("settings.gradle").write( diff --git a/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishExtension.kt b/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishExtension.kt index fcf5a351..52fe8624 100644 --- a/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishExtension.kt +++ b/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishExtension.kt @@ -17,6 +17,7 @@ package io.github.gradlenexus.publishplugin import org.gradle.api.Action +import org.gradle.api.Incubating import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property import org.gradle.api.tasks.Nested @@ -38,6 +39,9 @@ abstract class NexusPublishExtension(objects: ObjectFactory) { abstract val connectTimeout: Property + @get:Incubating + abstract val independentProjects: Property + @get:Nested abstract val transitionCheckOptions: TransitionCheckOptions diff --git a/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishPlugin.kt b/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishPlugin.kt index e03a73c8..760ce6f4 100644 --- a/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishPlugin.kt +++ b/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishPlugin.kt @@ -43,9 +43,7 @@ class NexusPublishPlugin : Plugin { } override fun apply(project: Project) { - require(project == project.rootProject) { - "Plugin must be applied to the root project but was applied to ${project.path}" - } + val isRoot = project == project.rootProject require(GradleVersion.current() >= GradleVersion.version("6.2")) { "io.github.gradle-nexus.publish-plugin requires Gradle version 6.2+" @@ -55,7 +53,7 @@ class NexusPublishPlugin : Plugin { val extension = project.extensions.create(NexusPublishExtension.NAME, NexusPublishExtension::class.java) configureExtension(project, extension) configureNexusTasks(project, extension, registry) - configurePublishingForAllProjects(project, extension, registry) + configurePublishingForAllProjects(project, extension, registry, isRoot) } private fun configureExtension(project: Project, extension: NexusPublishExtension) { @@ -68,6 +66,7 @@ class NexusPublishPlugin : Plugin { connectTimeout.convention(Duration.ofMinutes(5)) transitionCheckOptions.maxRetries.convention(60) transitionCheckOptions.delayBetween.convention(Duration.ofSeconds(10)) + independentProjects.convention(false) } } @@ -210,42 +209,74 @@ class NexusPublishPlugin : Plugin { } private fun configurePublishingForAllProjects( - rootProject: Project, + project: Project, extension: NexusPublishExtension, - registry: Provider + registry: Provider, + isRoot: Boolean ) { - rootProject.afterEvaluate { - it.allprojects { publishingProject -> - publishingProject.plugins.withType(PublishingPlugin::class.java) { - val nexusRepositories = addPublicationRepositories(publishingProject, extension, registry) - nexusRepositories.forEach { (nexusRepo, publicationRepo) -> - val publicationType = nexusRepo.publicationType.get() - val id = when (publicationType) { - PublicationType.IVY -> "ivy-publish" - PublicationType.MAVEN -> "maven-publish" - null -> error("Repo publication type must be \"ivy-publish\" or \"maven-publish\"") - } - publishingProject.plugins.withId(id) { - val initializeTask = rootProject.tasks.named("initialize${nexusRepo.capitalizedName}StagingRepository", InitializeNexusStagingRepository::class.java) - val findStagingRepositoryTask = rootProject.tasks.named("find${nexusRepo.capitalizedName}StagingRepository", FindStagingRepository::class.java) - val closeTask = rootProject.tasks.named("close${nexusRepo.capitalizedName}StagingRepository", CloseNexusStagingRepository::class.java) - val releaseTask = rootProject.tasks.named("release${nexusRepo.capitalizedName}StagingRepository", ReleaseNexusStagingRepository::class.java) - val publishAllTask = publishingProject.tasks.register("publishTo${nexusRepo.capitalizedName}") { task -> - task.group = PublishingPlugin.PUBLISH_TASK_GROUP - task.description = "Publishes all Maven/Ivy publications produced by this project to the '${nexusRepo.name}' Nexus repository." - } - closeTask.configure { task -> - task.mustRunAfter(publishAllTask) - } - releaseTask.configure { task -> - task.mustRunAfter(publishAllTask) - } - configureTaskDependencies(publishingProject, initializeTask, findStagingRepositoryTask, publishAllTask, closeTask, releaseTask, publicationRepo, publicationType) - } + project.afterEvaluate { + require(extension.independentProjects.get() || isRoot) { + "Plugin must be applied to the root project but was applied to ${project.path}" + } + if (extension.independentProjects.get()) { + configurePublishingProject(project, project, extension, registry) + } else { + it.allprojects { publishingProject -> configurePublishingProject(project, publishingProject, extension, registry) } + } + configureSimplifiedCloseAndReleaseTasks(project, extension) + } + } + + private fun configurePublishingProject(rootProject: Project, publishingProject: Project, extension: NexusPublishExtension, registry: Provider) { + publishingProject.plugins.withType(PublishingPlugin::class.java) { + val nexusRepositories = addPublicationRepositories(publishingProject, extension, registry) + nexusRepositories.forEach { (nexusRepo, publicationRepo) -> + val publicationType = nexusRepo.publicationType.get() + val id = when (publicationType) { + PublicationType.IVY -> "ivy-publish" + PublicationType.MAVEN -> "maven-publish" + null -> error("Repo publication type must be \"ivy-publish\" or \"maven-publish\"") + } + publishingProject.plugins.withId(id) { + val initializeTask = rootProject.tasks.named( + "initialize${nexusRepo.capitalizedName}StagingRepository", + InitializeNexusStagingRepository::class.java + ) + val findStagingRepositoryTask = rootProject.tasks.named( + "find${nexusRepo.capitalizedName}StagingRepository", + FindStagingRepository::class.java + ) + val closeTask = rootProject.tasks.named( + "close${nexusRepo.capitalizedName}StagingRepository", + CloseNexusStagingRepository::class.java + ) + val releaseTask = rootProject.tasks.named( + "release${nexusRepo.capitalizedName}StagingRepository", + ReleaseNexusStagingRepository::class.java + ) + val publishAllTask = publishingProject.tasks.register("publishTo${nexusRepo.capitalizedName}") { task -> + task.group = PublishingPlugin.PUBLISH_TASK_GROUP + task.description = + "Publishes all Maven/Ivy publications produced by this project to the '${nexusRepo.name}' Nexus repository." + } + closeTask.configure { task -> + task.mustRunAfter(publishAllTask) + } + releaseTask.configure { task -> + task.mustRunAfter(publishAllTask) } + configureTaskDependencies( + publishingProject, + initializeTask, + findStagingRepositoryTask, + publishAllTask, + closeTask, + releaseTask, + publicationRepo, + publicationType + ) } } - configureSimplifiedCloseAndReleaseTasks(rootProject, extension) } } diff --git a/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepository.kt b/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepository.kt index 56657bfb..18aafc1b 100644 --- a/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepository.kt +++ b/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepository.kt @@ -16,7 +16,7 @@ package io.github.gradlenexus.publishplugin.internal -data class StagingRepository constructor(val id: String, val state: State, val transitioning: Boolean) { +data class StagingRepository(val id: String, val state: State, val transitioning: Boolean) { enum class State { OPEN,