Skip to content

SCANJLIB-324 Add sonar-scanner-java-library-shaded fat jar for Gradle - #307

Merged
alban-auzeill merged 8 commits into
masterfrom
jh/SCANJLIB-324_shade_all_deps
Aug 28, 2026
Merged

alban-auzeill merged 8 commits into
masterfrom
jh/SCANJLIB-324_shade_all_deps

Conversation

@henryju

@henryju henryju commented Aug 27, 2026

Copy link
Copy Markdown
Member

Summary

  • Gradle doesn't isolate plugins in separate classloaders, so any unshaded dependency of sonar-scanner-java-library (gson, commons-compress, commons-lang3, fastcsv, ayza, BouncyCastle) can collide with a different version already on the build classpath. Maven and the CLI don't have this problem.
  • Instead of shading everything into the one artifact all three tools share, publish a second, Gradle-only artifact (sonar-scanner-java-library-shaded) with every third-party dependency relocated under org.sonarsource.scanner.lib.internal. lib itself is untouched and keeps its plain, unrelocated dependencies for Maven/CLI.
  • The new lib-shaded module has no sources of its own — it depends on lib and shades it plus its dependencies with maven-shade-plugin. BouncyCastle's relocation reuses Alban Auzeill's findings (the org.bouncycastle.pkcs12.* property-name exclude and the ServicesResourceTransformer for the Provider SPI file), folded into this single shading module rather than a standalone one.
  • Switching the actual Sonar Gradle plugin to depend on the new coordinate is a separate, follow-up change in that plugin's own repo.

Test plan

  • mvn install -pl lib-shaded --also-make builds the new module
  • Verified the shaded jar has no leftover unrelocated classes from gson/commons-compress/commons-lang3/fastcsv/ayza/BouncyCastle, and that the relocated packages are present
  • Verified META-INF/services/java.security.Provider is correctly rewritten to the relocated BouncyCastleProvider class name
  • Verified module-info.class and META-INF/versions/** are stripped from all bundled libraries (contained only duplicate module descriptors, no functional code)
  • Verified the dependency-reduced POM declares zero dependencies, so consumers can't accidentally pull in the unshaded libraries transitively
  • mvn install -pl lib --also-make — confirmed lib's full test suite is unaffected (no source changes)
  • mvn install from repo root — full reactor builds
  • End-to-end verification against the real Sonar Gradle plugin (out of scope here, follow-up)

🤖 Generated with Claude Code

Gradle doesn't isolate plugins in separate classloaders, so any unshaded
dependency of sonar-scanner-java-library (gson, commons-compress,
commons-lang3, fastcsv, ayza, BouncyCastle) can collide with a different
version already on the build classpath. Maven and the CLI don't have this
problem, so instead of shading everything into the one artifact they all
share, publish a second, Gradle-only artifact with every dependency
relocated under org.sonarsource.scanner.lib.internal.

The new lib-shaded module has no sources of its own: it just depends on
lib and shades it plus its dependencies with maven-shade-plugin, reusing
Alban Auzeill's BouncyCastle relocation findings (the pkcs12
property-name exclude and the Provider services transformer) folded into
this single shading module instead of a standalone one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@henryju
henryju requested a review from a team as a code owner August 27, 2026 10:20
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

SCANJLIB-324

@henryju
henryju requested a review from alban-auzeill August 27, 2026 10:21
Comment thread lib-shaded/pom.xml Resolved
Comment thread lib-shaded/pom.xml Resolved
Comment thread lib-shaded/pom.xml Resolved
gitar-bot[bot]

This comment was marked as resolved.

…ocation naming

The artifactSet's include list only enumerated lib's direct dependencies,
silently dropping non-optional transitive compile deps of commons-compress
(commons-io, commons-codec) and ayza (sude) from the fat jar: since they
matched neither the includes nor the reduced pom's single top-level
dependency, they were packaged nowhere, which would fail at runtime with
NoClassDefFoundError as soon as commons-compress touches its own
org.apache.commons.io usage. Switch to an excludes-based artifactSet (only
the provided, non-bundled slf4j-api/jsr305 stay out) so any current or
future transitive dependency gets shaded automatically, and add the
relocations these newly-included artifacts need, including
error_prone_annotations (a compile dependency of gson also missing until
now).

Also merge the bundled libraries' LICENSE/NOTICE files instead of letting
them collide arbitrarily, and align BouncyCastle's relocation target with
every other library's org.sonarsource.scanner.lib.internal.shaded prefix
now that it's relocated in this unified module rather than a standalone
one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@gitar-bot
gitar-bot Bot dismissed their stale review August 27, 2026 12:17

✅ All code review findings resolved.

Configure merge blocking

henryju and others added 2 commits August 27, 2026 14:20
com.google, org.apache.commons and nl.altindag each currently map to a
single library plus its own transitive dependencies (gson +
error_prone_annotations, commons-compress/commons-lang3 + their own
commons-io/commons-codec, ayza + sude). None of these namespaces overlap
with anything else bundled in the jar, so one relocation per namespace
covers the same ground as five separate entries while also covering any
future transitive dependency added under the same namespace automatically,
instead of needing a new relocation entry added by hand every time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…dividually

org.bouncycastle.util.Properties defines ~30 system-property names as
String constants. The shade plugin rewrites string constants that look
like class names, which would rename these properties in the shaded jar
but not in the forked scanner-engine JVM they're forwarded to, which runs
its own unrelocated BouncyCastle.

Some of these properties (x509.*, asn1.*) share their prefix with real
BouncyCastle packages containing real classes that do need relocation, so
a wildcard exclude would also block those classes. Listing each property
as an exact literal avoids that collision.
Comment thread lib-shaded/pom.xml Resolved
Comment thread lib-shaded/pom.xml Resolved
…cation regression test

Many BouncyCastle system-property names are read as literals outside
org.bouncycastle.util.Properties too (ASN1Integer, RSAKeyParameters,
ECCurve, PKCS1Encoding, the PKCS12/BC/BCFKS keystores, Argon2Parameters,
...). Add the ~28 that were missing from the relocation excludes.

Add BouncyCastleRelocationIT, which scans the real bcprov-jdk18on jar for
every org.bouncycastle.* string constant that isn't a real class/resource
reference and fails the build if it's not excluded, so a future
BouncyCastle upgrade adding new properties is caught automatically
instead of silently breaking them in the shaded jar.
Despite its name, ApacheLicenseResourceTransformer doesn't merge or
dedupe META-INF/LICENSE(.txt|.md) entries: processResource() is a no-op
and hasTransformedResource() always returns false, so it unconditionally
drops every one it matches - including BouncyCastle's own bundled MIT
META-INF/LICENSE.md (verified against the plugin's source).

Drop it and let the shade plugin's default last-jar-wins handling take
over for the one remaining collision (the identical Apache-2.0
META-INF/LICENSE.txt bundled by commons-compress/io/codec/lang3), the
same way the pre-existing META-INF/MANIFEST.MF overlap is already
tolerated. ApacheNoticeResourceTransformer genuinely merges NOTICE
content, so it's kept.
…BouncyCastleRelocationIT

Replaces the manual CONSTANT_Utf8 tag-switch parser with an ASM
ClassVisitor that collects LDC-loaded String constants and field
ConstantValue defaults. This also makes the test a more faithful mirror
of the actual mechanism it's guarding: maven-shade-plugin's own
DefaultShader.LazyInitRemapper overrides ASM's Remapper#mapValue to
rewrite exactly those two kinds of String constants.
Comment thread lib-shaded/pom.xml Resolved
Comment thread lib-shaded/pom.xml Resolved
- The META-INF/versions/** filter comment claimed those entries hold
  "no functional code". Verified against bcprov-jdk18on-1.85.2: its
  META-INF/versions/{17,25} carry classes with no base-tree counterpart,
  registered by always-loaded Mappings classes (KEM.ML-KEM, KDF.PBKDF2*/
  HKDF-*). Document exactly what's lost and why it's accepted (this
  module only uses BouncyCastle for PKCS12 keystore loading).

