[improve][build] Upgrade Shadow plugin to 9.6.1 and merge service descriptor files in shaded jars - #26333
Merged
lhotari merged 2 commits intoAug 14, 2026
Conversation
### Motivation The build pins the GradleUp Shadow plugin at 9.4.2. Upgrading to 9.6.1 picks up the accumulated fixes (Zip Slip validation on ZIP entry names, relocation patterns included in the task fingerprint, R8 rule handling) and moves the plugin's own log4j-core off 2.25.3. ### Modifications * Bump `shadow` in the version catalog from 9.4.2 to 9.6.1. * Add the `log4j-bom` platform to the build-logic conventions dependencies so the log4j that Shadow drags onto the build classpath is aligned with the `log4j2` version the rest of the build uses instead of floating on whatever the plugin happens to depend on. Assisted-by: Claude Code (Opus 5)
### Motivation
`ShadowJar` sets `duplicatesStrategy = EXCLUDE` in its own constructor, and
Gradle applies the strategy *before* the resource transformers run. Every
duplicate `META-INF/services/*` entry is therefore dropped before
`ServiceFileTransformer` ever sees it, so `mergeServiceFiles()` keeps only the
first provider file it encounters instead of merging them.
The shaded jars built today are missing 86 service providers across 28
descriptor files. The Maven-built 4.0.x releases on Maven Central contain the
fully merged files, so this is a regression introduced by the Gradle migration.
Shadow 9.5.0 started warning about the mismatch, which is how it surfaced.
### Modifications
Keep `EXCLUDE` as the global strategy — class files bypass the transformers
entirely (`ShadowCopyAction` handles them in a dedicated branch), so the
strategy is the only thing deduplicating them, and
`jetty-upgrade/zookeeper-with-patched-admin` depends on first-wins to override
ZooKeeper's `server.admin.*` classes with its patched copies. Instead, exempt
only the paths a transformer owns:
* `filesMatching(listOf("META-INF/services/**", "META-INF/*.kotlin_module"))`
sets `INCLUDE` so every duplicate reaches the transformer.
* An `inputs.property` records the patterns, because `filesMatching {}` actions
are not part of a task's input fingerprint — without it the build cache
serves jars built under the old behaviour even after this change.
* `failOnDuplicateEntries` guards against a future change letting an entry into
the archive twice.
Applied in `pulsar.shadow-conventions` plus `microbench` and
`pulsar-functions/runtime-all`, the two modules that apply
`com.gradleup.shadow` directly.
Assisted-by: Claude Code (Opus 5)
dao-jun
approved these changes
Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Two related build changes to the Gradle Shadow plugin.
Shadow 9.4.2 → 9.6.1. Picks up the accumulated fixes since 9.4.2 — Zip Slip validation on ZIP entry names, relocation patterns included in the task fingerprint,
afterEvaluateremoved when adding variants — and moves the plugin's ownlog4j-coreoff 2.25.3.Shaded jars are missing service providers.
ShadowJarsetsduplicatesStrategy = EXCLUDEin its own constructor, and Gradle applies the strategy before the resource transformers run. Every duplicateMETA-INF/services/*entry is dropped beforeServiceFileTransformersees it, somergeServiceFiles()keeps only the first provider file it encounters instead of merging them all.The jars built today are missing 86 service providers across 28 descriptor files in 6 jars:
jclouds-shadedorg.jclouds.apis.ApiMetadatajclouds-shadedorg.jclouds.providers.ProviderMetadatapulsar-client,pulsar-client-all,pulsar-client-admincom.fasterxml.jackson.databind.Modulepulsar-client-all,pulsar-client-adminorg.glassfish.jersey.internal.spi.AutoDiscoverablemicrobenchorg.eclipse.jetty.ee10.webapp.ConfigurationThis is a regression introduced by the Gradle migration: the Maven-built 4.0.6 artifacts on Maven Central contain the fully merged descriptors, since
maven-shade-plugin'sServicesResourceTransformermerged them correctly.The practical impact is narrower than the numbers suggest, because Pulsar's own code does not rely on most of these lookups — jclouds
ProviderMetadatainstances are constructed directly inJCloudBlobStoreProvider, andPulsarAdminImplregistersMultiPartFeatureexplicitly. ButContextBuilder.newBuilder("transient")and any third-partyServiceLoaderlookup against the published shaded jars resolve against truncated descriptors.Shadow 9.5.0 added a warning for exactly this mismatch, which is how it surfaced during the upgrade.
Modifications
Upgrade (
gradle/libs.versions.toml,build-logic/conventions/build.gradle.kts)shadow9.4.2 → 9.6.1.log4j-bomplatform to the build-logic conventions dependencies, so the log4j Shadow drags onto the build classpath tracks thelog4j2catalog version instead of floating on whatever the plugin depends on.Service descriptor fix (
pulsar.shadow-conventions,microbench,pulsar-functions/runtime-all)EXCLUDEstays the global strategy. Class files bypass the transformers entirely —ShadowCopyAction.visitFilehandles them in a dedicated branch — so the strategy is the only thing deduplicating them, andjetty-upgrade/zookeeper-with-patched-admindepends on first-wins to override ZooKeeper'sserver.admin.*classes with its patched copies. SettingINCLUDEglobally and deduplicating withPreserveFirstFoundResourceTransformer(as Shadow's docs suggest) does not work for that reason: it emits ~40 duplicated admin classes in that module.Instead, only the paths a transformer owns are exempted:
inputs.propertyis required, not cosmetic:filesMatching {}actions are not part of a task's input fingerprint. Measured before adding it,:jclouds-shaded:shadowJarproduced the identical build cache keyaf6dd43b…with and without thefilesMatchingblock, and a--build-cacherun served a pre-fix jar to the fixed configuration. Naming the patterns in an input property keeps the cache key honest across this change.META-INF/*.kotlin_moduleis covered for the same reason —KotlinModuleMetadataTransformer, whichShadowJarapplies on its own, hits the identical problem. No duplicates exist there today, so this changes no jar content; it removes the warning and closes the latent hole.failOnDuplicateEntriesguards against a future change letting an entry into the archive twice.The three call sites are
pulsar.shadow-conventions(which covers every module going throughpulsar.client-shade-conventionsandpulsar.minimized-dependencies-conventions) plusmicrobenchandpulsar-functions/runtime-all, the two modules applyingcom.gradleup.shadowdirectly.Verifying this change
This change is a build-only change verified by comparing the produced artifacts:
shadowJaroutputs was built under 9.4.2 and 9.6.1 with--rerun-tasks, and the entry lists plus per-entry CRCs are identical across all of them.META-INF/services/*contents, which now match a reference build that lets every duplicate through to the transformer.zookeeper-with-patched-adminstill resolves to the patched classes. 45 of the 48org.apache.zookeeper.server.admin.*classes in the shaded jar are this module's patched copies (verified by CRC against the module's build output), 0 are ZooKeeper's originals winning instead, all 48 ZK admin classes remain present, and the jar has 0 duplicate entries.failOnDuplicateEntriespasses for every shaded jar, and noDuplicatesStrategywarnings remain in the build output../gradlew quickCheckand./gradlew spotlessCheck checkstyleMain checkstyleTestpass.Does this pull request potentially affect one of the following parts:
If the box was checked, please highlight the changes
The Shadow Gradle plugin is upgraded from 9.4.2 to 9.6.1 (build-time only, not shipped). The service descriptor fix changes the contents of the published shaded jars —
pulsar-client,pulsar-client-all,pulsar-client-admin,jclouds-shaded,pulsar-functions-local-runner-shaded— by restoring service providers that are currently dropped. No dependency versions change for the shipped artifacts.