Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow applying the plugin to a non-root project #359

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -38,6 +39,9 @@ abstract class NexusPublishExtension(objects: ObjectFactory) {

abstract val connectTimeout: Property<Duration>

@get:Incubating
abstract val independentProjects: Property<Boolean>

@get:Nested
abstract val transitionCheckOptions: TransitionCheckOptions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ class NexusPublishPlugin : Plugin<Project> {
}

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondering if this change alone is enough? If the user applies to non-root, then it implicitly start using the independent mode? Since it was not allowed before, it means we can define any behaviour for that case.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to you -- I didn't want to break any of your existing tests.

Also, I'm not sure what would happen if someone tried applying the plugin to multiple projects in a build. I suspect it won't work properly; I'm pretty sure it'll open multiple staging repositories. So I wonder if needing to specify an "incubating" property is useful as an indication that the behaviour might not be entirely stable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having the plugin applied multiple times in the project might sometimes be useful for monorepos which (for some reason) would like to release particular projects to different locations in Central. However, the project author would then need to ensure that group/project/version is property set in that (sub)project and probably some other element which the plugin gets from the project.

Looking at some comments in #81:

Moving the plugin to the root project also implies declaring all the publishing etc in the root project and that would mess up all the configurations I have, basically giving up on isolation.

That's not entirely true. You only need to configure nexusPublishing in the root project but can continue declaring your publications in modules:java.

And:
#81 (comment)

I originally made it a root project plugin so there's a single place to configure Nexus repositories that are shared across subprojects. While each subproject has an "init" task, they use a resource attached to the project to synchronize on to ensure a shared staging repo is created. This could probably be changed to create a checksum of the repo config or require some kind of identifier and a BuildService but I lack the time to give it a try.

I'm a little afraid to add that switch and later on have an another set of issues related to "corner cases" 🤔

I have no experience with build_services. Do you? How hard would it be to convert the plugin to it? Would it "fix" all the (related) problems?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been pondering the question of how I'd go about turning this into a build service -- I do have some experience with them, but not of the degree of complexity that this plugin might require if we wanted to minimise surprises for existing users who upgrade.

There are some design questions that we'd need to decide on answers for. I'm not sure I have all the questions yet, let alone the answers.

If you'd be interested in seeing the result of my attempt to refactor a bit more thoroughly (with absolutely no obligation to accept any of my code, or indeed any of the design choices I might make) then I'll probably quite enjoy the diversion. I'm yak shaving for a personal project, interesting problems are if anything more what I'm looking for than actual solutions :).


require(GradleVersion.current() >= GradleVersion.version("6.2")) {
"io.github.gradle-nexus.publish-plugin requires Gradle version 6.2+"
Expand All @@ -55,7 +53,7 @@ class NexusPublishPlugin : Plugin<Project> {
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) {
Expand All @@ -68,6 +66,7 @@ class NexusPublishPlugin : Plugin<Project> {
connectTimeout.convention(Duration.ofMinutes(5))
transitionCheckOptions.maxRetries.convention(60)
transitionCheckOptions.delayBetween.convention(Duration.ofSeconds(10))
independentProjects.convention(false)
}
}

Expand Down Expand Up @@ -210,42 +209,74 @@ class NexusPublishPlugin : Plugin<Project> {
}

private fun configurePublishingForAllProjects(
rootProject: Project,
project: Project,
extension: NexusPublishExtension,
registry: Provider<StagingRepositoryDescriptorRegistryBuildService>
registry: Provider<StagingRepositoryDescriptorRegistryBuildService>,
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<StagingRepositoryDescriptorRegistryBuildService>) {
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)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down