diff --git a/build.gradle.kts b/build.gradle.kts index 56603b58..e44a3793 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -152,6 +152,9 @@ tasks { dependsOn(relocateShadowJar) configurations = listOf(shadowed) exclude("META-INF/maven/**", "META-INF/proguard/**", "META-INF/*.kotlin_module") + manifest { + attributes["Implementation-Version"] = project.version + } } jar { enabled = false @@ -186,6 +189,9 @@ tasks { useJUnitPlatform() maxParallelForks = 8 } + withType().matching { it.name.startsWith("compatTest") }.configureEach { + systemProperty("plugin.version", project.version) + } dokka { configuration { outputFormat = "javadoc" diff --git a/src/compatTest/kotlin/io/github/gradlenexus/publishplugin/NexusPublishPluginTests.kt b/src/compatTest/kotlin/io/github/gradlenexus/publishplugin/NexusPublishPluginTests.kt index f403f3c2..cced52b8 100644 --- a/src/compatTest/kotlin/io/github/gradlenexus/publishplugin/NexusPublishPluginTests.kt +++ b/src/compatTest/kotlin/io/github/gradlenexus/publishplugin/NexusPublishPluginTests.kt @@ -22,6 +22,7 @@ import com.github.tomakehurst.wiremock.client.WireMock.anyUrl import com.github.tomakehurst.wiremock.client.WireMock.containing import com.github.tomakehurst.wiremock.client.WireMock.get import com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor +import com.github.tomakehurst.wiremock.client.WireMock.matching import com.github.tomakehurst.wiremock.client.WireMock.matchingJsonPath import com.github.tomakehurst.wiremock.client.WireMock.post import com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor @@ -32,8 +33,6 @@ import com.github.tomakehurst.wiremock.client.WireMock.urlMatching import com.github.tomakehurst.wiremock.stubbing.Scenario import com.google.gson.Gson import io.github.gradlenexus.publishplugin.internal.StagingRepository -import java.nio.file.Files -import java.nio.file.Path import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner @@ -51,6 +50,8 @@ import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.io.TempDir import ru.lanwen.wiremock.ext.WiremockResolver import ru.lanwen.wiremock.ext.WiremockResolver.Wiremock +import java.nio.file.Files +import java.nio.file.Path @Suppress("FunctionName") // TODO: How to suppress "kotlin:S100" from SonarLint? @ExtendWith(WiremockResolver::class) @@ -199,7 +200,7 @@ class NexusPublishPluginTests { assertThat(result.output).containsOnlyOnce("Created staging repository '$STAGED_REPOSITORY_ID' at ${server.baseUrl()}/repositories/$STAGED_REPOSITORY_ID/content/") assertNotConsidered(result, ":initializeSomeOtherNexusStagingRepository") server.verify(postRequestedFor(urlEqualTo("/staging/profiles/$STAGING_PROFILE_ID/start")) - .withRequestBody(matchingJsonPath("\$.data[?(@.description == 'Created by io.github.gradle-nexus.publish-plugin Gradle plugin')]"))) + .withRequestBody(matchingJsonPath("\$.data[?(@.description == 'org.example:sample:0.0.1')]"))) assertUploadedToStagingRepo("/org/example/sample/0.0.1/sample-0.0.1.pom") assertUploadedToStagingRepo("/org/example/sample/0.0.1/sample-0.0.1.jar") } @@ -696,6 +697,7 @@ class NexusPublishPluginTests { @SafeVarargs private fun stubStagingProfileRequest(url: String, vararg stagingProfiles: Map) { server.stubFor(get(urlEqualTo(url)) + .withHeader("User-Agent", matching("gradle-nexus-publish-plugin/.*")) .willReturn(aResponse().withBody(gson.toJson(mapOf("data" to listOf(*stagingProfiles)))))) } diff --git a/src/main/kotlin/io/github/gradlenexus/publishplugin/AbstractNexusStagingRepositoryTask.kt b/src/main/kotlin/io/github/gradlenexus/publishplugin/AbstractNexusStagingRepositoryTask.kt index c1927cf4..632aeadc 100644 --- a/src/main/kotlin/io/github/gradlenexus/publishplugin/AbstractNexusStagingRepositoryTask.kt +++ b/src/main/kotlin/io/github/gradlenexus/publishplugin/AbstractNexusStagingRepositoryTask.kt @@ -18,6 +18,7 @@ package io.github.gradlenexus.publishplugin import org.gradle.api.DefaultTask import org.gradle.api.model.ObjectFactory +import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal import org.gradle.api.tasks.Nested import org.gradle.kotlin.dsl.property @@ -44,6 +45,11 @@ constructor(objects: ObjectFactory, extension: NexusPublishExtension, repository set(repository) } + @Input + val description = objects.property().apply { + set(extension.description) + } + init { this.onlyIf { extension.useStaging.getOrElse(false) } } diff --git a/src/main/kotlin/io/github/gradlenexus/publishplugin/CloseNexusStagingRepository.kt b/src/main/kotlin/io/github/gradlenexus/publishplugin/CloseNexusStagingRepository.kt index 9dc05832..9bc07a26 100644 --- a/src/main/kotlin/io/github/gradlenexus/publishplugin/CloseNexusStagingRepository.kt +++ b/src/main/kotlin/io/github/gradlenexus/publishplugin/CloseNexusStagingRepository.kt @@ -50,7 +50,7 @@ constructor(objects: ObjectFactory, extension: NexusPublishExtension, repository val client = NexusClient(repository.get().nexusUrl.get(), repository.get().username.orNull, repository.get().password.orNull, clientTimeout.orNull, connectTimeout.orNull) val repositoryTransitioner = StagingRepositoryTransitioner(client, BasicActionRetrier.retryUntilRepoTransitionIsCompletedRetrier(repository.get().retrying.get())) logger.info("Closing staging repository with id '{}'", stagingRepositoryId.get()) - repositoryTransitioner.effectivelyClose(stagingRepositoryId.get()) + repositoryTransitioner.effectivelyClose(stagingRepositoryId.get(), description.get()) logger.info("Repository with id '{}' effectively closed", stagingRepositoryId.get()) } } diff --git a/src/main/kotlin/io/github/gradlenexus/publishplugin/InitializeNexusStagingRepository.kt b/src/main/kotlin/io/github/gradlenexus/publishplugin/InitializeNexusStagingRepository.kt index 4b5e57ba..b59019ef 100644 --- a/src/main/kotlin/io/github/gradlenexus/publishplugin/InitializeNexusStagingRepository.kt +++ b/src/main/kotlin/io/github/gradlenexus/publishplugin/InitializeNexusStagingRepository.kt @@ -49,7 +49,7 @@ open class InitializeNexusStagingRepository @Inject constructor( val client = NexusClient(serverUrl, repository.username.orNull, repository.password.orNull, clientTimeout.orNull, connectTimeout.orNull) val stagingProfileId = determineStagingProfileId(repository, client) logger.info("Creating staging repository for {} at {}, stagingProfileId '{}'", repository.name, serverUrl, stagingProfileId) - val descriptor = client.createStagingRepository(stagingProfileId) + val descriptor = client.createStagingRepository(stagingProfileId, description.get()) val consumerUrl = HttpUrl.get(serverUrl)!!.newBuilder().addEncodedPathSegments("repositories/${descriptor.stagingRepositoryId}/content/").build() logger.lifecycle("Created staging repository '{}' at {}", descriptor.stagingRepositoryId, consumerUrl) registry.get()[repository.name] = descriptor diff --git a/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishExtension.kt b/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishExtension.kt index 2820fbc9..7a7b670a 100644 --- a/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishExtension.kt +++ b/src/main/kotlin/io/github/gradlenexus/publishplugin/NexusPublishExtension.kt @@ -38,6 +38,10 @@ open class NexusPublishExtension(project: Project) { set(project.provider { project.group.toString() }) } + val description = project.objects.property().apply { + set(project.provider { project.run { "$group:$name:$version" } }) + } + val clientTimeout = project.objects.property().apply { set(Duration.ofMinutes(1)) } diff --git a/src/main/kotlin/io/github/gradlenexus/publishplugin/ReleaseNexusStagingRepository.kt b/src/main/kotlin/io/github/gradlenexus/publishplugin/ReleaseNexusStagingRepository.kt index 6ea957ba..3c317df4 100644 --- a/src/main/kotlin/io/github/gradlenexus/publishplugin/ReleaseNexusStagingRepository.kt +++ b/src/main/kotlin/io/github/gradlenexus/publishplugin/ReleaseNexusStagingRepository.kt @@ -49,7 +49,7 @@ constructor(objects: ObjectFactory, extension: NexusPublishExtension, repository val client = NexusClient(repository.get().nexusUrl.get(), repository.get().username.orNull, repository.get().password.orNull, clientTimeout.orNull, connectTimeout.orNull) val repositoryTransitioner = StagingRepositoryTransitioner(client, BasicActionRetrier.retryUntilRepoTransitionIsCompletedRetrier(repository.get().retrying.get())) logger.info("Releasing staging repository with id '{}'", stagingRepositoryId.get()) - repositoryTransitioner.effectivelyRelease(stagingRepositoryId.get()) + repositoryTransitioner.effectivelyRelease(stagingRepositoryId.get(), description.get()) logger.info("Repository with id '{}' effectively released", stagingRepositoryId.get()) } } diff --git a/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/NexusClient.kt b/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/NexusClient.kt index 24ba685d..eb26e0d3 100644 --- a/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/NexusClient.kt +++ b/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/NexusClient.kt @@ -54,6 +54,12 @@ open class NexusClient(private val baseUrl: URI, username: String?, password: St .build()) } } + httpClientBuilder.addInterceptor { chain -> + val version = javaClass.`package`.implementationVersion ?: "dev" + chain.proceed(chain.request().newBuilder() + .header("User-Agent", "gradle-nexus-publish-plugin/$version") + .build()) + } val retrofit = Retrofit.Builder() .baseUrl(baseUrl.toString()) .client(httpClientBuilder.build()) @@ -80,8 +86,8 @@ open class NexusClient(private val baseUrl: URI, username: String?, password: St ?.id } - fun createStagingRepository(stagingProfileId: String): StagingRepositoryDescriptor { - val response = api.startStagingRepo(stagingProfileId, Dto(Description("Created by io.github.gradle-nexus.publish-plugin Gradle plugin"))).execute() + fun createStagingRepository(stagingProfileId: String, description: String): StagingRepositoryDescriptor { + val response = api.startStagingRepo(stagingProfileId, Dto(Description(description))).execute() if (!response.isSuccessful) { throw failure("create staging repository", response) } @@ -89,15 +95,15 @@ open class NexusClient(private val baseUrl: URI, username: String?, password: St return StagingRepositoryDescriptor(baseUrl, stagingRepositoryId) } - open fun closeStagingRepository(stagingRepositoryId: String) { - val response = api.closeStagingRepo(Dto(StagingRepositoryToTransit(listOf(stagingRepositoryId), "Closed by io.github.gradle-nexus.publish-plugin Gradle plugin"))).execute() + open fun closeStagingRepository(stagingRepositoryId: String, description: String) { + val response = api.closeStagingRepo(Dto(StagingRepositoryToTransit(listOf(stagingRepositoryId), description))).execute() if (!response.isSuccessful) { throw failure("close staging repository", response) } } - open fun releaseStagingRepository(stagingRepositoryId: String) { - val response = api.releaseStagingRepo(Dto(StagingRepositoryToTransit(listOf(stagingRepositoryId), "Release by io.github.gradle-nexus.publish-plugin Gradle plugin"))).execute() + open fun releaseStagingRepository(stagingRepositoryId: String, description: String) { + val response = api.releaseStagingRepo(Dto(StagingRepositoryToTransit(listOf(stagingRepositoryId), description))).execute() if (!response.isSuccessful) { throw failure("release staging repository", response) } diff --git a/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepositoryTransitioner.kt b/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepositoryTransitioner.kt index 08127e3b..96e4811f 100644 --- a/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepositoryTransitioner.kt +++ b/src/main/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepositoryTransitioner.kt @@ -26,13 +26,13 @@ class StagingRepositoryTransitioner(val nexusClient: NexusClient, val retrier: A private val log: Logger = LoggerFactory.getLogger(StagingRepositoryTransitioner::class.java.simpleName) } - fun effectivelyClose(repoId: String) { - effectivelyChangeState(repoId, StagingRepository.State.CLOSED, nexusClient::closeStagingRepository) + fun effectivelyClose(repoId: String, description: String) { + effectivelyChangeState(repoId, StagingRepository.State.CLOSED) { nexusClient.closeStagingRepository(it, description) } } //TODO: Add support for autoDrop=false - fun effectivelyRelease(repoId: String) { - effectivelyChangeState(repoId, StagingRepository.State.NOT_FOUND, nexusClient::releaseStagingRepository) + fun effectivelyRelease(repoId: String, description: String) { + effectivelyChangeState(repoId, StagingRepository.State.NOT_FOUND) { nexusClient.releaseStagingRepository(it, description) } } private fun effectivelyChangeState(repoId: String, desiredState: StagingRepository.State, transitionClientRequest: (String) -> Unit) { diff --git a/src/test/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepositoryTransitionerTest.kt b/src/test/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepositoryTransitionerTest.kt index 1ebb8937..37861d54 100644 --- a/src/test/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepositoryTransitionerTest.kt +++ b/src/test/kotlin/io/github/gradlenexus/publishplugin/internal/StagingRepositoryTransitionerTest.kt @@ -33,6 +33,7 @@ internal class StagingRepositoryTransitionerTest { companion object { private const val TEST_STAGING_REPO_ID = "orgexample-42" + private const val DESCRIPTION = "some description" } @Mock @@ -53,10 +54,10 @@ internal class StagingRepositoryTransitionerTest { .willReturn(StagingRepository(TEST_STAGING_REPO_ID, StagingRepository.State.CLOSED, false)) given(retrier.execute(anyOrNull())).willAnswer(executeFunctionPassedAsFirstArgument()) - transitioner.effectivelyClose(TEST_STAGING_REPO_ID) + transitioner.effectivelyClose(TEST_STAGING_REPO_ID, DESCRIPTION) val inOrder = inOrder(nexusClient, retrier) - inOrder.verify(nexusClient).closeStagingRepository(TEST_STAGING_REPO_ID) + inOrder.verify(nexusClient).closeStagingRepository(TEST_STAGING_REPO_ID, DESCRIPTION) inOrder.verify(nexusClient).getStagingRepositoryStateById(TEST_STAGING_REPO_ID) } @@ -66,10 +67,10 @@ internal class StagingRepositoryTransitionerTest { .willReturn(StagingRepository(TEST_STAGING_REPO_ID, StagingRepository.State.NOT_FOUND, false)) given(retrier.execute(anyOrNull())).willAnswer(executeFunctionPassedAsFirstArgument()) - transitioner.effectivelyRelease(TEST_STAGING_REPO_ID) + transitioner.effectivelyRelease(TEST_STAGING_REPO_ID, DESCRIPTION) val inOrder = inOrder(nexusClient, retrier) - inOrder.verify(nexusClient).releaseStagingRepository(TEST_STAGING_REPO_ID) + inOrder.verify(nexusClient).releaseStagingRepository(TEST_STAGING_REPO_ID, DESCRIPTION) inOrder.verify(nexusClient).getStagingRepositoryStateById(TEST_STAGING_REPO_ID) } @@ -80,7 +81,7 @@ internal class StagingRepositoryTransitionerTest { given(retrier.execute(anyOrNull())).willAnswer(executeFunctionPassedAsFirstArgument()) assertThatExceptionOfType(RepositoryTransitionException::class.java) - .isThrownBy { transitioner.effectivelyClose(TEST_STAGING_REPO_ID) } + .isThrownBy { transitioner.effectivelyClose(TEST_STAGING_REPO_ID, DESCRIPTION) } .withMessageContainingAll(TEST_STAGING_REPO_ID, "transitioning=true") } @@ -91,7 +92,7 @@ internal class StagingRepositoryTransitionerTest { given(retrier.execute(anyOrNull())).willAnswer(executeFunctionPassedAsFirstArgument()) assertThatExceptionOfType(RepositoryTransitionException::class.java) - .isThrownBy { transitioner.effectivelyClose(TEST_STAGING_REPO_ID) } + .isThrownBy { transitioner.effectivelyClose(TEST_STAGING_REPO_ID, DESCRIPTION) } .withMessageContainingAll(TEST_STAGING_REPO_ID, StagingRepository.State.OPEN.toString(), StagingRepository.State.CLOSED.toString()) }