Skip to content

Commit b5c19e4

Browse files
authored
fix(manifest): classify every Maven dependency scope as Maven does (#1717)
The Maven facts extension kept only Maven 3's five scopes and silently dropped any other dependency, subtree included. Maven 3 puts the jar of a scope it does not know (`import`, or a typo, in <dependencies>) on every classpath, and Maven 4 adds compile-only, test-only, test-runtime and none. Scopes Maven 3 does not know are now prod. Maven 4's compile-only, test-only and test-runtime are dev, on the compile or test path its PathScope gives them. Its none is on no path: the dependency and its subtree stay in the SBOM as dev, in a root the new noClasspath record marks, and the assembler leaves them off the reachability classpath and the sidecar's direct dependencies. --include-configs and --exclude-configs match any scope name. Resolves REA-1164.
1 parent bce3f0e commit b5c19e4

11 files changed

Lines changed: 333 additions & 51 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
88

99
### Fixed
1010
- Maven Socket facts honor artifact types that build extensions define, so they no longer list dependencies Maven doesn't resolve, or fetch files under the wrong extension.
11+
- Maven Socket facts no longer drop dependencies whose scope Maven 3 doesn't define, such as Maven 4's `compile-only` or `none`.
1112

1213
## [1.15.0](https://github.com/SocketDev/socket-cli/releases/tag/v1.15.0) - 2026-11-12
1314

‎src/commands/manifest/scripts/assemble.mts‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,15 @@ type MergedNode = {
5555

5656
type SubprojectGraph = {
5757
children: Map<string, Set<string>>
58+
// The nodes of the roots on a classpath: reachability analyses only these.
59+
classpath: Set<string>
5860
direct: Set<string>
5961
}
6062

6163
type PerRoot = {
6264
projectKey: string
6365
prod: boolean
66+
onClasspath: boolean
6467
target?: RawTarget | undefined
6568
nodes: Map<string, RootNode>
6669
}
@@ -177,6 +180,7 @@ function buildPerRoot(parsed: ParsedRecords): Map<string, PerRoot> {
177180
out.set(rootId, {
178181
projectKey: r.projectKey,
179182
prod: r.prod,
183+
onClasspath: r.onClasspath,
180184
target: r.target,
181185
nodes,
182186
})
@@ -259,13 +263,16 @@ function buildSubprojectGraphs(
259263
perRoot: Map<string, PerRoot>,
260264
): Map<string, SubprojectGraph> {
261265
const graphs = new Map<string, SubprojectGraph>()
262-
for (const { nodes, projectKey } of perRoot.values()) {
266+
for (const { nodes, onClasspath, projectKey } of perRoot.values()) {
263267
let g = graphs.get(projectKey)
264268
if (!g) {
265-
g = { children: new Map(), direct: new Set() }
269+
g = { children: new Map(), classpath: new Set(), direct: new Set() }
266270
graphs.set(projectKey, g)
267271
}
268272
for (const [coordId, node] of nodes) {
273+
if (onClasspath) {
274+
g.classpath.add(coordId)
275+
}
269276
let set = g.children.get(coordId)
270277
if (!set) {
271278
set = new Set()
@@ -480,8 +487,10 @@ function buildArtifactPaths(
480487
}
481488
return {
482489
pathsById,
483-
classpathByProject: byProject(projects, graphs, g => g.children.keys()),
484-
directDependenciesByProject: byProject(projects, graphs, g => g.direct),
490+
classpathByProject: byProject(projects, graphs, g => g.classpath),
491+
directDependenciesByProject: byProject(projects, graphs, g =>
492+
[...g.direct].filter(id => g.classpath.has(id)),
493+
),
485494
}
486495
}
487496

‎src/commands/manifest/scripts/assemble.test.mts‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,47 @@ describe('records → assemble → sidecar', () => {
441441
expect(assemble).not.toThrow()
442442
}
443443
})
444+
it('keeps the nodes of a root on no classpath in the SBOM but off every classpath', () => {
445+
// Maven 4: `none` is declared, and resolved with its subtree, but on no
446+
// path. Its child x is also on the classpath through lib.
447+
const records = [
448+
'meta\tmaven\t4.0.0\t21',
449+
'project\ta\tg\ta\t1\ta',
450+
'root\tr1\ta\tcompile\t1',
451+
'node\tr1\tg:lib:jar:1\tg\tlib\t1\tjar\t\t1',
452+
'node\tr1\tg:x:jar:1\tg\tx\t1\tjar\t\t0',
453+
'edge\tr1\tg:lib:jar:1\tg:x:jar:1',
454+
'root\tr2\ta\tnone\t0',
455+
'noClasspath\tr2',
456+
'node\tr2\tg:lib:jar:1\tg\tlib\t1\tjar\t\t1',
457+
'node\tr2\tg:x:jar:1\tg\tx\t1\tjar\t\t0',
458+
'node\tr2\tg:none:jar:1\tg\tnone\t1\tjar\t\t1',
459+
'node\tr2\tg:none-dep:jar:1\tg\tnone-dep\t1\tjar\t\t0',
460+
'edge\tr2\tg:lib:jar:1\tg:x:jar:1',
461+
'edge\tr2\tg:none:jar:1\tg:x:jar:1',
462+
'edge\tr2\tg:none:jar:1\tg:none-dep:jar:1',
463+
].join('\n')
464+
465+
const { artifactPaths, facts } = assembleFacts(parseRecords(records))
466+
467+
expect(trees(facts)).toEqual({
468+
a: [
469+
'g:lib:jar:1(g:x:jar:1)',
470+
'g:none:jar:1(g:x:jar:1, g:none-dep:jar:1)',
471+
],
472+
})
473+
expect(facts.components.filter(c => c.dev).map(c => c.id)).toEqual([
474+
'g:none-dep:jar:1',
475+
'g:none:jar:1',
476+
])
477+
expect(artifactPaths.classpathByProject.get('a')).toEqual([
478+
'g:lib:jar:1',
479+
'g:x:jar:1',
480+
])
481+
expect(artifactPaths.directDependenciesByProject.get('a')).toEqual([
482+
'g:lib:jar:1',
483+
])
484+
})
444485
it("gives a dependency on a build project that project's id, and marks only it firstParty", () => {
445486
const records = [
446487
'meta\tgradle\t8.0\t17',

‎src/commands/manifest/scripts/facts.mts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,11 @@ export type ComponentFramework = {
101101
export type ResolvedArtifactPaths = {
102102
pathsById: Map<string, { sources: string[]; targets?: string[] | undefined }>
103103
// Component ids on each project's resolved classpath (union over its
104-
// configurations), keyed by project id.
104+
// configurations), keyed by project id. A component on no classpath, such as
105+
// a Maven 4 `none` dependency, is in the SBOM but not here.
105106
classpathByProject: Map<string, string[]>
106-
// Component ids of each project's direct dependencies, keyed by project id.
107+
// Component ids of each project's direct dependencies on its classpath, keyed
108+
// by project id.
107109
directDependenciesByProject: Map<string, string[]>
108110
// dotnet only: the same per target framework.
109111
frameworksByProject?: Map<string, ProjectFramework[]> | undefined

0 commit comments

Comments
 (0)