This is an automated email from the ASF dual-hosted git repository.

lhotari 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 7280786066d [fix][build] Fix build caching for shadow jars and 
refactor to use convention plugins (#25409)
7280786066d is described below

commit 7280786066de5791d4eb685f97bc0b92489b13c8
Author: Lari Hotari <[email protected]>
AuthorDate: Fri Mar 27 16:48:24 2026 +0200

    [fix][build] Fix build caching for shadow jars and refactor to use 
convention plugins (#25409)
---
 .github/workflows/pulsar-ci.yaml                   |  10 ++
 .gitignore                                         |   1 +
 build.gradle.kts                                   |  14 --
 {microbench => buildSrc}/build.gradle.kts          |  24 ++--
 .../settings.gradle.kts                            |  25 +---
 .../src/main/kotlin/PulsarShadeUtils.kt            |  25 ++--
 .../pulsar.client-shade-conventions.gradle.kts     | 115 +++++++--------
 .../kotlin/pulsar.shadow-conventions.gradle.kts    |  39 +++--
 gradle/code-quality.gradle.kts                     |  14 +-
 jclouds-shaded/build.gradle.kts                    |  27 +---
 .../zookeeper-with-patched-admin/build.gradle.kts  |  22 +--
 microbench/build.gradle.kts                        |   2 +-
 pulsar-client-admin-shaded/build.gradle.kts        | 142 +-----------------
 pulsar-client-all/build.gradle.kts                 | 159 +--------------------
 .../build.gradle.kts                               |  24 +---
 pulsar-client-shaded/build.gradle.kts              | 144 +------------------
 pulsar-functions/localrun-shaded/build.gradle.kts  | 135 +++++++++--------
 .../java-test-functions/build.gradle.kts           |   7 +-
 .../build.gradle.kts                               |   3 +-
 .../pulsar-client-all-shade-test/build.gradle.kts  |   3 +-
 tests/pulsar-client-shade-test/build.gradle.kts    |   5 +-
 21 files changed, 190 insertions(+), 750 deletions(-)

diff --git a/.github/workflows/pulsar-ci.yaml b/.github/workflows/pulsar-ci.yaml
index 65f54e72656..5a1b04afe78 100644
--- a/.github/workflows/pulsar-ci.yaml
+++ b/.github/workflows/pulsar-ci.yaml
@@ -193,6 +193,16 @@ jobs:
         run: |
           src/check-binary-license.sh 
./distribution/server/build/distributions/apache-pulsar-*-bin.tar.gz
 
+      - name: Upload Gradle reports
+        uses: actions/upload-artifact@v4
+        if: ${{ !success() }}
+        with:
+          name: gradle-reports-build-and-license-check
+          path: |
+            **/build/reports/
+          retention-days: 7
+          if-no-files-found: ignore
+
       - name: Save build outputs for test jobs
         run: |
           # Tar up compiled classes, resources, and distribution archives so 
test jobs
diff --git a/.gitignore b/.gitignore
index d84a16c66df..20dba346284 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,6 +89,7 @@ docker.debug-info
 
 # Gradle
 .gradle/
+.kotlin/
 build/
 
 # Avro
diff --git a/build.gradle.kts b/build.gradle.kts
index bce8b013914..5a9f4322a30 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -214,20 +214,6 @@ subprojects {
     }
     artifacts.add("testJar", testJar)
 
-    // Shadow JAR modules: expose the shadow JAR as a consumable configuration 
so other
-    // projects can depend on it via project(path = "...", configuration = 
"shadowElements")
-    pluginManager.withPlugin("com.gradleup.shadow") {
-        val shadowElements by configurations.creating {
-            isCanBeConsumed = true
-            isCanBeResolved = false
-            attributes {
-                attribute(Usage.USAGE_ATTRIBUTE, 
objects.named(Usage.JAVA_RUNTIME))
-                attribute(Bundling.BUNDLING_ATTRIBUTE, 
objects.named(Bundling.SHADOWED))
-            }
-        }
-        artifacts.add("shadowElements", tasks.named("shadowJar"))
-    }
-
     // NAR modules should not bundle Pulsar platform dependencies — they are 
provided
     // at runtime by Pulsar's classloader hierarchy.
     // Note: pulsar-io-common is NOT in java-instance.jar (runtime-all), so it 
must be
diff --git a/microbench/build.gradle.kts b/buildSrc/build.gradle.kts
similarity index 60%
copy from microbench/build.gradle.kts
copy to buildSrc/build.gradle.kts
index f597575985b..22c18f1d18f 100644
--- a/microbench/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -18,24 +18,16 @@
  */
 
 plugins {
-    alias(libs.plugins.shadow)
+    `kotlin-dsl`
 }
 
-dependencies {
-    implementation(project(":managed-ledger"))
-    implementation(project(":pulsar-common"))
-    implementation(project(":pulsar-broker"))
-    implementation(libs.bookkeeper.server)
-    implementation(libs.guava)
-    implementation(libs.slf4j.api)
-    implementation("org.openjdk.jmh:jmh-core:1.37")
-    annotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.37")
+repositories {
+    gradlePluginPortal()
+    mavenCentral()
 }
 
-tasks.shadowJar {
-    archiveClassifier.set("benchmarks")
-    isZip64 = true
-    manifest {
-        attributes("Main-Class" to "org.openjdk.jmh.Main")
-    }
+dependencies {
+    implementation(libs.plugins.shadow.get().let {
+        "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}"
+    })
 }
diff --git a/microbench/build.gradle.kts b/buildSrc/settings.gradle.kts
similarity index 58%
copy from microbench/build.gradle.kts
copy to buildSrc/settings.gradle.kts
index f597575985b..17ff66638a8 100644
--- a/microbench/build.gradle.kts
+++ b/buildSrc/settings.gradle.kts
@@ -17,25 +17,10 @@
  * under the License.
  */
 
-plugins {
-    alias(libs.plugins.shadow)
-}
-
-dependencies {
-    implementation(project(":managed-ledger"))
-    implementation(project(":pulsar-common"))
-    implementation(project(":pulsar-broker"))
-    implementation(libs.bookkeeper.server)
-    implementation(libs.guava)
-    implementation(libs.slf4j.api)
-    implementation("org.openjdk.jmh:jmh-core:1.37")
-    annotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.37")
-}
-
-tasks.shadowJar {
-    archiveClassifier.set("benchmarks")
-    isZip64 = true
-    manifest {
-        attributes("Main-Class" to "org.openjdk.jmh.Main")
+dependencyResolutionManagement {
+    versionCatalogs {
+        create("libs") {
+            from(files("../gradle/libs.versions.toml"))
+        }
     }
 }
diff --git a/microbench/build.gradle.kts 
b/buildSrc/src/main/kotlin/PulsarShadeUtils.kt
similarity index 58%
copy from microbench/build.gradle.kts
copy to buildSrc/src/main/kotlin/PulsarShadeUtils.kt
index f597575985b..3e735df4018 100644
--- a/microbench/build.gradle.kts
+++ b/buildSrc/src/main/kotlin/PulsarShadeUtils.kt
@@ -17,25 +17,16 @@
  * under the License.
  */
 
-plugins {
-    alias(libs.plugins.shadow)
-}
+import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
 
-dependencies {
-    implementation(project(":managed-ledger"))
-    implementation(project(":pulsar-common"))
-    implementation(project(":pulsar-broker"))
-    implementation(libs.bookkeeper.server)
-    implementation(libs.guava)
-    implementation(libs.slf4j.api)
-    implementation("org.openjdk.jmh:jmh-core:1.37")
-    annotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:1.37")
+/** Relocate a package under the shade prefix: `"x.y"` → `"$prefix.x.y"` */
+fun ShadowJar.relocateWithPrefix(prefix: String, pattern: String) {
+    relocate(pattern, "$prefix.$pattern")
 }
 
-tasks.shadowJar {
-    archiveClassifier.set("benchmarks")
-    isZip64 = true
-    manifest {
-        attributes("Main-Class" to "org.openjdk.jmh.Main")
+/** Fix ahc-default.properties to use the relocated asynchttpclient package 
name. */
+fun ShadowJar.relocateAsyncHttpClientProperties(prefix: String) {
+    filesMatching("org/asynchttpclient/config/ahc-default.properties") {
+        filter { line -> line.replace("org.asynchttpclient.", 
"$prefix.org.asynchttpclient.") }
     }
 }
diff --git a/pulsar-client-all/build.gradle.kts 
b/buildSrc/src/main/kotlin/pulsar.client-shade-conventions.gradle.kts
similarity index 64%
copy from pulsar-client-all/build.gradle.kts
copy to buildSrc/src/main/kotlin/pulsar.client-shade-conventions.gradle.kts
index 88f01d3ac6d..04da28be1a7 100644
--- a/pulsar-client-all/build.gradle.kts
+++ b/buildSrc/src/main/kotlin/pulsar.client-shade-conventions.gradle.kts
@@ -17,30 +17,21 @@
  * under the License.
  */
 
-plugins {
-    alias(libs.plugins.shadow)
-}
-
-dependencies {
-    implementation(project(":pulsar-client-api"))
-    implementation(project(":pulsar-client-original")) {
-        exclude(group = "it.unimi.dsi", module = "fastutil")
-    }
-    implementation(project(":pulsar-client-dependencies-minimized"))
-    implementation(project(":pulsar-client-admin-original"))
-    implementation(project(":pulsar-client-messagecrypto-bc"))
+// Convention plugin for Pulsar client shaded modules (pulsar-client-shaded,
+// pulsar-client-admin-shaded, pulsar-client-all). Configures the shadow jar
+// with the shared dependency includes, file excludes, relocations, and
+// filesMatching blocks. Modules only need to define their project 
dependencies.
 
-    testImplementation(libs.log4j.api)
-    testImplementation(libs.log4j.core)
-    testImplementation(libs.log4j.slf4j2.impl)
+plugins {
+    id("pulsar.shadow-conventions")
 }
 
 val shadePrefix = "org.apache.pulsar.shade"
+extra["shadePrefix"] = shadePrefix
 
-tasks.shadowJar {
-    archiveClassifier.set("")
-    mergeServiceFiles()
+tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar")
 {
 
+    // ---- Dependency includes ----
     dependencies {
         include(project(":pulsar-client-original"))
         include(project(":pulsar-client-admin-original"))
@@ -69,6 +60,7 @@ tasks.shadowJar {
         include(dependency("io.netty:.*"))
         include(dependency("io.opencensus:.*"))
         include(dependency("io.perfmark:.*"))
+        include(dependency("io.prometheus:.*"))
         include(dependency("io.swagger:.*"))
         include(dependency("jakarta.activation:jakarta.activation-api"))
         include(dependency("jakarta.annotation:jakarta.annotation-api"))
@@ -92,14 +84,15 @@ tasks.shadowJar {
         include(dependency("org.tukaani:xz"))
         include(dependency("org.yaml:snakeyaml"))
         include(dependency("org.roaringbitmap:RoaringBitmap"))
+        include(dependency("com.github.ben-manes.caffeine:.*"))
         exclude(dependency("com.fasterxml.jackson.core:jackson-annotations"))
         exclude(dependency("com.google.protobuf:protobuf-java"))
         exclude(dependency("io.netty:netty-transport-native-kqueue"))
     }
 
-    // Exclude bouncycastle from pulsar-client (signatures would break if 
shaded)
+    // ---- File excludes ----
+    // Exclude bouncycastle (signatures would break if shaded)
     exclude("org/bouncycastle/**")
-    // Exclude common META-INF noise
     exclude("**/module-info.class")
     exclude("findbugsExclude.xml")
     exclude("META-INF/*-LICENSE")
@@ -116,34 +109,13 @@ tasks.shadowJar {
     exclude("META-INF/NOTICE*")
     exclude("META-INF/proguard/**")
 
+    // ---- Relocations ----
     relocate("com.fasterxml.jackson", "$shadePrefix.com.fasterxml.jackson") {
         exclude("com.fasterxml.jackson.annotation.*")
     }
     relocate("com.google", "$shadePrefix.com.google") {
         exclude("com.google.protobuf.**")
     }
-    relocate("com.spotify.futures", "$shadePrefix.com.spotify.futures")
-    relocate("com.squareup", "$shadePrefix.com.squareup")
-    relocate("com.sun.activation", "$shadePrefix.com.sun.activation")
-    relocate("com.thoughtworks.paranamer", 
"$shadePrefix.com.thoughtworks.paranamer")
-    relocate("com.typesafe", "$shadePrefix.com.typesafe")
-    relocate("com.yahoo", "$shadePrefix.com.yahoo")
-    relocate("com.github.benmanes", "$shadePrefix.com.github.benmanes")
-    relocate("io.airlift", "$shadePrefix.io.airlift")
-    relocate("io.grpc", "$shadePrefix.io.grpc")
-    relocate("io.netty", "$shadePrefix.io.netty")
-    relocate("io.opencensus", "$shadePrefix.io.opencensus")
-    relocate("io.swagger", "$shadePrefix.io.swagger")
-    relocate("it.unimi.dsi.fastutil", "$shadePrefix.it.unimi.dsi.fastutil")
-    relocate("javassist", "$shadePrefix.javassist")
-    relocate("javax.activation", "$shadePrefix.javax.activation")
-    relocate("javax.annotation", "$shadePrefix.javax.annotation")
-    relocate("javax.inject", "$shadePrefix.javax.inject")
-    relocate("javax.ws", "$shadePrefix.javax.ws")
-    relocate("javax.xml.bind", "$shadePrefix.javax.xml.bind")
-    relocate("jersey", "$shadePrefix.jersey")
-    relocate("okio", "$shadePrefix.okio")
-    relocate("org.aopalliance", "$shadePrefix.org.aopalliance")
     relocate("org.apache.avro", "$shadePrefix.org.apache.avro") {
         exclude("org.apache.avro.reflect.AvroAlias")
         exclude("org.apache.avro.reflect.AvroDefault")
@@ -156,34 +128,53 @@ tasks.shadowJar {
         exclude("org.apache.avro.reflect.Stringable")
         exclude("org.apache.avro.reflect.Union")
     }
-    relocate("org.apache.bookkeeper", "$shadePrefix.org.apache.bookkeeper")
-    relocate("org.apache.commons", "$shadePrefix.org.apache.commons")
     relocate("org.apache.pulsar.policies", 
"$shadePrefix.org.apache.pulsar.policies") {
         
exclude("org.apache.pulsar.policies.data.loadbalancer.LoadManagerReport")
         
exclude("org.apache.pulsar.policies.data.loadbalancer.NamespaceBundleStats")
         exclude("org.apache.pulsar.policies.data.loadbalancer.ResourceUsage")
         
exclude("org.apache.pulsar.policies.data.loadbalancer.ServiceLookupData")
     }
-    relocate("org.asynchttpclient", "$shadePrefix.org.asynchttpclient")
-    relocate("org.checkerframework", "$shadePrefix.org.checkerframework")
-    relocate("org.codehaus.jackson", "$shadePrefix.org.codehaus.jackson")
-    relocate("org.eclipse.jetty", "$shadePrefix.org.eclipse")
-    relocate("org.glassfish", "$shadePrefix.org.glassfish")
-    relocate("org.jvnet", "$shadePrefix.org.jvnet")
-    relocate("org.objenesis", "$shadePrefix.org.objenesis")
-    relocate("org.reactivestreams", "$shadePrefix.org.reactivestreams")
-    relocate("org.roaringbitmap", "$shadePrefix.org.roaringbitmap")
-    relocate("org.tukaani", "$shadePrefix.org.tukaani")
-    relocate("org.yaml", "$shadePrefix.org.yaml")
+    relocateWithPrefix(shadePrefix, "com.github.benmanes")
+    relocateWithPrefix(shadePrefix, "com.spotify.futures")
+    relocateWithPrefix(shadePrefix, "com.squareup")
+    relocateWithPrefix(shadePrefix, "com.sun.activation")
+    relocateWithPrefix(shadePrefix, "com.thoughtworks.paranamer")
+    relocateWithPrefix(shadePrefix, "com.typesafe")
+    relocateWithPrefix(shadePrefix, "com.yahoo")
+    relocateWithPrefix(shadePrefix, "io.airlift")
+    relocateWithPrefix(shadePrefix, "io.grpc")
+    relocateWithPrefix(shadePrefix, "io.netty")
+    relocateWithPrefix(shadePrefix, "io.opencensus")
+    relocateWithPrefix(shadePrefix, "io.prometheus.client")
+    relocateWithPrefix(shadePrefix, "io.swagger")
+    relocateWithPrefix(shadePrefix, "it.unimi.dsi.fastutil")
+    relocateWithPrefix(shadePrefix, "javassist")
+    relocateWithPrefix(shadePrefix, "javax.activation")
+    relocateWithPrefix(shadePrefix, "javax.annotation")
+    relocateWithPrefix(shadePrefix, "javax.inject")
+    relocateWithPrefix(shadePrefix, "javax.ws")
+    relocateWithPrefix(shadePrefix, "javax.xml.bind")
+    relocateWithPrefix(shadePrefix, "jersey")
+    relocateWithPrefix(shadePrefix, "okio")
+    relocateWithPrefix(shadePrefix, "org.aopalliance")
+    relocateWithPrefix(shadePrefix, "org.apache.bookkeeper")
+    relocateWithPrefix(shadePrefix, "org.apache.commons")
+    relocateWithPrefix(shadePrefix, "org.apache.pulsar.checksum")
+    relocateWithPrefix(shadePrefix, "org.asynchttpclient")
+    relocateWithPrefix(shadePrefix, "org.checkerframework")
+    relocateWithPrefix(shadePrefix, "org.codehaus.jackson")
+    relocateWithPrefix(shadePrefix, "org.eclipse.jetty")
+    relocateWithPrefix(shadePrefix, "org.glassfish")
+    relocateWithPrefix(shadePrefix, "org.jvnet")
+    relocateWithPrefix(shadePrefix, "org.objenesis")
+    relocateWithPrefix(shadePrefix, "org.reactivestreams")
+    relocateWithPrefix(shadePrefix, "org.roaringbitmap")
+    relocateWithPrefix(shadePrefix, "org.tukaani")
+    relocateWithPrefix(shadePrefix, "org.yaml")
     // NOTE: Do NOT shade log4j, otherwise logging won't work
 
-    // The shadow plugin's relocate() doesn't handle property file VALUES.
-    // AsyncHttpClient's ahc-default.properties contains property names 
prefixed with
-    // "org.asynchttpclient." which must be updated to the relocated package 
name.
-    filesMatching("org/asynchttpclient/config/ahc-default.properties") {
-        filter { line -> line.replace("org.asynchttpclient.", 
"org.apache.pulsar.shade.org.asynchttpclient.") }
-    }
-
+    // ---- File content transformations ----
+    relocateAsyncHttpClientProperties(shadePrefix)
     // Relocate Netty native library filenames to avoid conflicts with 
unshaded Netty
     filesMatching("META-INF/native/**") {
         if (name.matches(Regex("netty.+\\.(so|jnilib|dll)"))) {
diff --git a/jetty-upgrade/zookeeper-with-patched-admin/build.gradle.kts 
b/buildSrc/src/main/kotlin/pulsar.shadow-conventions.gradle.kts
similarity index 54%
copy from jetty-upgrade/zookeeper-with-patched-admin/build.gradle.kts
copy to buildSrc/src/main/kotlin/pulsar.shadow-conventions.gradle.kts
index c47827ac60f..87f2d29c5f9 100644
--- a/jetty-upgrade/zookeeper-with-patched-admin/build.gradle.kts
+++ b/buildSrc/src/main/kotlin/pulsar.shadow-conventions.gradle.kts
@@ -17,44 +17,37 @@
  * under the License.
  */
 
-plugins {
-    alias(libs.plugins.shadow)
-}
+// Convention plugin for modules using the Shadow plugin.
+// Applies the shadow plugin, disables the default jar task, and makes the
+// shadow jar the primary artifact for both runtimeElements and apiElements,
+// so plain project() dependencies resolve to the shadow jar.
 
-dependencies {
-    implementation(libs.zookeeper)
-    
implementation(project(":jetty-upgrade:pulsar-zookeeper-prometheus-metrics"))
-    implementation(libs.jetty.server)
-    implementation(libs.jetty.ee8.servlet)
-    compileOnly(libs.jackson.databind)
-    compileOnly(libs.spotbugs.annotations)
+plugins {
+    id("com.gradleup.shadow")
 }
 
-tasks.shadowJar {
+tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar")
 {
     archiveClassifier.set("")
-    // Only merge zookeeper dependency into the shadow jar (matching Maven 
shade artifactSet).
-    // The module's own classes (patched admin replacements) are included by 
default.
-    dependencies {
-        include(dependency("org.apache.zookeeper:zookeeper"))
-    }
+    mergeServiceFiles()
 }
 
-// Replace the default jar with the shadow jar for downstream consumers
-tasks.jar {
-    archiveClassifier.set("original")
+tasks.named<Jar>("jar") {
+    enabled = false
 }
 
 configurations {
-    runtimeElements {
+    named("runtimeElements") {
         outgoing {
             artifacts.clear()
-            artifact(tasks.shadowJar)
+            artifact(tasks.named("shadowJar"))
+            variants.clear()
         }
     }
-    apiElements {
+    named("apiElements") {
         outgoing {
             artifacts.clear()
-            artifact(tasks.shadowJar)
+            artifact(tasks.named("shadowJar"))
+            variants.clear()
         }
     }
 }
diff --git a/gradle/code-quality.gradle.kts b/gradle/code-quality.gradle.kts
index 977844797f8..43d5b15722d 100644
--- a/gradle/code-quality.gradle.kts
+++ b/gradle/code-quality.gradle.kts
@@ -37,6 +37,7 @@ subprojects {
         // Exclude generated source files (proto, lightproto, etc.)
         exclude { it.file.path.contains("/build/") }
         exclude { it.file.path.contains("/generated-lightproto/") }
+        exclude { it.file.path.contains("/generated-sources/") }
         // Match Maven exclusion: **/proto/*
         exclude("**/proto/*")
     }
@@ -147,12 +148,18 @@ tasks.named("rat").configure {
         "**/test-output/**",
         // Generated LightProto files
         "**/generated-lightproto/**",
+        // Generated source files (e.g. Protobuf, Avro)
+        "**/generated-sources/**",
+        // Local runtime data
+        "**/data/**",
+        "**/logs/**",
         // Hidden directories (AI tools, etc.)
         ".*/**",
-        // Gradle files
+        // Gradle/Kotlin files
         ".gradle/**",
         "gradle/wrapper/**",
         "**/.gradle/**",
+        "**/.kotlin/**",
         "**/gradle/wrapper/**",
         "gradlew",
         "gradlew.bat",
@@ -199,6 +206,7 @@ subprojects {
             source = project.files(source).asFileTree.matching {
                 exclude { it.file.path.contains("/build/") }
                 exclude { it.file.path.contains("/generated-lightproto/") }
+                exclude { it.file.path.contains("/generated-sources/") }
             }
         }
 
@@ -219,9 +227,13 @@ subprojects {
             "**/circe/**",
             "**/generated/**",
             "**/generated-lightproto/**",
+            "**/generated-sources/**",
             "**/zk-3.5-test-data/*",
             "**/*_pb2.py",
             "**/*_pb2_grpc.py",
+            "**/data/**",
+            "**/logs/**",
+            "**/.kotlin/**",
         ).forEach { excludeMethod.invoke(licenseExt, it) }
     }
 }
diff --git a/jclouds-shaded/build.gradle.kts b/jclouds-shaded/build.gradle.kts
index 9bad56a85a2..a14a5a9d922 100644
--- a/jclouds-shaded/build.gradle.kts
+++ b/jclouds-shaded/build.gradle.kts
@@ -18,7 +18,7 @@
  */
 
 plugins {
-    alias(libs.plugins.shadow)
+    id("pulsar.shadow-conventions")
 }
 
 // Pin guava to 32.x for jclouds compatibility — jclouds uses TypeToken from
@@ -47,9 +47,6 @@ dependencies {
 }
 
 tasks.shadowJar {
-    archiveClassifier.set("")
-    mergeServiceFiles()
-
     dependencies {
         include(dependency("com.google.guava:guava"))
         include(dependency("com.google.guava:failureaccess"))
@@ -81,25 +78,3 @@ tasks.shadowJar {
     // API (e.g. credentialsSupplier expects com.google.common.base.Supplier).
 }
 
-// Replace the default jar with the shadow jar for downstream consumers
-tasks.jar {
-    archiveClassifier.set("original")
-}
-
-// Replace the default jar artifact with the shadow jar for downstream 
consumers.
-// Consumers should use isTransitive=false to avoid pulling in guava/jclouds
-// separately (they're bundled in the shadow jar with guava 32.x).
-configurations {
-    runtimeElements {
-        outgoing {
-            artifacts.clear()
-            artifact(tasks.shadowJar)
-        }
-    }
-    apiElements {
-        outgoing {
-            artifacts.clear()
-            artifact(tasks.shadowJar)
-        }
-    }
-}
diff --git a/jetty-upgrade/zookeeper-with-patched-admin/build.gradle.kts 
b/jetty-upgrade/zookeeper-with-patched-admin/build.gradle.kts
index c47827ac60f..cde00ccfa4d 100644
--- a/jetty-upgrade/zookeeper-with-patched-admin/build.gradle.kts
+++ b/jetty-upgrade/zookeeper-with-patched-admin/build.gradle.kts
@@ -18,7 +18,7 @@
  */
 
 plugins {
-    alias(libs.plugins.shadow)
+    id("pulsar.shadow-conventions")
 }
 
 dependencies {
@@ -31,7 +31,6 @@ dependencies {
 }
 
 tasks.shadowJar {
-    archiveClassifier.set("")
     // Only merge zookeeper dependency into the shadow jar (matching Maven 
shade artifactSet).
     // The module's own classes (patched admin replacements) are included by 
default.
     dependencies {
@@ -39,22 +38,3 @@ tasks.shadowJar {
     }
 }
 
-// Replace the default jar with the shadow jar for downstream consumers
-tasks.jar {
-    archiveClassifier.set("original")
-}
-
-configurations {
-    runtimeElements {
-        outgoing {
-            artifacts.clear()
-            artifact(tasks.shadowJar)
-        }
-    }
-    apiElements {
-        outgoing {
-            artifacts.clear()
-            artifact(tasks.shadowJar)
-        }
-    }
-}
diff --git a/microbench/build.gradle.kts b/microbench/build.gradle.kts
index f597575985b..5d4e1304535 100644
--- a/microbench/build.gradle.kts
+++ b/microbench/build.gradle.kts
@@ -18,7 +18,7 @@
  */
 
 plugins {
-    alias(libs.plugins.shadow)
+    id("pulsar.shadow-conventions")
 }
 
 dependencies {
diff --git a/pulsar-client-admin-shaded/build.gradle.kts 
b/pulsar-client-admin-shaded/build.gradle.kts
index af4a7fee17b..39f9f01842e 100644
--- a/pulsar-client-admin-shaded/build.gradle.kts
+++ b/pulsar-client-admin-shaded/build.gradle.kts
@@ -18,7 +18,7 @@
  */
 
 plugins {
-    alias(libs.plugins.shadow)
+    id("pulsar.client-shade-conventions")
 }
 
 dependencies {
@@ -28,143 +28,3 @@ dependencies {
     implementation(project(":pulsar-client-dependencies-minimized"))
     implementation(project(":pulsar-client-messagecrypto-bc"))
 }
-
-val shadePrefix = "org.apache.pulsar.shade"
-
-tasks.shadowJar {
-    archiveClassifier.set("")
-    mergeServiceFiles()
-
-    dependencies {
-        include(project(":pulsar-client-original"))
-        include(project(":pulsar-client-admin-original"))
-        include(project(":pulsar-common"))
-        include(project(":pulsar-client-messagecrypto-bc"))
-        include(project(":pulsar-client-dependencies-minimized"))
-        include(dependency("com.fasterxml.jackson.*:.*"))
-        include(dependency("com.google.*:.*"))
-        include(dependency("com.google.auth:.*"))
-        include(dependency("com.google.code.gson:gson"))
-        include(dependency("com.google.guava:guava"))
-        include(dependency("com.google.re2j:re2j"))
-        include(dependency("com.spotify:completable-futures"))
-        include(dependency("com.squareup.*:.*"))
-        include(dependency("com.sun.activation:jakarta.activation"))
-        include(dependency("com.typesafe.netty:netty-reactive-streams"))
-        include(dependency("com.yahoo.datasketches:.*"))
-        include(dependency("commons-.*:.*"))
-        include(dependency("io.airlift:.*"))
-        include(dependency("io.grpc:.*"))
-        include(dependency("io.netty.incubator:.*"))
-        include(dependency("io.netty:.*"))
-        include(dependency("io.opencensus:.*"))
-        include(dependency("io.perfmark:.*"))
-        include(dependency("io.swagger:.*"))
-        include(dependency("jakarta.activation:jakarta.activation-api"))
-        include(dependency("jakarta.annotation:jakarta.annotation-api"))
-        include(dependency("jakarta.ws.rs:jakarta.ws.rs-api"))
-        include(dependency("jakarta.xml.bind:jakarta.xml.bind-api"))
-        include(dependency("javax.ws.rs:.*"))
-        include(dependency("javax.xml.bind:jaxb-api"))
-        include(dependency("org.apache.bookkeeper:.*"))
-        include(dependency("org.apache.commons:commons-compress"))
-        include(dependency("org.apache.commons:commons-lang3"))
-        include(dependency("org.asynchttpclient:.*"))
-        include(dependency("org.checkerframework:.*"))
-        include(dependency("org.eclipse.jetty:.*"))
-        include(dependency("org.glassfish.hk2.*:.*"))
-        include(dependency("org.glassfish.jersey.*:.*"))
-        include(dependency("org.javassist:javassist"))
-        include(dependency("org.jvnet.mimepull:.*"))
-        include(dependency("org.objenesis:.*"))
-        include(dependency("org.reactivestreams:reactive-streams"))
-        include(dependency("org.yaml:snakeyaml"))
-        include(dependency("org.roaringbitmap:RoaringBitmap"))
-        exclude(dependency("com.fasterxml.jackson.core:jackson-annotations"))
-        exclude(dependency("com.google.protobuf:protobuf-java"))
-        exclude(dependency("io.netty:netty-transport-native-kqueue"))
-    }
-
-    // Exclude bouncycastle from pulsar-client (signatures would break if 
shaded)
-    exclude("org/bouncycastle/**")
-    // Exclude common META-INF noise
-    exclude("**/module-info.class")
-    exclude("findbugsExclude.xml")
-    exclude("META-INF/*-LICENSE")
-    exclude("META-INF/*-NOTICE")
-    exclude("META-INF/*.DSA")
-    exclude("META-INF/*.RSA")
-    exclude("META-INF/*.SF")
-    exclude("META-INF/DEPENDENCIES*")
-    exclude("META-INF/io.netty.versions.properties")
-    exclude("META-INF/LICENSE*")
-    exclude("META-INF/license/**")
-    exclude("META-INF/maven/**")
-    exclude("META-INF/native-image/**")
-    exclude("META-INF/NOTICE*")
-    exclude("META-INF/proguard/**")
-
-    relocate("com.fasterxml.jackson", "$shadePrefix.com.fasterxml.jackson") {
-        exclude("com.fasterxml.jackson.annotation.*")
-    }
-    relocate("com.google", "$shadePrefix.com.google") {
-        exclude("com.google.protobuf.**")
-    }
-    relocate("com.spotify.futures", "$shadePrefix.com.spotify.futures")
-    relocate("com.squareup", "$shadePrefix.com.squareup")
-    relocate("com.sun.activation", "$shadePrefix.com.sun.activation")
-    relocate("com.typesafe", "$shadePrefix.com.typesafe")
-    relocate("com.yahoo", "$shadePrefix.com.yahoo")
-    relocate("com.github.benmanes", "$shadePrefix.com.github.benmanes")
-    relocate("io.airlift", "$shadePrefix.io.airlift")
-    relocate("io.grpc", "$shadePrefix.io.grpc")
-    relocate("io.netty", "$shadePrefix.io.netty")
-    relocate("io.opencensus", "$shadePrefix.io.opencensus")
-    relocate("io.swagger", "$shadePrefix.io.swagger")
-    relocate("io.prometheus.client", "$shadePrefix.io.prometheus.client")
-    relocate("it.unimi.dsi.fastutil", "$shadePrefix.it.unimi.dsi.fastutil")
-    relocate("javassist", "$shadePrefix.javassist")
-    relocate("javax.activation", "$shadePrefix.javax.activation")
-    relocate("javax.annotation", "$shadePrefix.javax.annotation")
-    relocate("javax.inject", "$shadePrefix.javax.inject")
-    relocate("javax.ws", "$shadePrefix.javax.ws")
-    relocate("javax.xml.bind", "$shadePrefix.javax.xml.bind")
-    relocate("jersey", "$shadePrefix.jersey")
-    relocate("okio", "$shadePrefix.okio")
-    relocate("org.aopalliance", "$shadePrefix.org.aopalliance")
-    relocate("org.apache.bookkeeper", "$shadePrefix.org.apache.bookkeeper")
-    relocate("org.apache.commons", "$shadePrefix.org.apache.commons")
-    relocate("org.apache.pulsar.checksum", 
"$shadePrefix.org.apache.pulsar.checksum")
-    relocate("org.apache.pulsar.policies", 
"$shadePrefix.org.apache.pulsar.policies") {
-        
exclude("org.apache.pulsar.policies.data.loadbalancer.LoadManagerReport")
-        
exclude("org.apache.pulsar.policies.data.loadbalancer.NamespaceBundleStats")
-        exclude("org.apache.pulsar.policies.data.loadbalancer.ResourceUsage")
-        
exclude("org.apache.pulsar.policies.data.loadbalancer.ServiceLookupData")
-    }
-    relocate("org.asynchttpclient", "$shadePrefix.org.asynchttpclient")
-    relocate("org.checkerframework", "$shadePrefix.org.checkerframework")
-    relocate("org.codehaus.jackson", "$shadePrefix.org.codehaus.jackson")
-    relocate("org.eclipse.jetty", "$shadePrefix.org.eclipse.jetty")
-    relocate("org.glassfish", "$shadePrefix.org.glassfish")
-    relocate("org.jvnet", "$shadePrefix.org.jvnet")
-    relocate("org.objenesis", "$shadePrefix.org.objenesis")
-    relocate("org.reactivestreams", "$shadePrefix.org.reactivestreams")
-    relocate("org.roaringbitmap", "$shadePrefix.org.roaringbitmap")
-    relocate("org.tukaani", "$shadePrefix.org.tukaani")
-    relocate("org.yaml", "$shadePrefix.org.yaml")
-    // NOTE: Do NOT shade log4j, otherwise logging won't work
-
-    // The shadow plugin's relocate() doesn't handle property file VALUES.
-    // AsyncHttpClient's ahc-default.properties contains property names 
prefixed with
-    // "org.asynchttpclient." which must be updated to the relocated package 
name.
-    filesMatching("org/asynchttpclient/config/ahc-default.properties") {
-        filter { line -> line.replace("org.asynchttpclient.", 
"org.apache.pulsar.shade.org.asynchttpclient.") }
-    }
-
-    // Relocate Netty native library filenames to avoid conflicts with 
unshaded Netty
-    filesMatching("META-INF/native/**") {
-        if (name.matches(Regex("netty.+\\.(so|jnilib|dll)"))) {
-            path = path.replace(name, "org_apache_pulsar_shade_$name")
-        }
-    }
-}
diff --git a/pulsar-client-all/build.gradle.kts 
b/pulsar-client-all/build.gradle.kts
index 88f01d3ac6d..d7961703409 100644
--- a/pulsar-client-all/build.gradle.kts
+++ b/pulsar-client-all/build.gradle.kts
@@ -18,7 +18,7 @@
  */
 
 plugins {
-    alias(libs.plugins.shadow)
+    id("pulsar.client-shade-conventions")
 }
 
 dependencies {
@@ -34,160 +34,3 @@ dependencies {
     testImplementation(libs.log4j.core)
     testImplementation(libs.log4j.slf4j2.impl)
 }
-
-val shadePrefix = "org.apache.pulsar.shade"
-
-tasks.shadowJar {
-    archiveClassifier.set("")
-    mergeServiceFiles()
-
-    dependencies {
-        include(project(":pulsar-client-original"))
-        include(project(":pulsar-client-admin-original"))
-        include(project(":pulsar-common"))
-        include(project(":pulsar-client-messagecrypto-bc"))
-        include(project(":pulsar-client-dependencies-minimized"))
-        include(dependency("com.fasterxml.jackson.*:.*"))
-        include(dependency("com.google.*:.*"))
-        include(dependency("com.google.auth:.*"))
-        include(dependency("com.google.code.findbugs:.*"))
-        include(dependency("com.google.code.gson:gson"))
-        include(dependency("com.google.errorprone:.*"))
-        include(dependency("com.google.guava:.*"))
-        include(dependency("com.google.j2objc:.*"))
-        include(dependency("com.google.re2j:re2j"))
-        include(dependency("com.spotify:completable-futures"))
-        include(dependency("com.squareup.*:.*"))
-        include(dependency("com.sun.activation:jakarta.activation"))
-        include(dependency("com.thoughtworks.paranamer:paranamer"))
-        include(dependency("com.typesafe.netty:netty-reactive-streams"))
-        include(dependency("com.yahoo.datasketches:.*"))
-        include(dependency("commons-.*:.*"))
-        include(dependency("io.airlift:.*"))
-        include(dependency("io.grpc:.*"))
-        include(dependency("io.netty.incubator:.*"))
-        include(dependency("io.netty:.*"))
-        include(dependency("io.opencensus:.*"))
-        include(dependency("io.perfmark:.*"))
-        include(dependency("io.swagger:.*"))
-        include(dependency("jakarta.activation:jakarta.activation-api"))
-        include(dependency("jakarta.annotation:jakarta.annotation-api"))
-        include(dependency("jakarta.ws.rs:jakarta.ws.rs-api"))
-        include(dependency("jakarta.xml.bind:jakarta.xml.bind-api"))
-        include(dependency("javax.ws.rs:.*"))
-        include(dependency("javax.xml.bind:jaxb-api"))
-        include(dependency("org.apache.avro:.*"))
-        include(dependency("org.apache.bookkeeper:.*"))
-        include(dependency("org.apache.commons:commons-compress"))
-        include(dependency("org.apache.commons:commons-lang3"))
-        include(dependency("org.asynchttpclient:.*"))
-        include(dependency("org.checkerframework:.*"))
-        include(dependency("org.eclipse.jetty:.*"))
-        include(dependency("org.glassfish.hk2.*:.*"))
-        include(dependency("org.glassfish.jersey.*:.*"))
-        include(dependency("org.javassist:javassist"))
-        include(dependency("org.jvnet.mimepull:.*"))
-        include(dependency("org.objenesis:.*"))
-        include(dependency("org.reactivestreams:reactive-streams"))
-        include(dependency("org.tukaani:xz"))
-        include(dependency("org.yaml:snakeyaml"))
-        include(dependency("org.roaringbitmap:RoaringBitmap"))
-        exclude(dependency("com.fasterxml.jackson.core:jackson-annotations"))
-        exclude(dependency("com.google.protobuf:protobuf-java"))
-        exclude(dependency("io.netty:netty-transport-native-kqueue"))
-    }
-
-    // Exclude bouncycastle from pulsar-client (signatures would break if 
shaded)
-    exclude("org/bouncycastle/**")
-    // Exclude common META-INF noise
-    exclude("**/module-info.class")
-    exclude("findbugsExclude.xml")
-    exclude("META-INF/*-LICENSE")
-    exclude("META-INF/*-NOTICE")
-    exclude("META-INF/*.DSA")
-    exclude("META-INF/*.RSA")
-    exclude("META-INF/*.SF")
-    exclude("META-INF/DEPENDENCIES*")
-    exclude("META-INF/io.netty.versions.properties")
-    exclude("META-INF/LICENSE*")
-    exclude("META-INF/license/**")
-    exclude("META-INF/maven/**")
-    exclude("META-INF/native-image/**")
-    exclude("META-INF/NOTICE*")
-    exclude("META-INF/proguard/**")
-
-    relocate("com.fasterxml.jackson", "$shadePrefix.com.fasterxml.jackson") {
-        exclude("com.fasterxml.jackson.annotation.*")
-    }
-    relocate("com.google", "$shadePrefix.com.google") {
-        exclude("com.google.protobuf.**")
-    }
-    relocate("com.spotify.futures", "$shadePrefix.com.spotify.futures")
-    relocate("com.squareup", "$shadePrefix.com.squareup")
-    relocate("com.sun.activation", "$shadePrefix.com.sun.activation")
-    relocate("com.thoughtworks.paranamer", 
"$shadePrefix.com.thoughtworks.paranamer")
-    relocate("com.typesafe", "$shadePrefix.com.typesafe")
-    relocate("com.yahoo", "$shadePrefix.com.yahoo")
-    relocate("com.github.benmanes", "$shadePrefix.com.github.benmanes")
-    relocate("io.airlift", "$shadePrefix.io.airlift")
-    relocate("io.grpc", "$shadePrefix.io.grpc")
-    relocate("io.netty", "$shadePrefix.io.netty")
-    relocate("io.opencensus", "$shadePrefix.io.opencensus")
-    relocate("io.swagger", "$shadePrefix.io.swagger")
-    relocate("it.unimi.dsi.fastutil", "$shadePrefix.it.unimi.dsi.fastutil")
-    relocate("javassist", "$shadePrefix.javassist")
-    relocate("javax.activation", "$shadePrefix.javax.activation")
-    relocate("javax.annotation", "$shadePrefix.javax.annotation")
-    relocate("javax.inject", "$shadePrefix.javax.inject")
-    relocate("javax.ws", "$shadePrefix.javax.ws")
-    relocate("javax.xml.bind", "$shadePrefix.javax.xml.bind")
-    relocate("jersey", "$shadePrefix.jersey")
-    relocate("okio", "$shadePrefix.okio")
-    relocate("org.aopalliance", "$shadePrefix.org.aopalliance")
-    relocate("org.apache.avro", "$shadePrefix.org.apache.avro") {
-        exclude("org.apache.avro.reflect.AvroAlias")
-        exclude("org.apache.avro.reflect.AvroDefault")
-        exclude("org.apache.avro.reflect.AvroEncode")
-        exclude("org.apache.avro.reflect.AvroIgnore")
-        exclude("org.apache.avro.reflect.AvroMeta")
-        exclude("org.apache.avro.reflect.AvroName")
-        exclude("org.apache.avro.reflect.AvroSchema")
-        exclude("org.apache.avro.reflect.Nullable")
-        exclude("org.apache.avro.reflect.Stringable")
-        exclude("org.apache.avro.reflect.Union")
-    }
-    relocate("org.apache.bookkeeper", "$shadePrefix.org.apache.bookkeeper")
-    relocate("org.apache.commons", "$shadePrefix.org.apache.commons")
-    relocate("org.apache.pulsar.policies", 
"$shadePrefix.org.apache.pulsar.policies") {
-        
exclude("org.apache.pulsar.policies.data.loadbalancer.LoadManagerReport")
-        
exclude("org.apache.pulsar.policies.data.loadbalancer.NamespaceBundleStats")
-        exclude("org.apache.pulsar.policies.data.loadbalancer.ResourceUsage")
-        
exclude("org.apache.pulsar.policies.data.loadbalancer.ServiceLookupData")
-    }
-    relocate("org.asynchttpclient", "$shadePrefix.org.asynchttpclient")
-    relocate("org.checkerframework", "$shadePrefix.org.checkerframework")
-    relocate("org.codehaus.jackson", "$shadePrefix.org.codehaus.jackson")
-    relocate("org.eclipse.jetty", "$shadePrefix.org.eclipse")
-    relocate("org.glassfish", "$shadePrefix.org.glassfish")
-    relocate("org.jvnet", "$shadePrefix.org.jvnet")
-    relocate("org.objenesis", "$shadePrefix.org.objenesis")
-    relocate("org.reactivestreams", "$shadePrefix.org.reactivestreams")
-    relocate("org.roaringbitmap", "$shadePrefix.org.roaringbitmap")
-    relocate("org.tukaani", "$shadePrefix.org.tukaani")
-    relocate("org.yaml", "$shadePrefix.org.yaml")
-    // NOTE: Do NOT shade log4j, otherwise logging won't work
-
-    // The shadow plugin's relocate() doesn't handle property file VALUES.
-    // AsyncHttpClient's ahc-default.properties contains property names 
prefixed with
-    // "org.asynchttpclient." which must be updated to the relocated package 
name.
-    filesMatching("org/asynchttpclient/config/ahc-default.properties") {
-        filter { line -> line.replace("org.asynchttpclient.", 
"org.apache.pulsar.shade.org.asynchttpclient.") }
-    }
-
-    // Relocate Netty native library filenames to avoid conflicts with 
unshaded Netty
-    filesMatching("META-INF/native/**") {
-        if (name.matches(Regex("netty.+\\.(so|jnilib|dll)"))) {
-            path = path.replace(name, "org_apache_pulsar_shade_$name")
-        }
-    }
-}
diff --git a/pulsar-client-dependencies-minimized/build.gradle.kts 
b/pulsar-client-dependencies-minimized/build.gradle.kts
index cc9db1daf50..282208bc773 100644
--- a/pulsar-client-dependencies-minimized/build.gradle.kts
+++ b/pulsar-client-dependencies-minimized/build.gradle.kts
@@ -18,7 +18,7 @@
  */
 
 plugins {
-    alias(libs.plugins.shadow)
+    id("pulsar.shadow-conventions")
 }
 
 dependencies {
@@ -26,7 +26,6 @@ dependencies {
 }
 
 tasks.shadowJar {
-    archiveClassifier.set("")
     dependencies {
         include(dependency("it.unimi.dsi:fastutil"))
     }
@@ -179,24 +178,3 @@ tasks.shadowJar {
     }
 }
 
-// Expose the shadow jar as the primary artifact so downstream modules
-// (pulsar-client-shaded, pulsar-client-admin-shaded, pulsar-client-all)
-// get the minimized fastutil classes instead of an empty jar.
-tasks.jar {
-    archiveClassifier.set("original")
-}
-
-configurations {
-    runtimeElements {
-        outgoing {
-            artifacts.clear()
-            artifact(tasks.shadowJar)
-        }
-    }
-    apiElements {
-        outgoing {
-            artifacts.clear()
-            artifact(tasks.shadowJar)
-        }
-    }
-}
diff --git a/pulsar-client-shaded/build.gradle.kts 
b/pulsar-client-shaded/build.gradle.kts
index 2a438dd3344..7394342f24b 100644
--- a/pulsar-client-shaded/build.gradle.kts
+++ b/pulsar-client-shaded/build.gradle.kts
@@ -18,7 +18,7 @@
  */
 
 plugins {
-    alias(libs.plugins.shadow)
+    id("pulsar.client-shade-conventions")
 }
 
 dependencies {
@@ -29,145 +29,3 @@ dependencies {
     implementation(project(":pulsar-client-dependencies-minimized"))
     implementation(project(":pulsar-client-messagecrypto-bc"))
 }
-
-val shadePrefix = "org.apache.pulsar.shade"
-
-tasks.shadowJar {
-    archiveClassifier.set("")
-    mergeServiceFiles()
-
-    dependencies {
-        include(project(":pulsar-client-original"))
-        include(project(":pulsar-common"))
-        include(project(":pulsar-client-messagecrypto-bc"))
-        include(project(":pulsar-client-dependencies-minimized"))
-        include(dependency("com.fasterxml.jackson.*:.*"))
-        include(dependency("com.google.code.findbugs:.*"))
-        include(dependency("com.google.code.gson:gson"))
-        include(dependency("com.google.errorprone:.*"))
-        include(dependency("com.google.guava:.*"))
-        include(dependency("com.google.j2objc:.*"))
-        include(dependency("com.google.re2j:re2j"))
-        include(dependency("com.spotify:completable-futures"))
-        include(dependency("com.sun.activation:jakarta.activation"))
-        include(dependency("com.thoughtworks.paranamer:paranamer"))
-        include(dependency("com.typesafe.netty:netty-reactive-streams"))
-        include(dependency("com.yahoo.datasketches:.*"))
-        include(dependency("commons-.*:.*"))
-        include(dependency("io.airlift:.*"))
-        include(dependency("io.netty.incubator:.*"))
-        include(dependency("io.netty:.*"))
-        include(dependency("io.perfmark:.*"))
-        include(dependency("io.swagger:.*"))
-        include(dependency("jakarta.activation:jakarta.activation-api"))
-        include(dependency("jakarta.annotation:jakarta.annotation-api"))
-        include(dependency("jakarta.ws.rs:jakarta.ws.rs-api"))
-        include(dependency("jakarta.xml.bind:jakarta.xml.bind-api"))
-        include(dependency("javax.ws.rs:.*"))
-        include(dependency("org.apache.avro:.*"))
-        include(dependency("org.apache.bookkeeper:.*"))
-        include(dependency("org.apache.commons:commons-compress"))
-        include(dependency("org.apache.commons:commons-lang3"))
-        include(dependency("org.asynchttpclient:.*"))
-        include(dependency("org.checkerframework:.*"))
-        include(dependency("org.eclipse.jetty:.*"))
-        include(dependency("org.javassist:javassist"))
-        include(dependency("org.objenesis:.*"))
-        include(dependency("org.reactivestreams:reactive-streams"))
-        include(dependency("org.tukaani:xz"))
-        include(dependency("org.yaml:snakeyaml"))
-        include(dependency("org.roaringbitmap:RoaringBitmap"))
-        include(dependency("io.prometheus:.*"))
-        include(dependency("com.github.ben-manes.caffeine:.*"))
-        exclude(dependency("com.fasterxml.jackson.core:jackson-annotations"))
-        // kqueue is pulled transitively by asynchttpclient but not bundled in 
Maven's shaded JAR
-        exclude(dependency("io.netty:netty-transport-native-kqueue"))
-    }
-
-    // Exclude bouncycastle from pulsar-client (signatures would break if 
shaded)
-    exclude("org/bouncycastle/**")
-    // Exclude common META-INF noise
-    exclude("**/module-info.class")
-    exclude("findbugsExclude.xml")
-    exclude("META-INF/*-LICENSE")
-    exclude("META-INF/*-NOTICE")
-    exclude("META-INF/*.DSA")
-    exclude("META-INF/*.RSA")
-    exclude("META-INF/*.SF")
-    exclude("META-INF/DEPENDENCIES*")
-    exclude("META-INF/io.netty.versions.properties")
-    exclude("META-INF/LICENSE*")
-    exclude("META-INF/license/**")
-    exclude("META-INF/maven/**")
-    exclude("META-INF/native-image/**")
-    exclude("META-INF/NOTICE*")
-    exclude("META-INF/proguard/**")
-
-    relocate("com.fasterxml.jackson", "$shadePrefix.com.fasterxml.jackson") {
-        exclude("com.fasterxml.jackson.annotation.*")
-    }
-    relocate("com.google", "$shadePrefix.com.google") {
-        exclude("com.google.protobuf.**")
-    }
-    relocate("com.spotify.futures", "$shadePrefix.com.spotify.futures")
-    relocate("com.sun.activation", "$shadePrefix.com.sun.activation")
-    relocate("com.thoughtworks.paranamer", 
"$shadePrefix.com.thoughtworks.paranamer")
-    relocate("com.typesafe", "$shadePrefix.com.typesafe")
-    relocate("com.yahoo.datasketches", "$shadePrefix.com.yahoo.datasketches")
-    relocate("com.yahoo.memory", "$shadePrefix.com.yahoo.memory")
-    relocate("com.yahoo.sketches", "$shadePrefix.com.yahoo.sketches")
-    relocate("com.github.benmanes", "$shadePrefix.com.github.benmanes")
-    relocate("io.airlift", "$shadePrefix.io.airlift")
-    relocate("io.netty", "$shadePrefix.io.netty")
-    relocate("io.swagger", "$shadePrefix.io.swagger")
-    relocate("io.prometheus.client", "$shadePrefix.io.prometheus.client")
-    relocate("it.unimi.dsi.fastutil", "$shadePrefix.it.unimi.dsi.fastutil")
-    relocate("javax.activation", "$shadePrefix.javax.activation")
-    relocate("javax.annotation", "$shadePrefix.javax.annotation")
-    relocate("javax.ws", "$shadePrefix.javax.ws")
-    relocate("org.apache.avro", "$shadePrefix.org.apache.avro") {
-        exclude("org.apache.avro.reflect.AvroAlias")
-        exclude("org.apache.avro.reflect.AvroDefault")
-        exclude("org.apache.avro.reflect.AvroEncode")
-        exclude("org.apache.avro.reflect.AvroIgnore")
-        exclude("org.apache.avro.reflect.AvroMeta")
-        exclude("org.apache.avro.reflect.AvroName")
-        exclude("org.apache.avro.reflect.AvroSchema")
-        exclude("org.apache.avro.reflect.Nullable")
-        exclude("org.apache.avro.reflect.Stringable")
-        exclude("org.apache.avro.reflect.Union")
-    }
-    relocate("org.apache.bookkeeper", "$shadePrefix.org.apache.bookkeeper")
-    relocate("org.apache.commons", "$shadePrefix.org.apache.commons")
-    relocate("org.apache.pulsar.policies", 
"$shadePrefix.org.apache.pulsar.policies") {
-        
exclude("org.apache.pulsar.policies.data.loadbalancer.LoadManagerReport")
-        
exclude("org.apache.pulsar.policies.data.loadbalancer.NamespaceBundleStats")
-        exclude("org.apache.pulsar.policies.data.loadbalancer.ResourceUsage")
-        
exclude("org.apache.pulsar.policies.data.loadbalancer.ServiceLookupData")
-    }
-    relocate("org.asynchttpclient", "$shadePrefix.org.asynchttpclient")
-    relocate("org.checkerframework", "$shadePrefix.org.checkerframework")
-    relocate("org.codehaus.jackson", "$shadePrefix.org.codehaus.jackson")
-    relocate("org.eclipse.jetty", "$shadePrefix.org.eclipse")
-    relocate("org.objenesis", "$shadePrefix.org.objenesis")
-    relocate("org.reactivestreams", "$shadePrefix.org.reactivestreams")
-    relocate("org.roaringbitmap", "$shadePrefix.org.roaringbitmap")
-    relocate("org.tukaani", "$shadePrefix.org.tukaani")
-    relocate("org.yaml", "$shadePrefix.org.yaml")
-    // NOTE: Do NOT shade log4j, otherwise logging won't work
-
-    // The shadow plugin's relocate() doesn't handle property file VALUES.
-    // AsyncHttpClient's ahc-default.properties contains property names 
prefixed with
-    // "org.asynchttpclient." which must be updated to the relocated package 
name.
-    filesMatching("org/asynchttpclient/config/ahc-default.properties") {
-        filter { line -> line.replace("org.asynchttpclient.", 
"org.apache.pulsar.shade.org.asynchttpclient.") }
-    }
-
-    // Relocate Netty native library filenames to avoid conflicts with 
unshaded Netty.
-    // Maven shade uses regex: 
(META-INF/native/(lib)?)(netty.+\.(so|jnilib|dll))$ → 
$1org_apache_pulsar_shade_$3
-    filesMatching("META-INF/native/**") {
-        if (name.matches(Regex("netty.+\\.(so|jnilib|dll)"))) {
-            path = path.replace(name, "org_apache_pulsar_shade_$name")
-        }
-    }
-}
diff --git a/pulsar-functions/localrun-shaded/build.gradle.kts 
b/pulsar-functions/localrun-shaded/build.gradle.kts
index c626eef8a41..9d3b1674df5 100644
--- a/pulsar-functions/localrun-shaded/build.gradle.kts
+++ b/pulsar-functions/localrun-shaded/build.gradle.kts
@@ -18,7 +18,7 @@
  */
 
 plugins {
-    alias(libs.plugins.shadow)
+    id("pulsar.shadow-conventions")
 }
 
 dependencies {
@@ -28,9 +28,7 @@ dependencies {
 val shadePrefix = "org.apache.pulsar.functions.runtime.shaded"
 
 tasks.shadowJar {
-    archiveClassifier.set("")
     isZip64 = true
-    mergeServiceFiles()
 
     dependencies {
         include(dependency("org.apache.pulsar:.*"))
@@ -71,74 +69,69 @@ tasks.shadowJar {
     // Exclude bouncycastle from pulsar-client (signatures would break if 
shaded)
     exclude("org/bouncycastle/**")
 
-    relocate("com.google", "$shadePrefix.com.google")
-    relocate("org.apache.jute", "$shadePrefix.org.apache.jute")
-    relocate("javax.servlet", "$shadePrefix.javax.servlet")
-    relocate("net.jodah", "$shadePrefix.net.jodah")
-    relocate("javax.ws.rs", "$shadePrefix.javax.ws.rs")
-    relocate("org.lz4", "$shadePrefix.org.lz4")
-    relocate("org.reactivestreams", "$shadePrefix.org.reactivestreams")
-    relocate("org.apache.commons", "$shadePrefix.org.apache.commons")
-    relocate("io.swagger", "$shadePrefix.io.swagger")
-    relocate("org.yaml", "$shadePrefix.org.yaml")
-    relocate("org.jctools", "$shadePrefix.org.jctools")
-    relocate("com.squareup.okhttp", "$shadePrefix.com.squareup.okhttp")
-    relocate("io.grpc", "$shadePrefix.io.grpc")
-    relocate("io.perfmark", "$shadePrefix.io.perfmark")
-    relocate("org.joda", "$shadePrefix.org.joda")
-    relocate("io.kubernetes", "$shadePrefix.io.kubernetes")
-    relocate("io.opencensus", "$shadePrefix.io.opencensus")
-    relocate("net.jpountz", "$shadePrefix.net.jpountz")
-    relocate("org.tukaani", "$shadePrefix.org.tukaani")
-    relocate("com.github", "$shadePrefix.com.github")
-    relocate("commons-io", "$shadePrefix.commons-io")
-    relocate("org.apache.distributedlog", 
"$shadePrefix.org.apache.distributedlog")
-    relocate("com.fasterxml", "$shadePrefix.com.fasterxml")
-    relocate("org.inferred", "$shadePrefix.org.inferred")
-    relocate("org.apache.bookkeeper", "$shadePrefix.org.apache.bookkeeper")
-    relocate("org.bookkeeper", "$shadePrefix.org.bookkeeper")
-    relocate("dlshade", "$shadePrefix.dlshade")
-    relocate("org.codehaus.jackson", "$shadePrefix.org.codehaus.jackson")
-    relocate("net.java.dev.jna", "$shadePrefix.net.java.dev.jna")
-    relocate("org.apache.curator", "$shadePrefix.org.apache.curator")
-    relocate("javax.validation", "$shadePrefix.javax.validation")
-    relocate("javax.activation", "$shadePrefix.javax.activation")
-    relocate("io.prometheus", "$shadePrefix.io.prometheus")
-    relocate("org.apache.zookeeper", "$shadePrefix.org.apache.zookeeper")
-    relocate("io.jsonwebtoken", "$shadePrefix.io.jsonwebtoken")
-    relocate("commons-codec", "$shadePrefix.commons-codec")
-    relocate("com.thoughtworks.paranamer", 
"$shadePrefix.com.thoughtworks.paranamer")
-    relocate("org.codehaus.mojo", "$shadePrefix.org.codehaus.mojo")
-    relocate("com.github.luben", "$shadePrefix.com.github.luben")
-    relocate("jline", "$shadePrefix.jline")
-    relocate("org.xerial.snappy", "$shadePrefix.org.xerial.snappy")
-    relocate("javax.annotation", "$shadePrefix.javax.annotation")
-    relocate("org.checkerframework", "$shadePrefix.org.checkerframework")
-    relocate("org.apache.yetus", "$shadePrefix.org.apache.yetus")
-    relocate("commons-cli", "$shadePrefix.commons-cli")
-    relocate("commons-lang", "$shadePrefix.commons-lang")
-    relocate("com.squareup.okio", "$shadePrefix.com.squareup.okio")
-    relocate("org.rocksdb", "$shadePrefix.org.rocksdb")
-    relocate("org.objenesis", "$shadePrefix.org.objenesis")
-    relocate("org.eclipse.jetty", "$shadePrefix.org.eclipse.jetty")
-    relocate("org.apache.avro", "$shadePrefix.org.apache.avro")
-    relocate("avro.shaded", "$shadePrefix.avro.shaded")
-    relocate("com.yahoo.datasketches", 
"org.apache.pulsar.shaded.com.yahoo.datasketches")
-    relocate("com.yahoo.sketches", 
"org.apache.pulsar.shaded.com.yahoo.sketches")
-    relocate("info.picocli", "$shadePrefix.info.picocli")
-    relocate("io.netty", "$shadePrefix.io.netty")
-    relocate("org.hamcrest", "$shadePrefix.org.hamcrest")
-    relocate("aj.org", "$shadePrefix.aj.org")
-    relocate("com.scurrilous", "$shadePrefix.com.scurrilous")
-    relocate("okio", "$shadePrefix.okio")
-    relocate("org.asynchttpclient", "$shadePrefix.org.asynchttpclient")
-    relocate("io.airlift", "$shadePrefix.io.airlift")
+    relocateWithPrefix(shadePrefix, "aj.org")
+    relocateWithPrefix(shadePrefix, "avro.shaded")
+    relocateWithPrefix(shadePrefix, "com.fasterxml")
+    relocateWithPrefix(shadePrefix, "com.github")
+    relocateWithPrefix(shadePrefix, "com.github.luben")
+    relocateWithPrefix(shadePrefix, "com.google")
+    relocateWithPrefix(shadePrefix, "com.scurrilous")
+    relocateWithPrefix(shadePrefix, "com.squareup.okhttp")
+    relocateWithPrefix(shadePrefix, "com.squareup.okio")
+    relocateWithPrefix(shadePrefix, "com.thoughtworks.paranamer")
+    relocateWithPrefix(shadePrefix, "com.yahoo.datasketches")
+    relocateWithPrefix(shadePrefix, "com.yahoo.sketches")
+    relocateWithPrefix(shadePrefix, "commons-cli")
+    relocateWithPrefix(shadePrefix, "commons-codec")
+    relocateWithPrefix(shadePrefix, "commons-io")
+    relocateWithPrefix(shadePrefix, "commons-lang")
+    relocateWithPrefix(shadePrefix, "dlshade")
+    relocateWithPrefix(shadePrefix, "info.picocli")
+    relocateWithPrefix(shadePrefix, "io.airlift")
+    relocateWithPrefix(shadePrefix, "io.grpc")
+    relocateWithPrefix(shadePrefix, "io.jsonwebtoken")
+    relocateWithPrefix(shadePrefix, "io.kubernetes")
+    relocateWithPrefix(shadePrefix, "io.netty")
+    relocateWithPrefix(shadePrefix, "io.opencensus")
+    relocateWithPrefix(shadePrefix, "io.perfmark")
+    relocateWithPrefix(shadePrefix, "io.prometheus")
+    relocateWithPrefix(shadePrefix, "io.swagger")
+    relocateWithPrefix(shadePrefix, "javax.activation")
+    relocateWithPrefix(shadePrefix, "javax.annotation")
+    relocateWithPrefix(shadePrefix, "javax.servlet")
+    relocateWithPrefix(shadePrefix, "javax.validation")
+    relocateWithPrefix(shadePrefix, "javax.ws.rs")
+    relocateWithPrefix(shadePrefix, "jline")
+    relocateWithPrefix(shadePrefix, "net.java.dev.jna")
+    relocateWithPrefix(shadePrefix, "net.jodah")
+    relocateWithPrefix(shadePrefix, "net.jpountz")
+    relocateWithPrefix(shadePrefix, "okio")
+    relocateWithPrefix(shadePrefix, "org.apache.avro")
+    relocateWithPrefix(shadePrefix, "org.apache.bookkeeper")
+    relocateWithPrefix(shadePrefix, "org.apache.commons")
+    relocateWithPrefix(shadePrefix, "org.apache.curator")
+    relocateWithPrefix(shadePrefix, "org.apache.distributedlog")
+    relocateWithPrefix(shadePrefix, "org.apache.jute")
+    relocateWithPrefix(shadePrefix, "org.apache.yetus")
+    relocateWithPrefix(shadePrefix, "org.apache.zookeeper")
+    relocateWithPrefix(shadePrefix, "org.asynchttpclient")
+    relocateWithPrefix(shadePrefix, "org.bookkeeper")
+    relocateWithPrefix(shadePrefix, "org.checkerframework")
+    relocateWithPrefix(shadePrefix, "org.codehaus.jackson")
+    relocateWithPrefix(shadePrefix, "org.codehaus.mojo")
+    relocateWithPrefix(shadePrefix, "org.eclipse.jetty")
+    relocateWithPrefix(shadePrefix, "org.hamcrest")
+    relocateWithPrefix(shadePrefix, "org.inferred")
+    relocateWithPrefix(shadePrefix, "org.jctools")
+    relocateWithPrefix(shadePrefix, "org.joda")
+    relocateWithPrefix(shadePrefix, "org.lz4")
+    relocateWithPrefix(shadePrefix, "org.objenesis")
+    relocateWithPrefix(shadePrefix, "org.reactivestreams")
+    relocateWithPrefix(shadePrefix, "org.rocksdb")
+    relocateWithPrefix(shadePrefix, "org.tukaani")
+    relocateWithPrefix(shadePrefix, "org.xerial.snappy")
+    relocateWithPrefix(shadePrefix, "org.yaml")
     // NOTE: Do NOT shade log4j, otherwise logging won't work
 
-    // The shadow plugin's relocate() doesn't handle property file VALUES.
-    // AsyncHttpClient's ahc-default.properties contains property names 
prefixed with
-    // "org.asynchttpclient." which must be updated to the relocated package 
name.
-    filesMatching("org/asynchttpclient/config/ahc-default.properties") {
-        filter { line -> line.replace("org.asynchttpclient.", 
"org.apache.pulsar.shade.org.asynchttpclient.") }
-    }
+    relocateAsyncHttpClientProperties(shadePrefix)
 }
diff --git a/tests/docker-images/java-test-functions/build.gradle.kts 
b/tests/docker-images/java-test-functions/build.gradle.kts
index b7826c18632..499c25096a6 100644
--- a/tests/docker-images/java-test-functions/build.gradle.kts
+++ b/tests/docker-images/java-test-functions/build.gradle.kts
@@ -18,7 +18,7 @@
  */
 
 plugins {
-    alias(libs.plugins.shadow)
+    id("pulsar.shadow-conventions")
 }
 
 dependencies {
@@ -36,11 +36,6 @@ tasks.shadowJar {
     dependencies {
         include(project(":pulsar-functions:pulsar-functions-api-examples"))
     }
-    mergeServiceFiles()
-}
-
-tasks.named("jar") {
-    enabled = false
 }
 
 tasks.named("assemble") {
diff --git a/tests/pulsar-client-admin-shade-test/build.gradle.kts 
b/tests/pulsar-client-admin-shade-test/build.gradle.kts
index 577891b2b46..f6e0cf69c58 100644
--- a/tests/pulsar-client-admin-shade-test/build.gradle.kts
+++ b/tests/pulsar-client-admin-shade-test/build.gradle.kts
@@ -22,8 +22,7 @@
 // so the Maven shade test depends on the shaded JAR, not the original.
 
 dependencies {
-    // Shadow JAR (provides relocated classes like 
org.apache.pulsar.shade.io.netty)
-    testImplementation(project(path = ":pulsar-client-admin-shaded", 
configuration = "shadowElements"))
+    testImplementation(project(":pulsar-client-admin-shaded"))
     // API modules and messagecrypto are not bundled in the shaded JAR
     testImplementation(project(":pulsar-client-admin-api"))
     testImplementation(project(":pulsar-client-messagecrypto-bc"))
diff --git a/tests/pulsar-client-all-shade-test/build.gradle.kts 
b/tests/pulsar-client-all-shade-test/build.gradle.kts
index 5ee8626b7ce..75a5ce3f87f 100644
--- a/tests/pulsar-client-all-shade-test/build.gradle.kts
+++ b/tests/pulsar-client-all-shade-test/build.gradle.kts
@@ -20,8 +20,7 @@
 // Use the shadow JAR from pulsar-client-all which contains relocated 
netty/jackson/etc classes.
 
 dependencies {
-    // Shadow JAR (provides relocated classes like 
org.apache.pulsar.shade.io.netty)
-    testImplementation(project(path = ":pulsar-client-all", configuration = 
"shadowElements"))
+    testImplementation(project(":pulsar-client-all"))
     // API modules are not bundled in the shaded JAR
     testImplementation(project(":pulsar-client-api"))
     testImplementation(project(":pulsar-client-admin-api"))
diff --git a/tests/pulsar-client-shade-test/build.gradle.kts 
b/tests/pulsar-client-shade-test/build.gradle.kts
index c4c3fc8f4ea..283b9ea3805 100644
--- a/tests/pulsar-client-shade-test/build.gradle.kts
+++ b/tests/pulsar-client-shade-test/build.gradle.kts
@@ -23,9 +23,8 @@
 // so the Maven shade tests depend on the shaded JARs, not the originals.
 
 dependencies {
-    // Shadow JARs (provides relocated classes like 
org.apache.pulsar.shade.io.netty)
-    testImplementation(project(path = ":pulsar-client-shaded", configuration = 
"shadowElements"))
-    testImplementation(project(path = ":pulsar-client-admin-shaded", 
configuration = "shadowElements"))
+    testImplementation(project(":pulsar-client-shaded"))
+    testImplementation(project(":pulsar-client-admin-shaded"))
     // API modules are not bundled in the shaded JARs
     testImplementation(project(":pulsar-client-api"))
     testImplementation(project(":pulsar-client-admin-api"))

Reply via email to