- The license-transformer comment said the shade plugin's default
  resource-merge behaviour is last-jar-wins; DefaultShader actually
  keeps the first jar processed and skips later duplicates. Correct the
  wording.
@gitar-bot

gitar-bot Bot commented Aug 28, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 7 resolved / 7 findings

Adds a sonar-scanner-java-library-shaded fat jar module for Gradle, which shades all third-party dependencies under org.sonarsource.scanner.lib.internal to prevent classpath collisions. Resolved transitive dependency inclusion, LICENSE file preservation, BouncyCastle relocation naming consistency, property key exclusions, resource transformer behavior, and META-INF/versions stripping. No issues remain.

✅ 7 resolved
✅ Bug: artifactSet omits transitive deps: commons-io, commons-codec, sude

📄 lib-shaded/pom.xml:49-61 📄 lib-shaded/pom.xml:62-76 📄 pom.xml:70-80 📄 pom.xml:88-95
The <artifactSet><includes> list only enumerates lib's direct dependencies, but three compile-scope transitive dependencies are missing: commons-io:commons-io and commons-codec:commons-codec (both declared non-optional compile deps of org.apache.commons:commons-compress:1.28.0 — which is also why the root pom manages commons-io 2.21.0 / commons-codec 1.21.0 for a lib that never declares them), and io.github.hakky54:sude:2.0.2 (non-optional compile dep of io.github.hakky54:ayza:10.0.3). Because these artifacts are outside the artifactSet they are not packaged, and because promoteTransitiveDependencies defaults to false the dependency-reduced POM ends up with zero dependencies (exactly as the PR verified), so consumers get neither the classes nor the coordinates: the relocated commons-compress/ayza code will hit NoClassDefFoundError at runtime (commons-compress 1.26+ uses org.apache.commons.io.* throughout its tar/zip streams, which is the code path used to unpack the scanner engine and JRE archives). Add these artifacts to the includes list together with relocations for org.apache.commons.io, org.apache.commons.codec and sude's package, or drop the include list entirely and exclude only the provided artifacts.

