This is an automated email from the ASF dual-hosted git repository.
nodece pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar.git
The following commit(s) were added to refs/heads/master by this push:
new 4b968eb2fcf [improve][build] Add ASF Nexus publishing repositories to
publish conventions (#26004)
4b968eb2fcf is described below
commit 4b968eb2fcff7bd8cd794c3eb1c39b6ac243b9bd
Author: Lari Hotari <[email protected]>
AuthorDate: Fri Jun 12 11:46:37 2026 +0300
[improve][build] Add ASF Nexus publishing repositories to publish
conventions (#26004)
---
.../kotlin/pulsar.publish-conventions.gradle.kts | 114 +++++++++++++++++++--
1 file changed, 104 insertions(+), 10 deletions(-)
diff --git
a/build-logic/conventions/src/main/kotlin/pulsar.publish-conventions.gradle.kts
b/build-logic/conventions/src/main/kotlin/pulsar.publish-conventions.gradle.kts
index eae72273f31..5da26aaac64 100644
---
a/build-logic/conventions/src/main/kotlin/pulsar.publish-conventions.gradle.kts
+++
b/build-logic/conventions/src/main/kotlin/pulsar.publish-conventions.gradle.kts
@@ -19,7 +19,8 @@
// Convention plugin for publishing Pulsar modules to Maven repositories.
// Configures maven-publish, GPG signing, POM metadata, sources/javadoc JARs,
-// and a local deploy repository for testing.
+// the ASF Nexus release/snapshot repositories, and a local deploy repository
+// for testing.
plugins {
`maven-publish`
@@ -86,25 +87,33 @@ run {
// Capture values in a local scope so withXml closures don't capture the
script object
// (which would break configuration cache serialization)
val projectName = project.name
- val projectDescription = project.description
val archivesNameValue = the<BasePluginExtension>().archivesName.get()
val isPlatformProject = plugins.hasPlugin("java-platform")
val isRootProject = project == rootProject
val pulsarVersion = version.toString()
val localDeployRepoDir =
rootProject.layout.buildDirectory.dir("local-deploy-repo")
+ // Per-module POM name and description. Read in afterEvaluate so that a
description
+ // assigned in a module's build script body is picked up, and captured as
plain strings
+ // so the pom configuration stays configuration-cache compatible.
+ if (!isRootProject) {
+ afterEvaluate {
+ val projectDescription = project.description ?: "Apache Pulsar ::
$projectName"
+ publishing.publications.withType<MavenPublication>().configureEach
{
+ pom {
+ name.set(projectDescription)
+ description.set(projectDescription)
+ }
+ }
+ }
+ }
+
publishing {
publications {
withType<MavenPublication>().configureEach {
artifactId = archivesNameValue
pom {
- // Per-module name and description
- if (!isRootProject) {
- name.set("Apache Pulsar :: $projectName")
- description.set(projectDescription ?: "Apache Pulsar
:: $projectName")
- }
-
// Clean up POM XML and inject <parent> reference
withXml {
val sb = asString()
@@ -153,6 +162,88 @@ run {
}
}
+// --- Apache distribution repositories (ASF Nexus) ---
+// Repository names follow the ASF parent POM (apache.releases.https /
apache.snapshots.https).
+// Publish with one of:
+// ./gradlew publishAllPublicationsToApacheSnapshotsRepository (for
-SNAPSHOT versions)
+// ./gradlew publishAllPublicationsToApacheReleasesRepository (for
release versions)
+// Releases must be published with --no-parallel: when uploading to the Apache
staging
+// repository, Nexus creates an implicit staging repository, and concurrent
per-module uploads
+// can end up split across multiple implicitly-created staging repositories
instead of being
+// collected into a single one.
+// Credentials are resolved by Gradle at execution time from the
apacheReleasesUsername /
+// apacheReleasesPassword and apacheSnapshotsUsername /
apacheSnapshotsPassword Gradle properties
+// (the credentials(PasswordCredentials::class) form, which keeps the publish
tasks
+// configuration-cache compatible — explicitly assigned credentials would not
be). Pass them as
+// ORG_GRADLE_PROJECT_-prefixed environment variables on the publish command
line so the password
+// doesn't have to be stored in ~/.gradle/gradle.properties where it could
leak to unrelated
+// builds; start the command line with a space to keep the password out of
shell history:
+// ORG_GRADLE_PROJECT_apacheReleasesUsername=$APACHE_USER \
+// ORG_GRADLE_PROJECT_apacheReleasesPassword="<your ASF password>" \
+// ./gradlew publishAllPublicationsToApacheReleasesRepository --no-parallel
...
+// The URLs can be overridden with the apacheReleasesRepoUrl /
apacheSnapshotsRepoUrl Gradle
+// properties (e.g. a file:// URL for testing the publication layout).
+run {
+ fun MavenArtifactRepository.configureApacheRepository(urlProperty: String,
defaultUrl: String) {
+ val repositoryUrl =
uri(providers.gradleProperty(urlProperty).getOrElse(defaultUrl))
+ url = repositoryUrl
+ // The file transport (an URL overridden to file:// for testing)
rejects credentials,
+ // and Gradle's credentials validation would fail when the properties
aren't set.
+ if (repositoryUrl.scheme != "file") {
+ credentials(PasswordCredentials::class)
+ }
+ }
+
+ publishing {
+ repositories {
+ maven {
+ name = "apacheReleases"
+ configureApacheRepository(
+ "apacheReleasesRepoUrl",
+
"https://repository.apache.org/service/local/staging/deploy/maven2"
+ )
+ }
+ maven {
+ name = "apacheSnapshots"
+ configureApacheRepository(
+ "apacheSnapshotsRepoUrl",
+
"https://repository.apache.org/content/repositories/snapshots"
+ )
+ }
+ }
+ }
+
+ // Validate before any upload: only -SNAPSHOT versions may go to
apacheSnapshots and only
+ // release versions to apacheReleases. (Maven's deploy picks the
repository from the version;
+ // in Gradle the task name picks the repository, so the version must be
checked instead.)
+ // The task's repository property is discarded by configuration cache
serialization, so
+ // capture the repository name at configuration time and only register the
validation
+ // action (capturing plain strings/booleans) for the Apache repositories.
+ val projectVersion = version.toString()
+ val isSnapshotVersion = projectVersion.endsWith("-SNAPSHOT")
+ tasks.withType<PublishToMavenRepository>().configureEach {
+ val repositoryName = repository.name
+ if (repositoryName == "apacheReleases" || repositoryName ==
"apacheSnapshots") {
+ doFirst {
+ if (repositoryName == "apacheSnapshots" && !isSnapshotVersion)
{
+ throw GradleException(
+ "Refusing to publish non-snapshot version
'$projectVersion' to the " +
+ "'apacheSnapshots' repository. Use " +
+ "publishAllPublicationsToApacheReleasesRepository
for release versions."
+ )
+ }
+ if (repositoryName == "apacheReleases" && isSnapshotVersion) {
+ throw GradleException(
+ "Refusing to publish snapshot version
'$projectVersion' to the " +
+ "'apacheReleases' repository. Use " +
+ "publishAllPublicationsToApacheSnapshotsRepository
for -SNAPSHOT versions."
+ )
+ }
+ }
+ }
+ }
+}
+
// --- GPG signing ---
signing {
isRequired = !version.toString().endsWith("-SNAPSHOT")
@@ -165,10 +256,13 @@ signing {
sign(publishing.publications)
}
-// Disable signing tasks when no key is configured (local dev without signing)
+// Disable signing tasks when no signing configuration is present (local dev
without signing).
+// With -PuseGpgCmd=true, an explicit key isn't needed: the gpg command uses
its default key
+// unless -Psigning.gnupg.keyName=<key id> selects one.
tasks.withType<Sign>().configureEach {
enabled = providers.gradleProperty("signing.keyId").isPresent ||
- providers.gradleProperty("signing.gnupg.keyName").isPresent
+ providers.gradleProperty("signing.gnupg.keyName").isPresent ||
+ (providers.gradleProperty("useGpgCmd").orNull?.toBoolean() ?: false)
}
// Suppress enforced-platform validation: all java-library modules use