@cdxgen/cdxgen 13.0.1 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -16
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +141 -7
- package/bin/evinse.js +3 -1
- package/data/component-tags.json +8 -1
- package/data/crypto-oid.json +88 -12
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1167 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +32 -1
- package/lib/cli/cliOptions.js +94 -7
- package/lib/cli/index.js +268 -49
- package/lib/cli/jsBom.js +91 -5
- package/lib/cli/jvmBom.js +1515 -1314
- package/lib/cli/managedBom.js +249 -28
- package/lib/cli/nativeBom.js +986 -10
- package/lib/core/activity.js +120 -2
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +108 -2
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +12 -99
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +13 -5
- package/lib/ecosystems/cppEvidence.js +40 -2
- package/lib/ecosystems/ecosystems.js +250 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +498 -17
- package/lib/ecosystems/kosi.js +1256 -0
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +69 -39
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +188 -2
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +172 -36
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +944 -19
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/ecosystems/rusi.js +126 -6
- package/lib/evinser/evinser.js +141 -13
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/remote/dependency-track.js +81 -14
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/auditCategories.js +17 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +7 -2
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +508 -22
- package/lib/inventory/envcontext.js +215 -24
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +131 -2
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +594 -0
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/osinfo.js +1 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +54 -88
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/source.js +10 -4
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +24 -0
- package/lib/managers/docker.js +26 -5
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +250 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +284 -3
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +26 -26
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +10 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +43 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +2 -11
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +53 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +73 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +21 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +34 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +12 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +43 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +43 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +190 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +58 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +35 -2
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -1,22 +1,43 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import { chmodSync } from "node:fs";
|
|
2
|
+
import { chmodSync, readFileSync } from "node:fs";
|
|
3
3
|
import { homedir, platform } from "node:os";
|
|
4
4
|
import { basename, join, resolve } from "node:path";
|
|
5
5
|
|
|
6
6
|
import { build } from "@cdxgen/cdx-purl";
|
|
7
|
+
import * as toml from "smol-toml";
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
DEBUG_MODE,
|
|
10
11
|
readEnvironmentVariable,
|
|
11
12
|
recordDecisionActivity,
|
|
12
13
|
} from "../core/activity.js";
|
|
14
|
+
import {
|
|
15
|
+
commandOutputText,
|
|
16
|
+
isLedgerEnabled,
|
|
17
|
+
LEDGER_EVENT_IMPACTS,
|
|
18
|
+
LEDGER_EVENT_KINDS,
|
|
19
|
+
recordDegradation,
|
|
20
|
+
recordLedgerEvent,
|
|
21
|
+
} from "../core/buildLedger.js";
|
|
13
22
|
import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
|
|
14
23
|
import { thoughtLog } from "../core/logger.js";
|
|
15
24
|
import { isWin } from "../core/paths.js";
|
|
16
25
|
import { collectJarNS } from "../inventory/deps.js";
|
|
17
|
-
import {
|
|
26
|
+
import { minimumGradleVersionForJava } from "../inventory/jvmToolEnv.js";
|
|
27
|
+
import { applyPurl, tryBuildPurl } from "../inventory/purl.js";
|
|
18
28
|
import { parsePkgJson } from "./parsers-js.js";
|
|
19
29
|
|
|
30
|
+
/**
|
|
31
|
+
* True when the pre-generation stage pinned an exact JVM build tool version
|
|
32
|
+
* from the CLI (e.g. `-t maven3.9.9`). Project wrappers must not override
|
|
33
|
+
* such an explicit pin.
|
|
34
|
+
*/
|
|
35
|
+
function isJvmToolPinned() {
|
|
36
|
+
return ["true", "1"].includes(
|
|
37
|
+
readEnvironmentVariable("CDXGEN_JVM_TOOL_PINNED"),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
20
41
|
/**
|
|
21
42
|
* Method to return the gradle command to use.
|
|
22
43
|
*
|
|
@@ -25,19 +46,21 @@ import { parsePkgJson } from "./parsers-js.js";
|
|
|
25
46
|
*/
|
|
26
47
|
export function getGradleCommand(srcPath, rootPath) {
|
|
27
48
|
let gradleCmd = "gradle";
|
|
49
|
+
const pinnedToolSelected = isJvmToolPinned();
|
|
28
50
|
|
|
29
51
|
let findGradleFile = "gradlew";
|
|
30
52
|
if (platform() === "win32") {
|
|
31
53
|
findGradleFile = "gradlew.bat";
|
|
32
54
|
}
|
|
33
55
|
|
|
34
|
-
if (safeExistsSync(join(srcPath, findGradleFile))) {
|
|
56
|
+
if (!pinnedToolSelected && safeExistsSync(join(srcPath, findGradleFile))) {
|
|
35
57
|
// Use local gradle wrapper if available
|
|
36
58
|
// Enable execute permission
|
|
37
59
|
try {
|
|
38
60
|
chmodSync(join(srcPath, findGradleFile), 0o775);
|
|
39
61
|
} catch (_e) {
|
|
40
|
-
//
|
|
62
|
+
// The wrapper stays un-chmod'ed; the spawn below still reports any
|
|
63
|
+
// permission failure as a normal command failure.
|
|
41
64
|
}
|
|
42
65
|
gradleCmd = resolve(join(srcPath, findGradleFile));
|
|
43
66
|
recordDecisionActivity(gradleCmd, {
|
|
@@ -48,12 +71,17 @@ export function getGradleCommand(srcPath, rootPath) {
|
|
|
48
71
|
},
|
|
49
72
|
reason: `Selected project-local Gradle wrapper ${gradleCmd}.`,
|
|
50
73
|
});
|
|
51
|
-
} else if (
|
|
74
|
+
} else if (
|
|
75
|
+
!pinnedToolSelected &&
|
|
76
|
+
rootPath &&
|
|
77
|
+
safeExistsSync(join(rootPath, findGradleFile))
|
|
78
|
+
) {
|
|
52
79
|
// Check if the root directory has a wrapper script
|
|
53
80
|
try {
|
|
54
81
|
chmodSync(join(rootPath, findGradleFile), 0o775);
|
|
55
82
|
} catch (_e) {
|
|
56
|
-
//
|
|
83
|
+
// The wrapper stays un-chmod'ed; the spawn below still reports any
|
|
84
|
+
// permission failure as a normal command failure.
|
|
57
85
|
}
|
|
58
86
|
gradleCmd = resolve(join(rootPath, findGradleFile));
|
|
59
87
|
recordDecisionActivity(gradleCmd, {
|
|
@@ -69,10 +97,12 @@ export function getGradleCommand(srcPath, rootPath) {
|
|
|
69
97
|
recordDecisionActivity(gradleCmd, {
|
|
70
98
|
metadata: {
|
|
71
99
|
decisionType: "path-resolution",
|
|
72
|
-
selectedSource: "GRADLE_CMD",
|
|
100
|
+
selectedSource: pinnedToolSelected ? "pinned-cli" : "GRADLE_CMD",
|
|
73
101
|
tool: "gradle",
|
|
74
102
|
},
|
|
75
|
-
reason:
|
|
103
|
+
reason: pinnedToolSelected
|
|
104
|
+
? `Selected Gradle command ${gradleCmd} pinned via the CLI project type.`
|
|
105
|
+
: `Selected Gradle command from GRADLE_CMD (${gradleCmd}).`,
|
|
76
106
|
});
|
|
77
107
|
} else if (readEnvironmentVariable("GRADLE_HOME")) {
|
|
78
108
|
gradleCmd = join(readEnvironmentVariable("GRADLE_HOME"), "bin", "gradle");
|
|
@@ -93,6 +123,16 @@ export function getGradleCommand(srcPath, rootPath) {
|
|
|
93
123
|
},
|
|
94
124
|
reason: "Falling back to Gradle from PATH.",
|
|
95
125
|
});
|
|
126
|
+
// A Gradle from PATH is a sound outcome whenever its version satisfies
|
|
127
|
+
// the wrapper pin, so the fallback is recorded as costing nothing.
|
|
128
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.FALLBACK_ENGAGED, {
|
|
129
|
+
ecosystem: "java",
|
|
130
|
+
tool: "gradle",
|
|
131
|
+
source: "PATH",
|
|
132
|
+
impact: LEDGER_EVENT_IMPACTS.NONE,
|
|
133
|
+
detail:
|
|
134
|
+
"No wrapper, GRADLE_CMD, or GRADLE_HOME was found; Gradle will run from PATH.",
|
|
135
|
+
});
|
|
96
136
|
}
|
|
97
137
|
return gradleCmd;
|
|
98
138
|
}
|
|
@@ -368,6 +408,9 @@ export function executeParallelGradleProperties(
|
|
|
368
408
|
shell: isWin,
|
|
369
409
|
});
|
|
370
410
|
if (result.status !== 0 || result.error) {
|
|
411
|
+
recordGradleInvocationFailure(result, {
|
|
412
|
+
command: `${basename(gradleCmd)} ${gradleArg.join(" ")}`,
|
|
413
|
+
});
|
|
371
414
|
if (readEnvironmentVariable("CDXGEN_IN_CONTAINER") === "true") {
|
|
372
415
|
thoughtLog(
|
|
373
416
|
"Gradle build has failed. Perhaps the user is using the wrong container image?",
|
|
@@ -420,6 +463,279 @@ export function executeParallelGradleProperties(
|
|
|
420
463
|
return "";
|
|
421
464
|
}
|
|
422
465
|
|
|
466
|
+
/** First class-file major a JDK maps from: class file 52 is Java 8. */
|
|
467
|
+
const CLASS_FILE_MAJOR_OFFSET = 44;
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Causes already reported this run, so a properties invocation and a
|
|
471
|
+
* dependencies invocation failing for the same reason record the diagnosis
|
|
472
|
+
* once.
|
|
473
|
+
*
|
|
474
|
+
* @type {Set<string>}
|
|
475
|
+
*/
|
|
476
|
+
const reportedGradleFailureCauses = new Set();
|
|
477
|
+
|
|
478
|
+
/** Whether an invocation failure was recorded this run. */
|
|
479
|
+
let gradleInvocationFailed = false;
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Forget the causes diagnosed so far, so a process that generates more than
|
|
483
|
+
* one BOM diagnoses each run on its own.
|
|
484
|
+
*
|
|
485
|
+
* @returns {void}
|
|
486
|
+
*/
|
|
487
|
+
export function resetGradleFailureCauses() {
|
|
488
|
+
reportedGradleFailureCauses.clear();
|
|
489
|
+
gradleInvocationFailed = false;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Whether a gradle invocation has already failed outright this run, so a
|
|
494
|
+
* caller can skip reporting a consequence of that failure as its own
|
|
495
|
+
* degradation.
|
|
496
|
+
*
|
|
497
|
+
* @returns {boolean} True once an invocation failure has been recorded.
|
|
498
|
+
*/
|
|
499
|
+
export function hasGradleInvocationFailure() {
|
|
500
|
+
return gradleInvocationFailed;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Extract the Java major from an `Unsupported class file major version N`
|
|
505
|
+
* message: the class file the running JVM refused, which names the JDK that
|
|
506
|
+
* is too new for this Gradle.
|
|
507
|
+
*
|
|
508
|
+
* @param {string} text Combined gradle output.
|
|
509
|
+
* @returns {number|undefined} The refused Java major, when named.
|
|
510
|
+
*/
|
|
511
|
+
function refusedJavaMajorFromMessage(text) {
|
|
512
|
+
const marker = "Unsupported class file major version ";
|
|
513
|
+
const start = text.indexOf(marker);
|
|
514
|
+
if (start === -1) {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
const digits = text
|
|
518
|
+
.slice(start + marker.length)
|
|
519
|
+
.split(/\s|\.|,|\r|\n/)[0]
|
|
520
|
+
.trim();
|
|
521
|
+
const major = Number.parseInt(digits, 10);
|
|
522
|
+
return Number.isNaN(major) ? undefined : major - CLASS_FILE_MAJOR_OFFSET;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Extract the Java pair the modern launcher refusal names: the JVM major
|
|
527
|
+
* Gradle demands and, when the message says so, the one the build is
|
|
528
|
+
* configured to use. Anything the message does not clearly name stays
|
|
529
|
+
* undefined — the demanded major is substituted into an install command, so
|
|
530
|
+
* it must be a bare Java major and nothing else.
|
|
531
|
+
*
|
|
532
|
+
* @param {string} text Combined gradle output.
|
|
533
|
+
* @returns {{wanted: number, found: number|undefined}|undefined} The required and running Java majors, when the refusal names them.
|
|
534
|
+
*/
|
|
535
|
+
function javaPairFromLauncherDemand(text) {
|
|
536
|
+
const demandMarker = "Gradle requires JVM ";
|
|
537
|
+
const demandStart = text.indexOf(demandMarker);
|
|
538
|
+
if (demandStart === -1) {
|
|
539
|
+
return undefined;
|
|
540
|
+
}
|
|
541
|
+
const wantedDigits = text
|
|
542
|
+
.slice(demandStart + demandMarker.length)
|
|
543
|
+
.split(/\s|\.|,|\r|\n/)[0]
|
|
544
|
+
.trim();
|
|
545
|
+
const wanted = Number.parseInt(wantedDigits, 10);
|
|
546
|
+
if (Number.isNaN(wanted)) {
|
|
547
|
+
return undefined;
|
|
548
|
+
}
|
|
549
|
+
const foundMarker = "configured to use JVM ";
|
|
550
|
+
const foundStart = text.indexOf(foundMarker);
|
|
551
|
+
let found;
|
|
552
|
+
if (foundStart !== -1) {
|
|
553
|
+
const foundDigits = text
|
|
554
|
+
.slice(foundStart + foundMarker.length)
|
|
555
|
+
.split(/\s|\.|,|\r|\n/)[0]
|
|
556
|
+
.trim();
|
|
557
|
+
const parsed = Number.parseInt(foundDigits, 10);
|
|
558
|
+
if (!Number.isNaN(parsed)) {
|
|
559
|
+
found = parsed;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return { wanted, found };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Extract the Java pair an `UnsupportedClassVersionError` names: the class
|
|
567
|
+
* file version that was compiled for a newer JDK and the version the running
|
|
568
|
+
* JDK recognises, so the event can say "needs Java A, found Java B".
|
|
569
|
+
*
|
|
570
|
+
* @param {string} text Combined gradle output.
|
|
571
|
+
* @returns {{wanted: number, found: number}|undefined} The required and running Java majors.
|
|
572
|
+
*/
|
|
573
|
+
function javaPairFromClassVersionError(text) {
|
|
574
|
+
if (!text.includes("UnsupportedClassVersionError")) {
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
const compiledMarker = "class file version ";
|
|
578
|
+
const compiledStart = text.indexOf(compiledMarker);
|
|
579
|
+
const limitMarker = "recognizes class file versions up to ";
|
|
580
|
+
const limitStart = text.indexOf(limitMarker);
|
|
581
|
+
if (compiledStart === -1 || limitStart === -1) {
|
|
582
|
+
return { wanted: Number.NaN, found: Number.NaN };
|
|
583
|
+
}
|
|
584
|
+
const wanted = Number.parseInt(
|
|
585
|
+
text.slice(compiledStart + compiledMarker.length).trim(),
|
|
586
|
+
10,
|
|
587
|
+
);
|
|
588
|
+
const found = Number.parseInt(
|
|
589
|
+
text.slice(limitStart + limitMarker.length).trim(),
|
|
590
|
+
10,
|
|
591
|
+
);
|
|
592
|
+
return {
|
|
593
|
+
wanted: wanted - CLASS_FILE_MAJOR_OFFSET,
|
|
594
|
+
found: found - CLASS_FILE_MAJOR_OFFSET,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Record why a gradle invocation failed. Every failure also records the
|
|
600
|
+
* `jvm.gradle.invocation-failed` degradation so the reflection can rank the
|
|
601
|
+
* repair; version-incompatibility causes additionally record a
|
|
602
|
+
* `tool.mismatch` event naming the versions, which is the fact the report
|
|
603
|
+
* cannot derive from an empty BOM alone. Diagnosis events are recorded once
|
|
604
|
+
* per cause per run; the degradation dedupes in the reflection by id.
|
|
605
|
+
*
|
|
606
|
+
* @param {Object} result The spawnSync result of the failed invocation.
|
|
607
|
+
* @param {Object} context Call-site facts.
|
|
608
|
+
* @param {string} [context.command] The redacted command that was attempted.
|
|
609
|
+
* @param {string} [context.gradleVersion] The gradle version in play, when the caller knows it.
|
|
610
|
+
* @returns {void}
|
|
611
|
+
*/
|
|
612
|
+
export function recordGradleInvocationFailure(result, context = {}) {
|
|
613
|
+
if (!isLedgerEnabled()) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (!result || (result.status === 0 && !result.error)) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const output = `${result.stderr || ""}\n${result.stdout || ""}`;
|
|
620
|
+
const exitCode =
|
|
621
|
+
typeof result.status === "number" ? result.status : undefined;
|
|
622
|
+
let causeDetail = "the gradle invocation failed";
|
|
623
|
+
|
|
624
|
+
// The JDK is too new for this Gradle: the build script or the launcher
|
|
625
|
+
// refuses its class files. Gradle's own compatibility table names the
|
|
626
|
+
// minimum release that runs on the refused Java major.
|
|
627
|
+
const refusedJavaMajor = refusedJavaMajorFromMessage(output);
|
|
628
|
+
// Gradle's own modern refusal, which names the JVM it demands before any
|
|
629
|
+
// class file is loaded.
|
|
630
|
+
const launcherDemand =
|
|
631
|
+
refusedJavaMajor === undefined
|
|
632
|
+
? javaPairFromLauncherDemand(output)
|
|
633
|
+
: undefined;
|
|
634
|
+
if (refusedJavaMajor !== undefined) {
|
|
635
|
+
const minimumGradle = minimumGradleVersionForJava(refusedJavaMajor);
|
|
636
|
+
const causeKey = `java-too-new:${refusedJavaMajor}`;
|
|
637
|
+
if (!reportedGradleFailureCauses.has(causeKey)) {
|
|
638
|
+
reportedGradleFailureCauses.add(causeKey);
|
|
639
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISMATCH, {
|
|
640
|
+
ecosystem: "java",
|
|
641
|
+
tool: "gradle",
|
|
642
|
+
wanted: minimumGradle ? `${minimumGradle}` : undefined,
|
|
643
|
+
found: context.gradleVersion,
|
|
644
|
+
source: "invocation",
|
|
645
|
+
detail: minimumGradle
|
|
646
|
+
? `Gradle cannot run on Java ${refusedJavaMajor} and refused its class files; Gradle ${minimumGradle} or higher supports it.`
|
|
647
|
+
: `Gradle cannot run on Java ${refusedJavaMajor} and refused its class files.`,
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
causeDetail = `gradle cannot run on Java ${refusedJavaMajor}`;
|
|
651
|
+
} else if (launcherDemand) {
|
|
652
|
+
// The demand names the JDK Gradle needs; the JVM that answered it is
|
|
653
|
+
// the one the build was configured to use.
|
|
654
|
+
const causeKey = `jvm-demanded:${launcherDemand.wanted}:${launcherDemand.found}`;
|
|
655
|
+
if (!reportedGradleFailureCauses.has(causeKey)) {
|
|
656
|
+
reportedGradleFailureCauses.add(causeKey);
|
|
657
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISMATCH, {
|
|
658
|
+
ecosystem: "java",
|
|
659
|
+
tool: "java",
|
|
660
|
+
wanted: `${launcherDemand.wanted}`,
|
|
661
|
+
found:
|
|
662
|
+
launcherDemand.found === undefined
|
|
663
|
+
? undefined
|
|
664
|
+
: `${launcherDemand.found}`,
|
|
665
|
+
source: "invocation",
|
|
666
|
+
detail:
|
|
667
|
+
launcherDemand.found === undefined
|
|
668
|
+
? `Gradle requires Java ${launcherDemand.wanted} or later to run.`
|
|
669
|
+
: `Gradle requires Java ${launcherDemand.wanted} or later to run; the active JVM is Java ${launcherDemand.found}.`,
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
causeDetail = "the gradle launcher requires a newer JDK";
|
|
673
|
+
} else {
|
|
674
|
+
// The opposite skew: Gradle (or its wrapper) is compiled for a newer JDK
|
|
675
|
+
// than the running one, so the JVM refuses to load it.
|
|
676
|
+
const javaPair = javaPairFromClassVersionError(output);
|
|
677
|
+
if (javaPair) {
|
|
678
|
+
const causeKey = `jvm-too-old:${javaPair.wanted}:${javaPair.found}`;
|
|
679
|
+
if (!reportedGradleFailureCauses.has(causeKey)) {
|
|
680
|
+
reportedGradleFailureCauses.add(causeKey);
|
|
681
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISMATCH, {
|
|
682
|
+
ecosystem: "java",
|
|
683
|
+
tool: "java",
|
|
684
|
+
wanted: Number.isNaN(javaPair.wanted)
|
|
685
|
+
? undefined
|
|
686
|
+
: `${javaPair.wanted}`,
|
|
687
|
+
found: Number.isNaN(javaPair.found) ? undefined : `${javaPair.found}`,
|
|
688
|
+
source: "invocation",
|
|
689
|
+
detail: `The gradle launcher requires Java ${Number.isNaN(javaPair.wanted) ? "a newer release" : javaPair.wanted}; the active JDK is older and refused its class files.`,
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
causeDetail = "the gradle launcher requires a newer JDK";
|
|
693
|
+
} else if (output.includes("JAVA_HOME is set to an invalid directory")) {
|
|
694
|
+
const causeKey = "invalid-java-home";
|
|
695
|
+
if (!reportedGradleFailureCauses.has(causeKey)) {
|
|
696
|
+
reportedGradleFailureCauses.add(causeKey);
|
|
697
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISSING, {
|
|
698
|
+
ecosystem: "java",
|
|
699
|
+
tool: "java",
|
|
700
|
+
source: "JAVA_HOME",
|
|
701
|
+
detail:
|
|
702
|
+
"JAVA_HOME names a directory that does not hold a JDK, so the gradle launcher refused to start.",
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
causeDetail = "JAVA_HOME does not point at a JDK";
|
|
706
|
+
} else if (
|
|
707
|
+
output.includes("Could not start Gradle Daemon") ||
|
|
708
|
+
output.includes("Daemon could not be started") ||
|
|
709
|
+
output.includes("could not install Gradle distribution")
|
|
710
|
+
) {
|
|
711
|
+
const causeKey = "daemon-start";
|
|
712
|
+
if (!reportedGradleFailureCauses.has(causeKey)) {
|
|
713
|
+
reportedGradleFailureCauses.add(causeKey);
|
|
714
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.EVIDENCE_DEGRADED, {
|
|
715
|
+
ecosystem: "java",
|
|
716
|
+
tool: "gradle",
|
|
717
|
+
impact: LEDGER_EVENT_IMPACTS.VERSIONS,
|
|
718
|
+
detail:
|
|
719
|
+
"The gradle daemon did not start, so no dependency resolution ran.",
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
causeDetail = "the gradle daemon did not start";
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
gradleInvocationFailed = true;
|
|
727
|
+
recordDegradation("jvm.gradle.invocation-failed", {
|
|
728
|
+
ecosystem: "java",
|
|
729
|
+
tool: "gradle",
|
|
730
|
+
impact: LEDGER_EVENT_IMPACTS.TRANSITIVE_DEPS,
|
|
731
|
+
command: context.command,
|
|
732
|
+
exitCode,
|
|
733
|
+
detail: causeDetail,
|
|
734
|
+
causeDetail,
|
|
735
|
+
outputExcerpt: commandOutputText(result),
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
|
|
423
739
|
/**
|
|
424
740
|
* Method to resolve dependencies from a gradle output
|
|
425
741
|
*
|
|
@@ -583,6 +899,8 @@ export async function parseGradleDep(
|
|
|
583
899
|
});
|
|
584
900
|
bomRef = decodeURIComponent(purl);
|
|
585
901
|
} catch {
|
|
902
|
+
// A purl that cannot be built keeps a name-only bom-ref so the
|
|
903
|
+
// component still lands in the graph.
|
|
586
904
|
purl = undefined;
|
|
587
905
|
bomRef = name.replace(/^:/, "");
|
|
588
906
|
}
|
|
@@ -724,6 +1042,7 @@ export async function buildObjectForGradleModule(name, metadata) {
|
|
|
724
1042
|
});
|
|
725
1043
|
applyPurl(component, purl);
|
|
726
1044
|
} catch {
|
|
1045
|
+
// The module component keeps its name identity without a purl.
|
|
727
1046
|
applyPurl(component, null);
|
|
728
1047
|
}
|
|
729
1048
|
}
|
|
@@ -896,7 +1215,8 @@ export function getMillCommand(srcPath) {
|
|
|
896
1215
|
try {
|
|
897
1216
|
chmodSync(join(srcPath, millCmd), 0o775);
|
|
898
1217
|
} catch (_e) {
|
|
899
|
-
//
|
|
1218
|
+
// The mill wrapper stays un-chmod'ed; the spawn below still reports any
|
|
1219
|
+
// permission failure as a normal command failure.
|
|
900
1220
|
}
|
|
901
1221
|
millCmd = resolve(join(srcPath, millCmd));
|
|
902
1222
|
}
|
|
@@ -914,6 +1234,7 @@ export function getMavenCommand(srcPath, rootPath) {
|
|
|
914
1234
|
// Check if the wrapper script is both available and functional
|
|
915
1235
|
let isWrapperReady = false;
|
|
916
1236
|
let isWrapperFound = false;
|
|
1237
|
+
const pinnedToolSelected = isJvmToolPinned();
|
|
917
1238
|
let findMavenFile = "mvnw";
|
|
918
1239
|
let mavenWrapperCmd = null;
|
|
919
1240
|
if (platform() === "win32") {
|
|
@@ -926,13 +1247,14 @@ export function getMavenCommand(srcPath, rootPath) {
|
|
|
926
1247
|
}
|
|
927
1248
|
}
|
|
928
1249
|
|
|
929
|
-
if (safeExistsSync(join(srcPath, findMavenFile))) {
|
|
1250
|
+
if (!pinnedToolSelected && safeExistsSync(join(srcPath, findMavenFile))) {
|
|
930
1251
|
// Use local maven wrapper if available
|
|
931
1252
|
// Enable execute permission
|
|
932
1253
|
try {
|
|
933
1254
|
chmodSync(join(srcPath, findMavenFile), 0o775);
|
|
934
1255
|
} catch (_e) {
|
|
935
|
-
//
|
|
1256
|
+
// The wrapper stays un-chmod'ed; the spawn below still reports any
|
|
1257
|
+
// permission failure as a normal command failure.
|
|
936
1258
|
}
|
|
937
1259
|
mavenWrapperCmd = resolve(join(srcPath, findMavenFile));
|
|
938
1260
|
isWrapperFound = true;
|
|
@@ -944,12 +1266,17 @@ export function getMavenCommand(srcPath, rootPath) {
|
|
|
944
1266
|
},
|
|
945
1267
|
reason: `Found Maven wrapper candidate ${mavenWrapperCmd}.`,
|
|
946
1268
|
});
|
|
947
|
-
} else if (
|
|
1269
|
+
} else if (
|
|
1270
|
+
!pinnedToolSelected &&
|
|
1271
|
+
rootPath &&
|
|
1272
|
+
safeExistsSync(join(rootPath, findMavenFile))
|
|
1273
|
+
) {
|
|
948
1274
|
// Check if the root directory has a wrapper script
|
|
949
1275
|
try {
|
|
950
1276
|
chmodSync(join(rootPath, findMavenFile), 0o775);
|
|
951
1277
|
} catch (_e) {
|
|
952
|
-
//
|
|
1278
|
+
// The wrapper stays un-chmod'ed; the spawn below still reports any
|
|
1279
|
+
// permission failure as a normal command failure.
|
|
953
1280
|
}
|
|
954
1281
|
mavenWrapperCmd = resolve(join(rootPath, findMavenFile));
|
|
955
1282
|
isWrapperFound = true;
|
|
@@ -1016,12 +1343,16 @@ export function getMavenCommand(srcPath, rootPath) {
|
|
|
1016
1343
|
recordDecisionActivity(mavenCmd, {
|
|
1017
1344
|
metadata: {
|
|
1018
1345
|
decisionType: "path-resolution",
|
|
1019
|
-
selectedSource:
|
|
1020
|
-
? "
|
|
1021
|
-
: "
|
|
1346
|
+
selectedSource: pinnedToolSelected
|
|
1347
|
+
? "pinned-cli"
|
|
1348
|
+
: readEnvironmentVariable("MVN_CMD")
|
|
1349
|
+
? "MVN_CMD"
|
|
1350
|
+
: "MAVEN_CMD",
|
|
1022
1351
|
tool: "maven",
|
|
1023
1352
|
},
|
|
1024
|
-
reason:
|
|
1353
|
+
reason: pinnedToolSelected
|
|
1354
|
+
? `Selected Maven command ${mavenCmd} pinned via the CLI project type.`
|
|
1355
|
+
: `Selected Maven command from environment (${mavenCmd}).`,
|
|
1025
1356
|
});
|
|
1026
1357
|
} else if (readEnvironmentVariable("MAVEN_HOME")) {
|
|
1027
1358
|
mavenCmd = join(readEnvironmentVariable("MAVEN_HOME"), "bin", "mvn");
|
|
@@ -1042,7 +1373,157 @@ export function getMavenCommand(srcPath, rootPath) {
|
|
|
1042
1373
|
},
|
|
1043
1374
|
reason: "Falling back to Maven from PATH.",
|
|
1044
1375
|
});
|
|
1376
|
+
// A Maven from PATH is a sound outcome whenever its version satisfies
|
|
1377
|
+
// the wrapper pin, so the fallback is recorded as costing nothing.
|
|
1378
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.FALLBACK_ENGAGED, {
|
|
1379
|
+
ecosystem: "java",
|
|
1380
|
+
tool: "maven",
|
|
1381
|
+
source: "PATH",
|
|
1382
|
+
impact: LEDGER_EVENT_IMPACTS.NONE,
|
|
1383
|
+
detail:
|
|
1384
|
+
"No wrapper, MVN_CMD/MAVEN_CMD, or MAVEN_HOME was found; Maven will run from PATH.",
|
|
1385
|
+
});
|
|
1045
1386
|
}
|
|
1046
1387
|
}
|
|
1047
1388
|
return mavenCmd;
|
|
1048
1389
|
}
|
|
1390
|
+
|
|
1391
|
+
/**
|
|
1392
|
+
* Parse a Gradle version catalog (`gradle/libs.versions.toml`).
|
|
1393
|
+
*
|
|
1394
|
+
* A catalog declares coordinates the build scripts then reference by alias.
|
|
1395
|
+
* It is a declaration, not a resolution: entries may go unused, and their
|
|
1396
|
+
* versions may be references into the `[versions]` table. Because of that,
|
|
1397
|
+
* catalog components are emitted only as a fallback when Gradle itself
|
|
1398
|
+
* produced no dependency information, and every component carries a
|
|
1399
|
+
* `cdx:gradle:catalog=true` property so consumers can tell declared catalog
|
|
1400
|
+
* entries from a resolved graph.
|
|
1401
|
+
*
|
|
1402
|
+
* Only libraries whose version resolves to a literal (inline or via
|
|
1403
|
+
* `version.ref`) are emitted; rich or unresolved versions would produce purls
|
|
1404
|
+
* that identify no artifact. Plugins are skipped because they are build-time
|
|
1405
|
+
* only.
|
|
1406
|
+
*
|
|
1407
|
+
* @param {string} catalogFile Path to `libs.versions.toml`
|
|
1408
|
+
* @returns {Object[]} Package records for resolvable library entries
|
|
1409
|
+
*/
|
|
1410
|
+
export function parseGradleVersionCatalog(catalogFile) {
|
|
1411
|
+
let catalog;
|
|
1412
|
+
try {
|
|
1413
|
+
catalog = toml.parse(readFileSync(catalogFile, "utf-8"));
|
|
1414
|
+
} catch (error) {
|
|
1415
|
+
console.warn(`Failed to parse ${catalogFile}: ${error.message}`);
|
|
1416
|
+
return [];
|
|
1417
|
+
}
|
|
1418
|
+
const versions =
|
|
1419
|
+
catalog?.versions && typeof catalog.versions === "object"
|
|
1420
|
+
? catalog.versions
|
|
1421
|
+
: {};
|
|
1422
|
+
const libraries =
|
|
1423
|
+
catalog?.libraries && typeof catalog.libraries === "object"
|
|
1424
|
+
? catalog.libraries
|
|
1425
|
+
: {};
|
|
1426
|
+
const pkgList = [];
|
|
1427
|
+
for (const entry of Object.values(libraries)) {
|
|
1428
|
+
const coordinate = catalogCoordinate(entry);
|
|
1429
|
+
if (!coordinate) {
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
const version = resolveCatalogVersion(entry, versions);
|
|
1433
|
+
if (!version) {
|
|
1434
|
+
continue;
|
|
1435
|
+
}
|
|
1436
|
+
const { group, name } = coordinate;
|
|
1437
|
+
// A catalog is authored input, but a malformed coordinate must cost the
|
|
1438
|
+
// one entry, not the whole scan.
|
|
1439
|
+
const purl = tryBuildPurl({
|
|
1440
|
+
type: "maven",
|
|
1441
|
+
namespace: group,
|
|
1442
|
+
name,
|
|
1443
|
+
version,
|
|
1444
|
+
});
|
|
1445
|
+
if (!purl) {
|
|
1446
|
+
continue;
|
|
1447
|
+
}
|
|
1448
|
+
pkgList.push({
|
|
1449
|
+
group,
|
|
1450
|
+
name,
|
|
1451
|
+
version,
|
|
1452
|
+
type: "library",
|
|
1453
|
+
scope: "required",
|
|
1454
|
+
purl,
|
|
1455
|
+
"bom-ref": decodeURIComponent(purl),
|
|
1456
|
+
properties: [
|
|
1457
|
+
{ name: "internal:SrcFile", value: catalogFile },
|
|
1458
|
+
{ name: "cdx:gradle:catalog", value: "true" },
|
|
1459
|
+
],
|
|
1460
|
+
evidence: {
|
|
1461
|
+
identity: {
|
|
1462
|
+
field: "purl",
|
|
1463
|
+
confidence: 0.5,
|
|
1464
|
+
methods: [
|
|
1465
|
+
{
|
|
1466
|
+
technique: "source-code-analysis",
|
|
1467
|
+
confidence: 0.5,
|
|
1468
|
+
value: `Filename ${catalogFile}`,
|
|
1469
|
+
},
|
|
1470
|
+
],
|
|
1471
|
+
},
|
|
1472
|
+
},
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
return pkgList;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/**
|
|
1479
|
+
* Read the Maven coordinate of one catalog entry.
|
|
1480
|
+
*
|
|
1481
|
+
* A catalog writes the coordinate either as `module = "group:name"` or as a
|
|
1482
|
+
* separate `group` and `name` pair; both forms are equivalent and both appear
|
|
1483
|
+
* in real catalogs.
|
|
1484
|
+
*
|
|
1485
|
+
* @param {object} entry Catalog library entry
|
|
1486
|
+
* @returns {{group: string, name: string}|undefined} Coordinate, or undefined
|
|
1487
|
+
*/
|
|
1488
|
+
function catalogCoordinate(entry) {
|
|
1489
|
+
const module = typeof entry?.module === "string" ? entry.module : undefined;
|
|
1490
|
+
if (module?.includes(":")) {
|
|
1491
|
+
const separator = module.indexOf(":");
|
|
1492
|
+
return {
|
|
1493
|
+
group: module.slice(0, separator),
|
|
1494
|
+
name: module.slice(separator + 1),
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
if (typeof entry?.group === "string" && typeof entry?.name === "string") {
|
|
1498
|
+
return { group: entry.group, name: entry.name };
|
|
1499
|
+
}
|
|
1500
|
+
return undefined;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/**
|
|
1504
|
+
* Resolve the version of one catalog entry.
|
|
1505
|
+
*
|
|
1506
|
+
* An entry may pin `version = "1.2.3"` directly or reference the versions
|
|
1507
|
+
* table with `version.ref = "alias"`. Version references that are missing, or
|
|
1508
|
+
* that carry Gradle rich-version syntax (ranges, `!!`, separators), resolve
|
|
1509
|
+
* to nothing and the entry is skipped.
|
|
1510
|
+
*
|
|
1511
|
+
* @param {object} entry Catalog library entry
|
|
1512
|
+
* @param {object} versions Parsed `[versions]` table
|
|
1513
|
+
* @returns {string|undefined} Concrete version, or undefined
|
|
1514
|
+
*/
|
|
1515
|
+
function resolveCatalogVersion(entry, versions) {
|
|
1516
|
+
const raw =
|
|
1517
|
+
typeof entry?.version === "string"
|
|
1518
|
+
? entry.version
|
|
1519
|
+
: typeof entry?.version?.ref === "string"
|
|
1520
|
+
? versions[entry.version.ref]
|
|
1521
|
+
: undefined;
|
|
1522
|
+
if (typeof raw !== "string" || !raw) {
|
|
1523
|
+
return undefined;
|
|
1524
|
+
}
|
|
1525
|
+
if (/[[\](),!]|->|\.\./u.test(raw)) {
|
|
1526
|
+
return undefined;
|
|
1527
|
+
}
|
|
1528
|
+
return raw;
|
|
1529
|
+
}
|