From 31482a1faf3537227b181c6756efd675b6c46378 Mon Sep 17 00:00:00 2001 From: Jeppe Fredsgaard Blaabjerg Date: Sun, 1 Nov 2026 08:49:28 +0100 Subject: [PATCH] fix(manifest): keep Maven dependencies left under a sibling's test-jar A module that reaches a sibling through both its jar and its test-jar shares one node for both, but Maven can keep the sibling's dependencies under the test-jar alone. The walk visited each node once, so it skipped that subtree and the dependencies were missing from the SBOM. Key the visited set by the full coordinate instead. --- CHANGELOG.md | 1 + .../socket/SocketFactsRecordsEngine.java | 14 ++++++------- .../test/maven-compat/project/app/pom.xml | 14 +++++++++++-- .../test/maven-compat/project/lib/pom.xml | 21 +++++++++++++++++++ .../test/maven-compat/project/mid/pom.xml | 17 +++++++++++++++ .../project/mid/src/main/java/demo/Mid.java | 4 ++++ .../scripts/test/maven-compat/project/pom.xml | 1 + .../maven-compat/smoke-test-workspaces.sh | 4 ++-- .../scripts/test/maven-compat/smoke-test.sh | 13 +++++++++++- 9 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 src/commands/manifest/scripts/test/maven-compat/project/mid/pom.xml create mode 100644 src/commands/manifest/scripts/test/maven-compat/project/mid/src/main/java/demo/Mid.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 54bb3b26be..dd2fd26301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Fixed - `--exclude-paths` patterns are now matched relative to the scan root in every build root. They now also exclude modules a build reaches outside its own directory (Maven `../` modules, relocated Gradle projects) and modules matched by wildcards that span a build-root boundary, such as `*/core`. - `--exclude-paths` patterns written with Windows `\` separators now exclude JVM build modules in single-root runs too. +- Maven SBOMs keep the dependencies a module reaches through a sibling module's test-jar. ## [1.12.2](https://github.com/SocketDev/socket-cli/releases/tag/v1.12.2) - 2026-10-30 diff --git a/src/commands/manifest/scripts/maven-extension/src/main/java/tech/coana/socket/SocketFactsRecordsEngine.java b/src/commands/manifest/scripts/maven-extension/src/main/java/tech/coana/socket/SocketFactsRecordsEngine.java index 4de0d60610..93c8ad272f 100644 --- a/src/commands/manifest/scripts/maven-extension/src/main/java/tech/coana/socket/SocketFactsRecordsEngine.java +++ b/src/commands/manifest/scripts/maven-extension/src/main/java/tech/coana/socket/SocketFactsRecordsEngine.java @@ -416,14 +416,12 @@ private String visit( String type = artifact.getProperty(ARTIFACT_PROPERTY_TYPE, artifact.getExtension()); String classifier = artifact.getClassifier(); String version = artifact.getBaseVersion(); - String id = internal - ? SocketSupport.bareId(artifact.getGroupId(), artifact.getArtifactId(), version) - : externalId(artifact); - - // One walk per node per module traversal: a shared subtree reached via another edge is already - // fully recorded (node, children, resolved file), so hand back the id without re-descending. - // Keeps reconverging graphs linear. - if (!visited.add(id)) return id; + String key = externalId(artifact); + String id = internal ? SocketSupport.bareId(artifact.getGroupId(), artifact.getArtifactId(), version) : key; + + // One walk per artifact: a shared subtree is already recorded. Keyed by coordinate, as a sibling's jar + // and test-jar share a node but Maven can keep the sibling's dependencies under either. + if (!visited.add(key)) return id; Node node = internal ? upsert(nodes, id, artifact.getGroupId(), artifact.getArtifactId(), "", "", version, gav) diff --git a/src/commands/manifest/scripts/test/maven-compat/project/app/pom.xml b/src/commands/manifest/scripts/test/maven-compat/project/app/pom.xml index f1b947ff63..10d2d6fbe4 100644 --- a/src/commands/manifest/scripts/test/maven-compat/project/app/pom.xml +++ b/src/commands/manifest/scripts/test/maven-compat/project/app/pom.xml @@ -8,10 +8,13 @@ app - + demo - lib + mid 1.0 @@ -43,5 +46,12 @@ conflict-prod 1.0 + + demo + lib + 1.0 + test-jar + test + diff --git a/src/commands/manifest/scripts/test/maven-compat/project/lib/pom.xml b/src/commands/manifest/scripts/test/maven-compat/project/lib/pom.xml index f16c76bf3b..37cba8823a 100644 --- a/src/commands/manifest/scripts/test/maven-compat/project/lib/pom.xml +++ b/src/commands/manifest/scripts/test/maven-compat/project/lib/pom.xml @@ -7,4 +7,25 @@ 1.0 lib + + + demo.ext + libdep + 1.0 + + + + + + maven-jar-plugin + + + + test-jar + + + + + + diff --git a/src/commands/manifest/scripts/test/maven-compat/project/mid/pom.xml b/src/commands/manifest/scripts/test/maven-compat/project/mid/pom.xml new file mode 100644 index 0000000000..8feda9593c --- /dev/null +++ b/src/commands/manifest/scripts/test/maven-compat/project/mid/pom.xml @@ -0,0 +1,17 @@ + + + 4.0.0 + + demo + root + 1.0 + + mid + + + demo + lib + 1.0 + + + diff --git a/src/commands/manifest/scripts/test/maven-compat/project/mid/src/main/java/demo/Mid.java b/src/commands/manifest/scripts/test/maven-compat/project/mid/src/main/java/demo/Mid.java new file mode 100644 index 0000000000..5d5b917c99 --- /dev/null +++ b/src/commands/manifest/scripts/test/maven-compat/project/mid/src/main/java/demo/Mid.java @@ -0,0 +1,4 @@ +package demo; + +public class Mid { +} diff --git a/src/commands/manifest/scripts/test/maven-compat/project/pom.xml b/src/commands/manifest/scripts/test/maven-compat/project/pom.xml index 1bbca3458c..c9dc686a95 100644 --- a/src/commands/manifest/scripts/test/maven-compat/project/pom.xml +++ b/src/commands/manifest/scripts/test/maven-compat/project/pom.xml @@ -7,6 +7,7 @@ pom lib + mid app diff --git a/src/commands/manifest/scripts/test/maven-compat/smoke-test-workspaces.sh b/src/commands/manifest/scripts/test/maven-compat/smoke-test-workspaces.sh index 209ecb7209..116e871f15 100755 --- a/src/commands/manifest/scripts/test/maven-compat/smoke-test-workspaces.sh +++ b/src/commands/manifest/scripts/test/maven-compat/smoke-test-workspaces.sh @@ -45,10 +45,10 @@ if tool != 'maven': errors.append(f"meta tool {tool!r} != 'maven'") if build_root != sys.argv[2]: errors.append(f"buildRoot {build_root!r} != {sys.argv[2]!r}") -expected_build_files = {'pom.xml', 'lib/pom.xml', 'app/pom.xml'} +expected_build_files = {'pom.xml', 'lib/pom.xml', 'mid/pom.xml', 'app/pom.xml'} if build_files != expected_build_files: errors.append(f"projectBuild files {sorted(build_files)} != {sorted(expected_build_files)}") -expected = {'root', 'lib', 'app'} +expected = {'root', 'lib', 'mid', 'app'} if set(projects) != expected: errors.append(f"expected reactor modules {sorted(expected)}, got {sorted(projects)}") for artifact in expected & set(projects): diff --git a/src/commands/manifest/scripts/test/maven-compat/smoke-test.sh b/src/commands/manifest/scripts/test/maven-compat/smoke-test.sh index f5e55de2b5..be8dac3f40 100755 --- a/src/commands/manifest/scripts/test/maven-compat/smoke-test.sh +++ b/src/commands/manifest/scripts/test/maven-compat/smoke-test.sh @@ -8,6 +8,8 @@ # inter-module edge lines up with its `project` record); # - resolves a scope conflict so the retained node stays reachable from its parent root; # - every component is reachable from a direct dependency of its own root (no orphaned components); +# - keeps the dependencies Maven leaves only under a sibling module's test-jar (app reaches +# demo.ext:libdep under lib's test-jar node, after lib's jar node through mid); # - materializes resolved external jars under -Dsocket.withFiles; # - scopes that materialization to -Dsocket.populateFilesFor (a newline-delimited GAV file). # @@ -36,7 +38,8 @@ bash "$HERE/../make-stub-repo.sh" "$STUBS" \ 'demo.ext:harness-core:1.0' \ 'demo.ext:conflict-test:1.0+demo.ext:shared:1.0' \ 'demo.ext:conflict-prod:1.0+demo.ext:shared:1.0' \ - 'demo.ext:shared:1.0' + 'demo.ext:shared:1.0' \ + 'demo.ext:libdep:1.0' # Maven's own plugin closure stays cached between runs; the stubs never do, so every run has to # resolve them from the repo just generated. rm -rf "$M2/demo" "$RECORDS" "$PROJECT"/*/target "$PROJECT"/target @@ -52,6 +55,14 @@ echo "+ $("$MVN" -v 2>/dev/null | head -1)" compile ) python3 "$HERE/assert-records.py" "$RECORDS" +python3 - "$RECORDS" <<'PY' +import sys +rows = [l.rstrip('\n').split('\t') for l in open(sys.argv[1]) if l.strip()] +app = {r[1] for r in rows if r[0] == 'root' and r[2] == 'demo:app:1.0'} +if not any(r[0] == 'node' and r[1] in app and r[2].startswith('demo.ext:libdep:') for r in rows): + print("FAIL: demo.ext:libdep missing from app"); sys.exit(1) +print("PASS: demo.ext:libdep in app through lib's test-jar") +PY python3 "$HERE/../assert-reachability.py" "$RECORDS" # Second run: scope --with-files to a single GAV and assert ONLY that artifact is materialized.