✅ Quality: Bundled third-party licenses are not preserved or reported

📄 lib-shaded/pom.xml:44-48 📄 lib-shaded/pom.xml:127-130
The published fat jar embeds Apache-2.0 (gson, commons-compress, commons-lang3, ayza) and MIT-licensed (BouncyCastle, fastcsv) code, but the shade configuration has no ApacheLicenseResourceTransformer/ApacheNoticeResourceTransformer/AppendingTransformer for META-INF/LICENSE/NOTICE, so colliding notice files from the bundled jars are resolved arbitrarily (first one wins). At the same time the dependency-reduced POM declares zero dependencies, so third-party-licenses.sh (license-maven-plugin ... -Dlicense.includedScopes=compile) will report no third-party libraries for this new artifact even though it ships their bytecode. Add license/notice transformers (and/or keep a checked-in THIRD-PARTY listing for lib-shaded) so attribution ships with the jar.

✅ Quality: BouncyCastle relocation prefix differs from all other relocations

📄 lib-shaded/pom.xml:62-76
Every other relocation targets org.sonarsource.scanner.lib.internal.shaded.*, but BouncyCastle targets org.sonarsource.scanner.lib.internal.impldep.org.bouncycastle (impldep is Gradle's own prefix). Nothing depends on the difference, but it makes the relocation scheme inconsistent and easy to get wrong when a future dependency is added or when someone greps the jar for internal.shaded. Use the same internal.shaded prefix for bcprov.

✅ Bug: BC exclude list misses ~28 property names read outside Properties

📄 lib-shaded/pom.xml:94-108 📄 lib-shaded/pom.xml:110
I extracted every non-class org.bouncycastle.* string literal from bcprov-jdk18on-1.85.2 (the version pinned in lib/pom.xml:60-63). The 30 excludes here match org/bouncycastle/util/Properties.class exactly, but BC reads many more system properties as literals at their use sites, e.g. org.bouncycastle.asn1.allow_unsafe_integer (ASN1Integer), org.bouncycastle.rsa.max_size / allow_unsafe_mod / no_lenstra_check / max_mr_tests (RSAKeyParameters), org.bouncycastle.ec.disable, ec.disable_f2m, ec.disable_mqv, ec.enable_pc, ec.fp_certainty, ec.fp_max_size, ec.max_f2m_field_size (ECCurve/ECUtil), org.bouncycastle.pkcs1.strict / not_strict (PKCS1Encoding), org.bouncycastle.pkcs12.default (PKCS12$Mappings), org.bouncycastle.pkcs8.v1_info_only, org.bouncycastle.pemreader.lax, org.bouncycastle.argon2.max_passes / max_memory_exp / max_parallelism, org.bouncycastle.fpe.disable(_ff1), org.bouncycastle.dh.allow_unsafe_p_value, org.bouncycastle.dsa.FIPS186, org.bouncycastle.constraints.allow_override, org.bouncycastle.asn1.allow_wrong_oid_enc, org.bouncycastle.x509.allow_absent_equiv_NULL, x509.allow_ca_without_crl_sign, x509.ignore_repeated_extensions. Those literals get rewritten to org.sonarsource.scanner.lib.internal.shaded.org.bouncycastle..., so a user who sets the documented -Dorg.bouncycastle.asn1.allow_unsafe_integer=true (or any of the above) to work around a malformed cert/truststore silently gets no effect with the Gradle-only shaded artifact while it still works with plain lib — exactly the boundary mismatch this exclude block exists to prevent. Add the missing literals and change the maintenance note: syncing with org.bouncycastle.util.Properties alone is not sufficient, the whole jar must be scanned (ideally by an automated check over the built jar).

✅ Quality: ApacheLicenseResourceTransformer drops LICENSE files, not merges them

📄 lib-shaded/pom.xml:175-182
The comment added for the transformers states that without them the colliding META-INF/LICENSE/NOTICE entries would be "resolved arbitrarily (first one wins), dropping the others' attribution", implying the MIT licences of BouncyCastle/fastcsv/ayza are preserved. ApacheLicenseResourceTransformer does the opposite: its processResource is a no-op and hasTransformedResource() returns false, so every matched META-INF/LICENSE* entry is simply discarded from the shaded jar, and ApacheNoticeResourceTransformer only merges Apache-style NOTICE content. The published fat jar therefore ships MIT-licensed bytecode with no licence text at all. Bundle the licences explicitly (e.g. keep them under META-INF/licenses/<artifact>/ via license-maven-plugin or an IncludeResourceTransformer) and fix the comment.

...and 2 more resolved from earlier reviews

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqube-next

Copy link
Copy Markdown

@alban-auzeill
alban-auzeill merged commit 4857950 into master Aug 28, 2026
11 checks passed
@alban-auzeill
alban-auzeill deleted the jh/SCANJLIB-324_shade_all_deps branch August 28, 2026 11:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants