@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
package/lib/cli/jvmBom.js
CHANGED
|
@@ -8,6 +8,11 @@ import { build } from "@cdxgen/cdx-purl";
|
|
|
8
8
|
import { gte, lte } from "semver";
|
|
9
9
|
|
|
10
10
|
import { DEBUG_MODE, readEnvironmentVariable } from "../core/activity.js";
|
|
11
|
+
import { commandOutputText, recordDegradation } from "../core/buildLedger.js";
|
|
12
|
+
import {
|
|
13
|
+
deferFailOnError,
|
|
14
|
+
isDeferredFailOnError,
|
|
15
|
+
} from "../core/deferredExit.js";
|
|
11
16
|
import {
|
|
12
17
|
hasAnyProjectType,
|
|
13
18
|
includeMavenTestScope,
|
|
@@ -39,10 +44,13 @@ import {
|
|
|
39
44
|
getGradleCommand,
|
|
40
45
|
getMavenCommand,
|
|
41
46
|
getMillCommand,
|
|
47
|
+
hasGradleInvocationFailure,
|
|
42
48
|
parseGradleDep,
|
|
43
49
|
parseGradleInfoLogsForUrls,
|
|
44
50
|
parseGradleProperties,
|
|
45
51
|
parseGradleResolvedDistributions,
|
|
52
|
+
parseGradleVersionCatalog,
|
|
53
|
+
recordGradleInvocationFailure,
|
|
46
54
|
splitOutputByGradleProjects,
|
|
47
55
|
} from "../ecosystems/gradleutils.js";
|
|
48
56
|
import {
|
|
@@ -77,6 +85,7 @@ import {
|
|
|
77
85
|
attachIdentityTools,
|
|
78
86
|
extractToolRefs,
|
|
79
87
|
} from "../inventory/evidenceUtils.js";
|
|
88
|
+
import { readGradleWrapperVersion } from "../inventory/jvmToolEnv.js";
|
|
80
89
|
import { applyPurl, mavenPurl } from "../inventory/purl.js";
|
|
81
90
|
import {
|
|
82
91
|
buildBomNSData,
|
|
@@ -263,7 +272,25 @@ export function discoverSbtProjectsFromCmd(basePath, sbtCmd, env) {
|
|
|
263
272
|
"sbt projects command did not succeed. Falling back to heuristic project discovery.",
|
|
264
273
|
);
|
|
265
274
|
}
|
|
275
|
+
// Reached only when the sbt projects command did not yield usable
|
|
276
|
+
// subprojects; both success paths above returned early.
|
|
277
|
+
recordDegradation("jvm.sbt.project-discovery", {
|
|
278
|
+
ecosystem: "java",
|
|
279
|
+
tool: "sbt",
|
|
280
|
+
impact: "components",
|
|
281
|
+
command: `${basename(sbtCmd)} -batch -no-colors "projects"`,
|
|
282
|
+
detail:
|
|
283
|
+
"The sbt projects command did not yield usable project ids, so project discovery falls back to the build-file heuristic.",
|
|
284
|
+
});
|
|
266
285
|
} catch (err) {
|
|
286
|
+
recordDegradation("jvm.sbt.project-discovery", {
|
|
287
|
+
ecosystem: "java",
|
|
288
|
+
tool: "sbt",
|
|
289
|
+
kind: "command.failed",
|
|
290
|
+
impact: "components",
|
|
291
|
+
command: `${basename(sbtCmd)} -batch -no-colors "projects"`,
|
|
292
|
+
detail: "The sbt projects command could not be executed.",
|
|
293
|
+
});
|
|
267
294
|
if (DEBUG_MODE) {
|
|
268
295
|
console.log("Unable to run sbt projects command.", err);
|
|
269
296
|
}
|
|
@@ -291,269 +318,245 @@ export async function createJavaBom(path, options) {
|
|
|
291
318
|
let tools;
|
|
292
319
|
let possible_misses = false;
|
|
293
320
|
let parallelPropTaskOut = "";
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
if (
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
// Clean up
|
|
308
|
-
if (tempDir?.startsWith(getTmpDir())) {
|
|
309
|
-
console.log(`Cleaning up ${tempDir}`);
|
|
310
|
-
safeRmSync(tempDir, { recursive: true, force: true });
|
|
311
|
-
}
|
|
312
|
-
} else {
|
|
313
|
-
console.log(`${path} doesn't exist`);
|
|
314
|
-
}
|
|
315
|
-
return buildBomNSData(options, pkgList, "maven", {
|
|
316
|
-
src: dirname(path),
|
|
317
|
-
filename: path,
|
|
318
|
-
nsMapping: jarNSMapping,
|
|
319
|
-
dependencies,
|
|
320
|
-
parentComponent,
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
// -t quarkus is supported
|
|
324
|
-
let isQuarkus = options?.projectType?.includes("quarkus");
|
|
325
|
-
let useMavenDepsTree = isQuarkus ? false : PREFER_MAVEN_DEPS_TREE;
|
|
326
|
-
// Is this a multi-module project
|
|
327
|
-
let rootModules;
|
|
328
|
-
// maven - pom.xml
|
|
329
|
-
const pomFiles = getAllFiles(
|
|
330
|
-
path,
|
|
331
|
-
`${options.multiProject ? "**/" : ""}pom.xml`,
|
|
332
|
-
options,
|
|
333
|
-
);
|
|
334
|
-
// gradle
|
|
335
|
-
const gradleFiles = getAllFiles(
|
|
336
|
-
path,
|
|
337
|
-
`${options.multiProject ? "**/" : ""}build.gradle*`,
|
|
338
|
-
options,
|
|
339
|
-
);
|
|
340
|
-
// mill
|
|
341
|
-
const millFiles = getAllFiles(
|
|
342
|
-
path,
|
|
343
|
-
`${options.multiProject ? "**/" : ""}build.mill`,
|
|
344
|
-
options,
|
|
345
|
-
);
|
|
346
|
-
let bomJsonFiles = [];
|
|
347
|
-
if (
|
|
348
|
-
pomFiles?.length &&
|
|
349
|
-
isPackageManagerAllowed(
|
|
350
|
-
"maven",
|
|
351
|
-
["bazel", "sbt", "gradle", "mill"],
|
|
352
|
-
options,
|
|
353
|
-
)
|
|
354
|
-
) {
|
|
355
|
-
if (gradleFiles.length) {
|
|
356
|
-
thoughtLog(
|
|
357
|
-
`Is this a Gradle project? I recommend invoking cdxgen with the "-t gradle" option if you're encountering build errors.`,
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
if (!isQuarkus) {
|
|
361
|
-
// Quarkus projects require special treatment. To detect quarkus, we parse the first 3 maven file to look for a hit
|
|
362
|
-
for (const pf of pomFiles.slice(0, 3)) {
|
|
363
|
-
const pomMap = parsePom(pf);
|
|
364
|
-
if (!rootModules && pomMap?.modules?.length) {
|
|
365
|
-
rootModules = pomMap.modules;
|
|
321
|
+
try {
|
|
322
|
+
// war/ear mode
|
|
323
|
+
if (path.endsWith(".war") || path.endsWith(".jar")) {
|
|
324
|
+
// Check if the file exists
|
|
325
|
+
if (safeExistsSync(path)) {
|
|
326
|
+
if (DEBUG_MODE) {
|
|
327
|
+
console.log(`Retrieving packages from ${path}`);
|
|
328
|
+
}
|
|
329
|
+
const tempDir = safeMkdtempSync(join(getTmpDir(), "war-deps-"));
|
|
330
|
+
jarNSMapping = await collectJarNS(tempDir);
|
|
331
|
+
pkgList = await extractJarArchive(path, tempDir, jarNSMapping);
|
|
332
|
+
if (pkgList.length) {
|
|
333
|
+
pkgList = await getMvnMetadata(pkgList);
|
|
366
334
|
}
|
|
367
|
-
//
|
|
368
|
-
if (
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
break;
|
|
335
|
+
// Clean up
|
|
336
|
+
if (tempDir?.startsWith(getTmpDir())) {
|
|
337
|
+
console.log(`Cleaning up ${tempDir}`);
|
|
338
|
+
safeRmSync(tempDir, { recursive: true, force: true });
|
|
372
339
|
}
|
|
340
|
+
} else {
|
|
341
|
+
console.log(`${path} doesn't exist`);
|
|
373
342
|
}
|
|
343
|
+
return buildBomNSData(options, pkgList, "maven", {
|
|
344
|
+
src: dirname(path),
|
|
345
|
+
filename: path,
|
|
346
|
+
nsMapping: jarNSMapping,
|
|
347
|
+
dependencies,
|
|
348
|
+
parentComponent,
|
|
349
|
+
});
|
|
374
350
|
}
|
|
375
|
-
|
|
376
|
-
let
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
351
|
+
// -t quarkus is supported
|
|
352
|
+
let isQuarkus = options?.projectType?.includes("quarkus");
|
|
353
|
+
let useMavenDepsTree = isQuarkus ? false : PREFER_MAVEN_DEPS_TREE;
|
|
354
|
+
// Is this a multi-module project
|
|
355
|
+
let rootModules;
|
|
356
|
+
// maven - pom.xml
|
|
357
|
+
const pomFiles = getAllFiles(
|
|
358
|
+
path,
|
|
359
|
+
`${options.multiProject ? "**/" : ""}pom.xml`,
|
|
360
|
+
options,
|
|
361
|
+
);
|
|
362
|
+
// gradle
|
|
363
|
+
const gradleFiles = getAllFiles(
|
|
364
|
+
path,
|
|
365
|
+
`${options.multiProject ? "**/" : ""}build.gradle*`,
|
|
366
|
+
options,
|
|
367
|
+
);
|
|
368
|
+
// mill
|
|
369
|
+
const millFiles = getAllFiles(
|
|
370
|
+
path,
|
|
371
|
+
`${options.multiProject ? "**/" : ""}build.mill`,
|
|
372
|
+
options,
|
|
373
|
+
);
|
|
374
|
+
let bomJsonFiles = [];
|
|
375
|
+
if (
|
|
376
|
+
pomFiles?.length &&
|
|
377
|
+
isPackageManagerAllowed(
|
|
378
|
+
"maven",
|
|
379
|
+
["bazel", "sbt", "gradle", "mill"],
|
|
380
|
+
options,
|
|
381
|
+
)
|
|
382
|
+
) {
|
|
383
|
+
if (gradleFiles.length) {
|
|
384
|
+
thoughtLog(
|
|
385
|
+
`Is this a Gradle project? I recommend invoking cdxgen with the "-t gradle" option if you're encountering build errors.`,
|
|
392
386
|
);
|
|
393
387
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
if (includeMavenTestScope) {
|
|
408
|
-
mvnArgs.push("-DincludeTestScope=true");
|
|
409
|
-
}
|
|
410
|
-
// By using quiet mode we can reduce the maxBuffer used and avoid crashes
|
|
411
|
-
if (!DEBUG_MODE) {
|
|
412
|
-
mvnArgs.push("-q");
|
|
413
|
-
}
|
|
414
|
-
// Support for passing additional settings and profile to maven
|
|
415
|
-
if (readEnvironmentVariable("MVN_ARGS")) {
|
|
416
|
-
const addArgs = parseMavenArgs(readEnvironmentVariable("MVN_ARGS"));
|
|
417
|
-
mvnArgs = mvnArgs.concat(addArgs);
|
|
418
|
-
}
|
|
419
|
-
// specVersion 1.4 doesn't support externalReferences.type=distribution-intake
|
|
420
|
-
// so we need to run the plugin with the correct version
|
|
421
|
-
if (options.specVersion >= 1.6) {
|
|
422
|
-
mvnArgs = mvnArgs.concat(`-DschemaVersion=${toolsSpecVersion}`);
|
|
423
|
-
} else if (options.specVersion > 1.4) {
|
|
424
|
-
mvnArgs = mvnArgs.concat(`-DschemaVersion=${options.specVersion}`);
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
const firstPom = pomFiles.length ? pomFiles[0] : undefined;
|
|
428
|
-
let mavenCmd = getMavenCommand(path, path);
|
|
429
|
-
for (const f of pomFiles) {
|
|
430
|
-
const basePath = dirname(f);
|
|
431
|
-
if (
|
|
432
|
-
isQuarkus &&
|
|
433
|
-
!options.deep &&
|
|
434
|
-
rootModules?.includes(basename(basePath))
|
|
435
|
-
) {
|
|
436
|
-
if (DEBUG_MODE) {
|
|
437
|
-
console.log("Skipped sub-module", basePath);
|
|
388
|
+
if (!isQuarkus) {
|
|
389
|
+
// Quarkus projects require special treatment. To detect quarkus, we parse the first 3 maven file to look for a hit
|
|
390
|
+
for (const pf of pomFiles.slice(0, 3)) {
|
|
391
|
+
const pomMap = parsePom(pf);
|
|
392
|
+
if (!rootModules && pomMap?.modules?.length) {
|
|
393
|
+
rootModules = pomMap.modules;
|
|
394
|
+
}
|
|
395
|
+
// In quarkus mode, we cannot use the maven deps tree
|
|
396
|
+
if (pomMap.isQuarkus) {
|
|
397
|
+
isQuarkus = true;
|
|
398
|
+
useMavenDepsTree = false;
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
438
401
|
}
|
|
439
|
-
continue;
|
|
440
402
|
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
mavenCmd = getMavenCommand(basePath, path);
|
|
449
|
-
}
|
|
450
|
-
// Should we attempt to resolve class names
|
|
451
|
-
if (options.resolveClass || options.deep) {
|
|
452
|
-
const tmpjarNSMapping = await collectMvnDependencies(
|
|
453
|
-
mavenCmd,
|
|
454
|
-
basePath,
|
|
455
|
-
true,
|
|
456
|
-
false,
|
|
403
|
+
let result;
|
|
404
|
+
let mvnArgs;
|
|
405
|
+
// FIXME: How do we motivate everyone to upgrade to 1.7?
|
|
406
|
+
const toolsSpecVersion = 1.6;
|
|
407
|
+
if (isQuarkus) {
|
|
408
|
+
thoughtLog(
|
|
409
|
+
"This appears to be a Quarkus project. Let's use the right Maven plugin.",
|
|
457
410
|
);
|
|
458
|
-
|
|
459
|
-
|
|
411
|
+
// disable analytics. See: https://quarkus.io/usage/
|
|
412
|
+
mvnArgs = [
|
|
413
|
+
"-fn",
|
|
414
|
+
"quarkus:dependency-sbom",
|
|
415
|
+
"-Dquarkus.analytics.disabled=true",
|
|
416
|
+
];
|
|
417
|
+
if (options.specVersion >= 1.6) {
|
|
418
|
+
mvnArgs = mvnArgs.concat(
|
|
419
|
+
`-Dquarkus.dependency.sbom.schema-version=${toolsSpecVersion}`,
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
} else {
|
|
423
|
+
// FIXME: The last maven plugin release was on November 28th, 2024.
|
|
424
|
+
// Should we fork this repo and maintain it ourselves?
|
|
425
|
+
const cdxMavenPlugin =
|
|
426
|
+
readEnvironmentVariable("CDX_MAVEN_PLUGIN") ||
|
|
427
|
+
"org.cyclonedx:cyclonedx-maven-plugin:2.9.1";
|
|
428
|
+
const cdxMavenGoal =
|
|
429
|
+
readEnvironmentVariable("CDX_MAVEN_GOAL") || "makeAggregateBom";
|
|
430
|
+
mvnArgs = [
|
|
431
|
+
"-fn",
|
|
432
|
+
`${cdxMavenPlugin}:${cdxMavenGoal}`,
|
|
433
|
+
"-DoutputName=bom",
|
|
434
|
+
];
|
|
435
|
+
if (includeMavenTestScope) {
|
|
436
|
+
mvnArgs.push("-DincludeTestScope=true");
|
|
437
|
+
}
|
|
438
|
+
// By using quiet mode we can reduce the maxBuffer used and avoid crashes
|
|
439
|
+
if (!DEBUG_MODE) {
|
|
440
|
+
mvnArgs.push("-q");
|
|
441
|
+
}
|
|
442
|
+
// Support for passing additional settings and profile to maven
|
|
443
|
+
if (readEnvironmentVariable("MVN_ARGS")) {
|
|
444
|
+
const addArgs = parseMavenArgs(readEnvironmentVariable("MVN_ARGS"));
|
|
445
|
+
mvnArgs = mvnArgs.concat(addArgs);
|
|
446
|
+
}
|
|
447
|
+
// specVersion 1.4 doesn't support externalReferences.type=distribution-intake
|
|
448
|
+
// so we need to run the plugin with the correct version
|
|
449
|
+
if (options.specVersion >= 1.6) {
|
|
450
|
+
mvnArgs = mvnArgs.concat(`-DschemaVersion=${toolsSpecVersion}`);
|
|
451
|
+
} else if (options.specVersion > 1.4) {
|
|
452
|
+
mvnArgs = mvnArgs.concat(`-DschemaVersion=${options.specVersion}`);
|
|
460
453
|
}
|
|
461
454
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
);
|
|
477
|
-
|
|
478
|
-
|
|
455
|
+
const firstPom = pomFiles.length ? pomFiles[0] : undefined;
|
|
456
|
+
let mavenCmd = getMavenCommand(path, path);
|
|
457
|
+
for (const f of pomFiles) {
|
|
458
|
+
const basePath = dirname(f);
|
|
459
|
+
if (
|
|
460
|
+
isQuarkus &&
|
|
461
|
+
!options.deep &&
|
|
462
|
+
rootModules?.includes(basename(basePath))
|
|
463
|
+
) {
|
|
464
|
+
if (DEBUG_MODE) {
|
|
465
|
+
console.log("Skipped sub-module", basePath);
|
|
466
|
+
}
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
const settingsXml = join(basePath, "settings.xml");
|
|
470
|
+
if (safeExistsSync(settingsXml)) {
|
|
471
|
+
console.log(
|
|
472
|
+
`maven settings.xml found in ${basePath}. Please set the MVN_ARGS environment variable based on the full mvn build command used for this project.\nExample: MVN_ARGS='--settings ${settingsXml}'`,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
if (mavenCmd?.endsWith("mvn")) {
|
|
476
|
+
mavenCmd = getMavenCommand(basePath, path);
|
|
477
|
+
}
|
|
478
|
+
// Should we attempt to resolve class names
|
|
479
|
+
if (options.resolveClass || options.deep) {
|
|
480
|
+
const tmpjarNSMapping = await collectMvnDependencies(
|
|
481
|
+
mavenCmd,
|
|
482
|
+
basePath,
|
|
483
|
+
true,
|
|
484
|
+
false,
|
|
485
|
+
);
|
|
486
|
+
if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
|
|
487
|
+
jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// Use the cyclonedx maven plugin if there is no preference for maven deps tree
|
|
491
|
+
if (!useMavenDepsTree) {
|
|
492
|
+
thoughtLog("The user wants me to use the cyclonedx-maven plugin.");
|
|
493
|
+
console.log(`Executing '${mavenCmd}' in`, basePath);
|
|
494
|
+
result = safeSpawnSync(mavenCmd, mvnArgs, {
|
|
495
|
+
cwd: basePath,
|
|
496
|
+
shell: isWin,
|
|
497
|
+
});
|
|
498
|
+
// Check if the cyclonedx plugin created the required bom.json file
|
|
499
|
+
// Sometimes the plugin fails silently for complex maven projects
|
|
479
500
|
bomJsonFiles = getAllFiles(
|
|
480
501
|
path,
|
|
481
|
-
"target
|
|
502
|
+
"**/target/*{cdx,bom,cyclonedx}*.json",
|
|
482
503
|
options,
|
|
483
504
|
);
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
result?.status !== 0 ||
|
|
491
|
-
result?.error
|
|
492
|
-
) {
|
|
493
|
-
const tempRoot = getTmpDir();
|
|
494
|
-
const tempDir = safeMkdtempSync(join(tempRoot, "cdxgen-mvn-"));
|
|
495
|
-
const tempMvnTree = join(tempDir, "cdxgen-mvn-tree.json");
|
|
496
|
-
const tempMvnTreeText = join(tempDir, "cdxgen-mvn-tree.txt");
|
|
497
|
-
const tempMvnParentTree = join(tempDir, "cdxgen-mvn-parent-tree.json");
|
|
498
|
-
const tempMvnParentTreeText = join(
|
|
499
|
-
tempDir,
|
|
500
|
-
"cdxgen-mvn-parent-tree.txt",
|
|
501
|
-
);
|
|
502
|
-
try {
|
|
503
|
-
let mvnTreeArgs = [
|
|
504
|
-
"dependency:tree",
|
|
505
|
-
`-DoutputFile=${tempMvnTree}`,
|
|
506
|
-
"-DoutputType=json",
|
|
507
|
-
];
|
|
508
|
-
let addArgs = [];
|
|
509
|
-
if (readEnvironmentVariable("MVN_ARGS")) {
|
|
510
|
-
addArgs = parseMavenArgs(readEnvironmentVariable("MVN_ARGS"));
|
|
511
|
-
mvnTreeArgs = mvnTreeArgs.concat(addArgs);
|
|
512
|
-
}
|
|
513
|
-
// Automatically use settings.xml to improve the success for fallback
|
|
514
|
-
if (safeExistsSync(settingsXml)) {
|
|
515
|
-
mvnTreeArgs.push("-s");
|
|
516
|
-
mvnTreeArgs.push(settingsXml);
|
|
517
|
-
}
|
|
518
|
-
// For the first pom alone, we need to execute first in non-recursive mode to capture
|
|
519
|
-
// the parent component. Then, we execute all of them in recursive mode
|
|
520
|
-
if (f === firstPom) {
|
|
521
|
-
thoughtLog(
|
|
522
|
-
"What is the parent component here? Let's use maven command to find out.",
|
|
505
|
+
// Check if the bom json files got created in a directory other than target
|
|
506
|
+
if (!bomJsonFiles.length) {
|
|
507
|
+
bomJsonFiles = getAllFiles(
|
|
508
|
+
path,
|
|
509
|
+
"target/**/*{cdx,bom,cyclonedx}*.json",
|
|
510
|
+
options,
|
|
523
511
|
);
|
|
524
|
-
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// Also check if the user has a preference for maven deps tree command
|
|
515
|
+
if (
|
|
516
|
+
useMavenDepsTree ||
|
|
517
|
+
!bomJsonFiles.length ||
|
|
518
|
+
result?.status !== 0 ||
|
|
519
|
+
result?.error
|
|
520
|
+
) {
|
|
521
|
+
const tempRoot = getTmpDir();
|
|
522
|
+
const tempDir = safeMkdtempSync(join(tempRoot, "cdxgen-mvn-"));
|
|
523
|
+
const tempMvnTree = join(tempDir, "cdxgen-mvn-tree.json");
|
|
524
|
+
const tempMvnTreeText = join(tempDir, "cdxgen-mvn-tree.txt");
|
|
525
|
+
const tempMvnParentTree = join(
|
|
526
|
+
tempDir,
|
|
527
|
+
"cdxgen-mvn-parent-tree.json",
|
|
528
|
+
);
|
|
529
|
+
const tempMvnParentTreeText = join(
|
|
530
|
+
tempDir,
|
|
531
|
+
"cdxgen-mvn-parent-tree.txt",
|
|
532
|
+
);
|
|
533
|
+
try {
|
|
534
|
+
let mvnTreeArgs = [
|
|
525
535
|
"dependency:tree",
|
|
526
|
-
|
|
527
|
-
`-DoutputFile=${tempMvnParentTree}`,
|
|
536
|
+
`-DoutputFile=${tempMvnTree}`,
|
|
528
537
|
"-DoutputType=json",
|
|
529
538
|
];
|
|
530
|
-
|
|
531
|
-
|
|
539
|
+
let addArgs = [];
|
|
540
|
+
if (readEnvironmentVariable("MVN_ARGS")) {
|
|
541
|
+
addArgs = parseMavenArgs(readEnvironmentVariable("MVN_ARGS"));
|
|
542
|
+
mvnTreeArgs = mvnTreeArgs.concat(addArgs);
|
|
532
543
|
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
// If json is empty or unparseable, fallback to text parsing
|
|
538
|
-
let emptyJson = !safeExistsSync(tempMvnParentTree);
|
|
539
|
-
if (safeExistsSync(tempMvnParentTree)) {
|
|
540
|
-
const mvnTreeString = readFileSync(tempMvnParentTree, {
|
|
541
|
-
encoding: "utf-8",
|
|
542
|
-
});
|
|
543
|
-
const parsedList = parseMavenTreeJson(mvnTreeString, f);
|
|
544
|
-
if (!parsedList?.pkgList?.length) {
|
|
545
|
-
emptyJson = true;
|
|
546
|
-
// Remove the invalid json file so text output is preferred below.
|
|
547
|
-
if (safeExistsSync(tempMvnParentTree)) {
|
|
548
|
-
safeUnlinkSync(tempMvnParentTree);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
544
|
+
// Automatically use settings.xml to improve the success for fallback
|
|
545
|
+
if (safeExistsSync(settingsXml)) {
|
|
546
|
+
mvnTreeArgs.push("-s");
|
|
547
|
+
mvnTreeArgs.push(settingsXml);
|
|
551
548
|
}
|
|
552
|
-
|
|
553
|
-
|
|
549
|
+
// For the first pom alone, we need to execute first in non-recursive mode to capture
|
|
550
|
+
// the parent component. Then, we execute all of them in recursive mode
|
|
551
|
+
if (f === firstPom) {
|
|
552
|
+
thoughtLog(
|
|
553
|
+
"What is the parent component here? Let's use maven command to find out.",
|
|
554
|
+
);
|
|
555
|
+
let findParentComponentArgs = [
|
|
554
556
|
"dependency:tree",
|
|
555
557
|
"-N",
|
|
556
|
-
`-DoutputFile=${
|
|
558
|
+
`-DoutputFile=${tempMvnParentTree}`,
|
|
559
|
+
"-DoutputType=json",
|
|
557
560
|
];
|
|
558
561
|
if (addArgs.length) {
|
|
559
562
|
findParentComponentArgs =
|
|
@@ -563,349 +566,407 @@ export async function createJavaBom(path, options) {
|
|
|
563
566
|
cwd: basePath,
|
|
564
567
|
shell: isWin,
|
|
565
568
|
});
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
: tempMvnParentTree;
|
|
571
|
-
if (safeExistsSync(parentTreeFile)) {
|
|
572
|
-
const mvnTreeString = readFileSync(parentTreeFile, {
|
|
569
|
+
// If json is empty or unparseable, fallback to text parsing
|
|
570
|
+
let emptyJson = !safeExistsSync(tempMvnParentTree);
|
|
571
|
+
if (safeExistsSync(tempMvnParentTree)) {
|
|
572
|
+
const mvnTreeString = readFileSync(tempMvnParentTree, {
|
|
573
573
|
encoding: "utf-8",
|
|
574
574
|
});
|
|
575
|
-
const parsedList =
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
tmpParentComponent.type = "application";
|
|
582
|
-
parentComponent = tmpParentComponent;
|
|
583
|
-
parentComponent.components = [];
|
|
584
|
-
if (parentComponent.name) {
|
|
585
|
-
thoughtLog(
|
|
586
|
-
`Parent component is called ${parentComponent.name}!`,
|
|
587
|
-
);
|
|
575
|
+
const parsedList = parseMavenTreeJson(mvnTreeString, f);
|
|
576
|
+
if (!parsedList?.pkgList?.length) {
|
|
577
|
+
emptyJson = true;
|
|
578
|
+
// Remove the invalid json file so text output is preferred below.
|
|
579
|
+
if (safeExistsSync(tempMvnParentTree)) {
|
|
580
|
+
safeUnlinkSync(tempMvnParentTree);
|
|
588
581
|
}
|
|
589
582
|
}
|
|
590
583
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
584
|
+
if (result.status !== 0 || result.error || emptyJson) {
|
|
585
|
+
findParentComponentArgs = [
|
|
586
|
+
"dependency:tree",
|
|
587
|
+
"-N",
|
|
588
|
+
`-DoutputFile=${tempMvnParentTreeText}`,
|
|
589
|
+
];
|
|
590
|
+
if (addArgs.length) {
|
|
591
|
+
findParentComponentArgs =
|
|
592
|
+
findParentComponentArgs.concat(addArgs);
|
|
593
|
+
}
|
|
594
|
+
result = safeSpawnSync(mavenCmd, findParentComponentArgs, {
|
|
595
|
+
cwd: basePath,
|
|
596
|
+
shell: isWin,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
if (result.status === 0) {
|
|
600
|
+
const parentTreeFile = safeExistsSync(tempMvnParentTreeText)
|
|
601
|
+
? tempMvnParentTreeText
|
|
602
|
+
: tempMvnParentTree;
|
|
603
|
+
if (safeExistsSync(parentTreeFile)) {
|
|
604
|
+
const mvnTreeString = readFileSync(parentTreeFile, {
|
|
605
|
+
encoding: "utf-8",
|
|
606
|
+
});
|
|
607
|
+
const parsedList = parentTreeFile.endsWith(".json")
|
|
608
|
+
? parseMavenTreeJson(mvnTreeString, f)
|
|
609
|
+
: parseMavenTree(mvnTreeString, f);
|
|
610
|
+
const dlist = parsedList.pkgList || [];
|
|
611
|
+
if (dlist.length) {
|
|
612
|
+
const tmpParentComponent = dlist.splice(0, 1)[0];
|
|
613
|
+
tmpParentComponent.type = "application";
|
|
614
|
+
parentComponent = tmpParentComponent;
|
|
615
|
+
parentComponent.components = [];
|
|
616
|
+
if (parentComponent.name) {
|
|
617
|
+
thoughtLog(
|
|
618
|
+
`Parent component is called ${parentComponent.name}!`,
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
616
623
|
}
|
|
617
624
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
mvnTreeArgs = mvnTreeArgs.concat(addArgs);
|
|
626
|
-
}
|
|
627
|
-
if (safeExistsSync(settingsXml)) {
|
|
628
|
-
mvnTreeArgs.push("-s");
|
|
629
|
-
mvnTreeArgs.push(settingsXml);
|
|
625
|
+
thoughtLog(
|
|
626
|
+
`**MAVEN**: Let's use Maven to collect packages from ${basePath}.`,
|
|
627
|
+
);
|
|
628
|
+
if (DEBUG_MODE) {
|
|
629
|
+
console.log(
|
|
630
|
+
`Executing '${basename(mavenCmd)} dependency:tree ...' in ${basePath}`,
|
|
631
|
+
);
|
|
630
632
|
}
|
|
631
633
|
result = safeSpawnSync(mavenCmd, mvnTreeArgs, {
|
|
632
634
|
cwd: basePath,
|
|
633
635
|
shell: isWin,
|
|
634
636
|
});
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (
|
|
642
|
-
|
|
637
|
+
let emptyJson = !safeExistsSync(tempMvnTree);
|
|
638
|
+
if (safeExistsSync(tempMvnTree)) {
|
|
639
|
+
const mvnTreeString = readFileSync(tempMvnTree, {
|
|
640
|
+
encoding: "utf-8",
|
|
641
|
+
});
|
|
642
|
+
const parsedList = parseMavenTreeJson(mvnTreeString, f);
|
|
643
|
+
if (!parsedList?.pkgList?.length) {
|
|
644
|
+
emptyJson = true;
|
|
645
|
+
// Remove the invalid json file so text output is preferred below.
|
|
646
|
+
if (safeExistsSync(tempMvnTree)) {
|
|
647
|
+
safeUnlinkSync(tempMvnTree);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (result.status !== 0 || result.error || emptyJson) {
|
|
652
|
+
mvnTreeArgs = [
|
|
653
|
+
"dependency:tree",
|
|
654
|
+
`-DoutputFile=${tempMvnTreeText}`,
|
|
655
|
+
];
|
|
656
|
+
if (addArgs.length) {
|
|
657
|
+
mvnTreeArgs = mvnTreeArgs.concat(addArgs);
|
|
643
658
|
}
|
|
644
|
-
if (
|
|
645
|
-
|
|
646
|
-
|
|
659
|
+
if (safeExistsSync(settingsXml)) {
|
|
660
|
+
mvnTreeArgs.push("-s");
|
|
661
|
+
mvnTreeArgs.push(settingsXml);
|
|
647
662
|
}
|
|
663
|
+
result = safeSpawnSync(mavenCmd, mvnTreeArgs, {
|
|
664
|
+
cwd: basePath,
|
|
665
|
+
shell: isWin,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
if (result.status !== 0 || result.error) {
|
|
669
|
+
possible_misses = true;
|
|
670
|
+
// Our approach to recursively invoking the maven plugin for each sub-module is bound to result in failures
|
|
671
|
+
// These could be due to a range of reasons that are covered below.
|
|
648
672
|
if (
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
) {
|
|
653
|
-
console.log(
|
|
654
|
-
"1. Check if the pom.xml contains valid settings for parent and modules. Some projects can be built only from a specific directory.",
|
|
655
|
-
);
|
|
656
|
-
} else if (
|
|
657
|
-
result.stdout &&
|
|
658
|
-
(result.stdout.includes("Could not resolve dependencies") ||
|
|
659
|
-
result.stdout.includes(
|
|
660
|
-
"no dependency information available",
|
|
661
|
-
) ||
|
|
662
|
-
result.stdout.includes(
|
|
663
|
-
"The following artifacts could not be resolved",
|
|
664
|
-
))
|
|
665
|
-
) {
|
|
666
|
-
console.log(
|
|
667
|
-
"1. Try building the project with 'mvn package -Dmaven.test.skip=true' using the correct version of Java and maven before invoking cdxgen.",
|
|
668
|
-
);
|
|
669
|
-
} else if (
|
|
670
|
-
result.stdout?.includes(
|
|
671
|
-
"Could not resolve target platform specification",
|
|
672
|
-
)
|
|
673
|
+
pomFiles.length === 1 ||
|
|
674
|
+
DEBUG_MODE ||
|
|
675
|
+
PREFER_MAVEN_DEPS_TREE
|
|
673
676
|
) {
|
|
677
|
+
if (result.stdout) {
|
|
678
|
+
console.log(result.stdout);
|
|
679
|
+
}
|
|
680
|
+
if (result.stderr) {
|
|
681
|
+
console.log(result.stderr);
|
|
682
|
+
console.log("The above build errors could be due to:\n");
|
|
683
|
+
}
|
|
684
|
+
if (
|
|
685
|
+
result.stdout &&
|
|
686
|
+
(result.stdout.includes("Non-resolvable parent POM") ||
|
|
687
|
+
result.stdout.includes("points at wrong local POM"))
|
|
688
|
+
) {
|
|
689
|
+
console.log(
|
|
690
|
+
"1. Check if the pom.xml contains valid settings for parent and modules. Some projects can be built only from a specific directory.",
|
|
691
|
+
);
|
|
692
|
+
} else if (
|
|
693
|
+
result.stdout &&
|
|
694
|
+
(result.stdout.includes("Could not resolve dependencies") ||
|
|
695
|
+
result.stdout.includes(
|
|
696
|
+
"no dependency information available",
|
|
697
|
+
) ||
|
|
698
|
+
result.stdout.includes(
|
|
699
|
+
"The following artifacts could not be resolved",
|
|
700
|
+
))
|
|
701
|
+
) {
|
|
702
|
+
recordDegradation("jvm.maven.build-first", {
|
|
703
|
+
ecosystem: "java",
|
|
704
|
+
tool: "maven",
|
|
705
|
+
impact: "transitive-deps",
|
|
706
|
+
command: `${basename(mavenCmd)} ${mvnTreeArgs.join(" ")}`,
|
|
707
|
+
exitCode:
|
|
708
|
+
typeof result.status === "number"
|
|
709
|
+
? result.status
|
|
710
|
+
: undefined,
|
|
711
|
+
detail:
|
|
712
|
+
"Maven could not resolve the project dependencies, so the dependency tree is incomplete.",
|
|
713
|
+
outputExcerpt: commandOutputText(result),
|
|
714
|
+
});
|
|
715
|
+
console.log(
|
|
716
|
+
"1. Try building the project with 'mvn package -Dmaven.test.skip=true' using the correct version of Java and maven before invoking cdxgen.",
|
|
717
|
+
);
|
|
718
|
+
} else if (
|
|
719
|
+
result.stdout?.includes(
|
|
720
|
+
"Could not resolve target platform specification",
|
|
721
|
+
)
|
|
722
|
+
) {
|
|
723
|
+
console.log(
|
|
724
|
+
"1. Some projects can be built only from the root directory. Invoke cdxgen with --no-recurse option",
|
|
725
|
+
);
|
|
726
|
+
} else {
|
|
727
|
+
console.log(
|
|
728
|
+
"1. Java version requirement: cdxgen container image bundles Java 24 with maven 3.9 which might be incompatible. Try running cdxgen with the custom JDK11-based image `ghcr.io/cdxgen/cdxgen-java11:v13`.",
|
|
729
|
+
);
|
|
730
|
+
}
|
|
674
731
|
console.log(
|
|
675
|
-
"
|
|
732
|
+
"2. Private dependencies cannot be downloaded: Check if any additional arguments must be passed to maven and set them via MVN_ARGS environment variable.",
|
|
676
733
|
);
|
|
677
|
-
} else {
|
|
678
734
|
console.log(
|
|
679
|
-
"
|
|
735
|
+
"3. Check if all required environment variables including any maven profile arguments are passed correctly to this tool.",
|
|
680
736
|
);
|
|
681
737
|
}
|
|
738
|
+
// The degradation is recorded before the fail-on-error handling
|
|
739
|
+
// below, so the run that stops here still reports why maven
|
|
740
|
+
// failed and what fixes it.
|
|
741
|
+
recordDegradation("jvm.maven.manifest-fallback", {
|
|
742
|
+
ecosystem: "java",
|
|
743
|
+
tool: "maven",
|
|
744
|
+
impact: "transitive-deps",
|
|
745
|
+
command: `${basename(mavenCmd)} ${mvnTreeArgs.join(" ")}`,
|
|
746
|
+
exitCode:
|
|
747
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
748
|
+
detail:
|
|
749
|
+
"The maven dependency tree did not succeed, so at best the direct dependencies parsed from pom.xml are available.",
|
|
750
|
+
outputExcerpt: commandOutputText(result),
|
|
751
|
+
});
|
|
752
|
+
// Do not fall back to methods that can produce incomplete results when failOnError is set
|
|
753
|
+
deferFailOnError(options, {
|
|
754
|
+
ecosystem: "java",
|
|
755
|
+
tool: "maven",
|
|
756
|
+
exitCode:
|
|
757
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
758
|
+
command: `${mavenCmd} ${mvnTreeArgs.join(" ")}`,
|
|
759
|
+
outputExcerpt: commandOutputText(result),
|
|
760
|
+
detail: "the maven dependency tree command failed",
|
|
761
|
+
});
|
|
682
762
|
console.log(
|
|
683
|
-
"
|
|
763
|
+
"\nFalling back to parsing pom.xml files. Only direct dependencies would get included!",
|
|
684
764
|
);
|
|
685
|
-
|
|
686
|
-
"
|
|
765
|
+
thoughtLog(
|
|
766
|
+
"**MAVEN**: There appear to be build errors, so the SBOM will be incomplete.",
|
|
687
767
|
);
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
options.failOnError && process.exit(1);
|
|
691
|
-
console.log(
|
|
692
|
-
"\nFalling back to parsing pom.xml files. Only direct dependencies would get included!",
|
|
693
|
-
);
|
|
694
|
-
thoughtLog(
|
|
695
|
-
"**MAVEN**: There appear to be build errors, so the SBOM will be incomplete.",
|
|
696
|
-
);
|
|
697
|
-
const pomMap = parsePom(f);
|
|
698
|
-
const dlist = pomMap?.dependencies || [];
|
|
699
|
-
if (dlist.length) {
|
|
700
|
-
pkgList = pkgList.concat(dlist);
|
|
701
|
-
}
|
|
702
|
-
} else {
|
|
703
|
-
const treeFile = safeExistsSync(tempMvnTreeText)
|
|
704
|
-
? tempMvnTreeText
|
|
705
|
-
: tempMvnTree;
|
|
706
|
-
if (safeExistsSync(treeFile)) {
|
|
707
|
-
const mvnTreeString = readFileSync(treeFile, {
|
|
708
|
-
encoding: "utf-8",
|
|
709
|
-
});
|
|
710
|
-
const parsedList = treeFile.endsWith(".json")
|
|
711
|
-
? parseMavenTreeJson(mvnTreeString, f)
|
|
712
|
-
: parseMavenTree(mvnTreeString, f);
|
|
713
|
-
const dlist = parsedList.pkgList || [];
|
|
714
|
-
const tmpParentComponent = dlist.splice(0, 1)[0];
|
|
715
|
-
if (tmpParentComponent) {
|
|
716
|
-
tmpParentComponent.type = "application";
|
|
717
|
-
}
|
|
768
|
+
const pomMap = parsePom(f);
|
|
769
|
+
const dlist = pomMap?.dependencies || [];
|
|
718
770
|
if (dlist.length) {
|
|
719
771
|
pkgList = pkgList.concat(dlist);
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
772
|
+
}
|
|
773
|
+
} else {
|
|
774
|
+
const treeFile = safeExistsSync(tempMvnTreeText)
|
|
775
|
+
? tempMvnTreeText
|
|
776
|
+
: tempMvnTree;
|
|
777
|
+
if (safeExistsSync(treeFile)) {
|
|
778
|
+
const mvnTreeString = readFileSync(treeFile, {
|
|
779
|
+
encoding: "utf-8",
|
|
780
|
+
});
|
|
781
|
+
const parsedList = treeFile.endsWith(".json")
|
|
782
|
+
? parseMavenTreeJson(mvnTreeString, f)
|
|
783
|
+
: parseMavenTree(mvnTreeString, f);
|
|
784
|
+
const dlist = parsedList.pkgList || [];
|
|
785
|
+
const tmpParentComponent = dlist.splice(0, 1)[0];
|
|
786
|
+
if (tmpParentComponent) {
|
|
787
|
+
tmpParentComponent.type = "application";
|
|
788
|
+
}
|
|
789
|
+
if (dlist.length) {
|
|
790
|
+
pkgList = pkgList.concat(dlist);
|
|
791
|
+
if (dlist.length > 1) {
|
|
792
|
+
thoughtLog(
|
|
793
|
+
`Obtained ${dlist.length} components from maven.`,
|
|
794
|
+
);
|
|
795
|
+
} else {
|
|
796
|
+
thoughtLog(
|
|
797
|
+
`"Received very few components from the maven dependency tree command for ${basePath}."`,
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
// Retain the parent hierarchy
|
|
802
|
+
if (!tmpParentComponent) {
|
|
723
803
|
thoughtLog(
|
|
724
|
-
`
|
|
804
|
+
`No parseable components were found after executing '${basename(mavenCmd)}'.`,
|
|
725
805
|
);
|
|
806
|
+
} else if (!Object.keys(parentComponent).length) {
|
|
807
|
+
parentComponent = tmpParentComponent;
|
|
808
|
+
parentComponent.components = [];
|
|
809
|
+
} else {
|
|
810
|
+
parentComponent.components.push(tmpParentComponent);
|
|
726
811
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
);
|
|
733
|
-
} else if (!Object.keys(parentComponent).length) {
|
|
734
|
-
parentComponent = tmpParentComponent;
|
|
735
|
-
parentComponent.components = [];
|
|
736
|
-
} else {
|
|
737
|
-
parentComponent.components.push(tmpParentComponent);
|
|
738
|
-
}
|
|
739
|
-
if (parsedList?.dependenciesList?.length) {
|
|
740
|
-
dependencies = mergeDependencies(
|
|
741
|
-
dependencies,
|
|
742
|
-
parsedList.dependenciesList,
|
|
743
|
-
tmpParentComponent,
|
|
744
|
-
);
|
|
745
|
-
} else {
|
|
746
|
-
if (dlist?.length) {
|
|
747
|
-
thoughtLog(
|
|
748
|
-
`Hmm, I didn't find any dependencies after executing '${basename(mavenCmd)}'. However, I did get ${dlist.length} components, which is confusing.`,
|
|
812
|
+
if (parsedList?.dependenciesList?.length) {
|
|
813
|
+
dependencies = mergeDependencies(
|
|
814
|
+
dependencies,
|
|
815
|
+
parsedList.dependenciesList,
|
|
816
|
+
tmpParentComponent,
|
|
749
817
|
);
|
|
818
|
+
} else {
|
|
819
|
+
if (dlist?.length) {
|
|
820
|
+
thoughtLog(
|
|
821
|
+
`Hmm, I didn't find any dependencies after executing '${basename(mavenCmd)}'. However, I did get ${dlist.length} components, which is confusing.`,
|
|
822
|
+
);
|
|
823
|
+
}
|
|
750
824
|
}
|
|
751
825
|
}
|
|
752
826
|
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
827
|
+
} finally {
|
|
828
|
+
if (!DEBUG_MODE && tempDir?.startsWith(tempRoot)) {
|
|
829
|
+
safeRmSync(tempDir, { recursive: true, force: true });
|
|
830
|
+
}
|
|
757
831
|
}
|
|
758
832
|
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
console.log(`Extracting data from generated bom file ${abjson}`);
|
|
768
|
-
}
|
|
769
|
-
bomJsonObj = JSON.parse(
|
|
770
|
-
readFileSync(abjson, {
|
|
771
|
-
encoding: "utf-8",
|
|
772
|
-
}),
|
|
773
|
-
);
|
|
774
|
-
if (bomJsonObj) {
|
|
775
|
-
if (
|
|
776
|
-
!tools &&
|
|
777
|
-
bomJsonObj.metadata &&
|
|
778
|
-
bomJsonObj.metadata.tools &&
|
|
779
|
-
(Array.isArray(bomJsonObj.metadata.tools) ||
|
|
780
|
-
bomJsonObj.metadata.tools.components ||
|
|
781
|
-
bomJsonObj.metadata.tools.services)
|
|
782
|
-
) {
|
|
783
|
-
tools = bomJsonObj.metadata.tools;
|
|
833
|
+
} // for
|
|
834
|
+
// Locate and parse all bom.json files from the maven plugin
|
|
835
|
+
if (!useMavenDepsTree) {
|
|
836
|
+
for (const abjson of bomJsonFiles) {
|
|
837
|
+
let bomJsonObj;
|
|
838
|
+
try {
|
|
839
|
+
if (DEBUG_MODE) {
|
|
840
|
+
console.log(`Extracting data from generated bom file ${abjson}`);
|
|
784
841
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
842
|
+
bomJsonObj = JSON.parse(
|
|
843
|
+
readFileSync(abjson, {
|
|
844
|
+
encoding: "utf-8",
|
|
845
|
+
}),
|
|
788
846
|
);
|
|
789
|
-
if (
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
847
|
+
if (bomJsonObj) {
|
|
848
|
+
if (
|
|
849
|
+
!tools &&
|
|
850
|
+
bomJsonObj.metadata &&
|
|
851
|
+
bomJsonObj.metadata.tools &&
|
|
852
|
+
(Array.isArray(bomJsonObj.metadata.tools) ||
|
|
853
|
+
bomJsonObj.metadata.tools.components ||
|
|
854
|
+
bomJsonObj.metadata.tools.services)
|
|
855
|
+
) {
|
|
856
|
+
tools = bomJsonObj.metadata.tools;
|
|
857
|
+
}
|
|
858
|
+
const toolRefs = extractToolRefs(
|
|
859
|
+
bomJsonObj?.metadata?.tools,
|
|
860
|
+
(tool) => tool?.name !== "cdxgen",
|
|
861
|
+
);
|
|
862
|
+
if (
|
|
863
|
+
bomJsonObj.metadata?.component &&
|
|
864
|
+
!Object.keys(parentComponent).length
|
|
865
|
+
) {
|
|
866
|
+
parentComponent = bomJsonObj.metadata.component;
|
|
867
|
+
options.parentComponent = parentComponent;
|
|
868
|
+
}
|
|
869
|
+
if (bomJsonObj.components) {
|
|
870
|
+
// Inject evidence into the components. #994
|
|
871
|
+
if (options.specVersion >= 1.5) {
|
|
872
|
+
// maven would usually generate a target directory closest to the pom.xml
|
|
873
|
+
// I am sure there would be cases where this assumption is not true :)
|
|
874
|
+
const srcPomFile = join(dirname(abjson), "..", "pom.xml");
|
|
875
|
+
for (const acomp of bomJsonObj.components) {
|
|
876
|
+
if (!acomp.evidence) {
|
|
877
|
+
acomp.evidence = {
|
|
878
|
+
identity: {
|
|
879
|
+
field: "purl",
|
|
880
|
+
confidence: 0.8,
|
|
881
|
+
methods: [
|
|
882
|
+
{
|
|
883
|
+
technique: "manifest-analysis",
|
|
884
|
+
confidence: 0.8,
|
|
885
|
+
value: srcPomFile,
|
|
886
|
+
},
|
|
887
|
+
],
|
|
888
|
+
},
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
if (!acomp.properties) {
|
|
892
|
+
acomp.properties = [];
|
|
893
|
+
}
|
|
894
|
+
acomp.properties.push({
|
|
895
|
+
name: "internal:SrcFile",
|
|
896
|
+
value: srcPomFile,
|
|
897
|
+
});
|
|
898
|
+
attachIdentityTools(acomp, toolRefs);
|
|
820
899
|
}
|
|
821
|
-
acomp.properties.push({
|
|
822
|
-
name: "internal:SrcFile",
|
|
823
|
-
value: srcPomFile,
|
|
824
|
-
});
|
|
825
|
-
attachIdentityTools(acomp, toolRefs);
|
|
826
900
|
}
|
|
901
|
+
pkgList = pkgList.concat(bomJsonObj.components);
|
|
902
|
+
}
|
|
903
|
+
if (bomJsonObj.dependencies) {
|
|
904
|
+
dependencies = mergeDependencies(
|
|
905
|
+
dependencies,
|
|
906
|
+
bomJsonObj.dependencies,
|
|
907
|
+
parentComponent,
|
|
908
|
+
);
|
|
827
909
|
}
|
|
828
|
-
pkgList = pkgList.concat(bomJsonObj.components);
|
|
829
910
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
bomJsonObj.dependencies,
|
|
834
|
-
parentComponent,
|
|
835
|
-
);
|
|
911
|
+
} catch (err) {
|
|
912
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
913
|
+
console.log(err);
|
|
836
914
|
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
915
|
+
deferFailOnError(options, {
|
|
916
|
+
ecosystem: "java",
|
|
917
|
+
tool: "maven",
|
|
918
|
+
detail:
|
|
919
|
+
"reading the BOM generated by the cyclonedx maven plugin failed",
|
|
920
|
+
});
|
|
842
921
|
}
|
|
843
922
|
}
|
|
844
923
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
)
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
924
|
+
if (possible_misses) {
|
|
925
|
+
if (gradleFiles.length) {
|
|
926
|
+
console.log(
|
|
927
|
+
"Is this a gradle project? Try running cdxgen with `-t gradle`.",
|
|
928
|
+
);
|
|
929
|
+
} else if (!DEBUG_MODE) {
|
|
930
|
+
console.warn(
|
|
931
|
+
"Multiple errors occurred while building this project with maven. The SBOM is therefore incomplete!",
|
|
932
|
+
);
|
|
933
|
+
}
|
|
855
934
|
}
|
|
856
935
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
)
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
"wrapper",
|
|
892
|
-
"gradle-wrapper.properties",
|
|
893
|
-
);
|
|
894
|
-
if (safeExistsSync(wrapperPropPath)) {
|
|
895
|
-
try {
|
|
896
|
-
const propContent = readFileSync(wrapperPropPath, "utf-8");
|
|
897
|
-
const distUrlLine = propContent
|
|
898
|
-
.split("\n")
|
|
899
|
-
.find((l) => l.startsWith("distributionUrl="));
|
|
900
|
-
if (distUrlLine) {
|
|
901
|
-
const distributionUrl = distUrlLine
|
|
902
|
-
.split("=")[1]
|
|
903
|
-
.trim()
|
|
904
|
-
.replace(/\\:/g, ":");
|
|
905
|
-
const versionMatch = distributionUrl.match(
|
|
906
|
-
/gradle-(.*?)-(bin|all)\.zip/,
|
|
907
|
-
);
|
|
908
|
-
const gradleVersion = versionMatch ? versionMatch[1] : "unknown";
|
|
936
|
+
const allProjects = [];
|
|
937
|
+
const allProjectsAddedPurls = [];
|
|
938
|
+
const rootDependsOn = new Set();
|
|
939
|
+
const gradleModules = new Map();
|
|
940
|
+
// Determine the root path for gradle
|
|
941
|
+
// Fixes gradle invocation for microservices-demo
|
|
942
|
+
let gradleRootPath = path;
|
|
943
|
+
if (
|
|
944
|
+
gradleFiles?.length &&
|
|
945
|
+
!safeExistsSync(join(path, "settings.gradle")) &&
|
|
946
|
+
!safeExistsSync(join(path, "settings.gradle.kts")) &&
|
|
947
|
+
!safeExistsSync(join(path, "build.gradle")) &&
|
|
948
|
+
!safeExistsSync(join(path, "build.gradle.kts"))
|
|
949
|
+
) {
|
|
950
|
+
gradleRootPath = dirname(gradleFiles[0]);
|
|
951
|
+
}
|
|
952
|
+
if (safeExistsSync(join(gradleRootPath, "gradle.properties"))) {
|
|
953
|
+
thoughtLog(
|
|
954
|
+
"Hmm, there is a gradle.properties file. Do we need any private modules or custom JVM arguments for this project 🤔?",
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
// Execute gradle properties
|
|
958
|
+
if (
|
|
959
|
+
gradleFiles?.length &&
|
|
960
|
+
isPackageManagerAllowed(
|
|
961
|
+
"gradle",
|
|
962
|
+
["maven", "bazel", "sbt", "mill"],
|
|
963
|
+
options,
|
|
964
|
+
)
|
|
965
|
+
) {
|
|
966
|
+
const wrapperInfo = readGradleWrapperVersion(gradleRootPath);
|
|
967
|
+
if (wrapperInfo) {
|
|
968
|
+
try {
|
|
969
|
+
const { version: gradleVersion, distributionUrl } = wrapperInfo;
|
|
909
970
|
thoughtLog(
|
|
910
971
|
`Found Gradle wrapper with distributionUrl: ${distributionUrl}, version: ${gradleVersion}`,
|
|
911
972
|
);
|
|
@@ -921,20 +982,14 @@ export async function createJavaBom(path, options) {
|
|
|
921
982
|
}
|
|
922
983
|
}
|
|
923
984
|
if (!toolsArr.find((c) => c.name === "gradle")) {
|
|
924
|
-
const distShaLine = propContent
|
|
925
|
-
.split("\n")
|
|
926
|
-
.find((l) => l.startsWith("distributionSha256Sum="));
|
|
927
985
|
let hashes;
|
|
928
|
-
if (
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
},
|
|
936
|
-
];
|
|
937
|
-
}
|
|
986
|
+
if (wrapperInfo.distributionSha256Sum) {
|
|
987
|
+
hashes = [
|
|
988
|
+
{
|
|
989
|
+
alg: "SHA-256",
|
|
990
|
+
content: wrapperInfo.distributionSha256Sum,
|
|
991
|
+
},
|
|
992
|
+
];
|
|
938
993
|
}
|
|
939
994
|
const gradleComponent = {
|
|
940
995
|
type: "application",
|
|
@@ -965,875 +1020,935 @@ export async function createJavaBom(path, options) {
|
|
|
965
1020
|
} else {
|
|
966
1021
|
tools.components = toolsArr;
|
|
967
1022
|
}
|
|
1023
|
+
} catch (_err) {
|
|
1024
|
+
// ignore
|
|
968
1025
|
}
|
|
969
|
-
} catch (_err) {
|
|
970
|
-
// ignore
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
let includedBuilds = [];
|
|
974
|
-
let allProjectsStr = [];
|
|
975
|
-
if (readEnvironmentVariable("GRADLE_INCLUDED_BUILDS")) {
|
|
976
|
-
includedBuilds = readEnvironmentVariable("GRADLE_INCLUDED_BUILDS")
|
|
977
|
-
.split(",")
|
|
978
|
-
.map((b) => (!b.startsWith(":") ? `:${b}` : b));
|
|
979
|
-
}
|
|
980
|
-
const resolveGradleDistribution = isFeatureEnabled(
|
|
981
|
-
options,
|
|
982
|
-
"resolve-gradle-distribution",
|
|
983
|
-
);
|
|
984
|
-
const gradleInitArgs = readEnvironmentVariable("GRADLE_INCLUDED_BUILDS")
|
|
985
|
-
? []
|
|
986
|
-
: ["--init-script", GRADLE_INIT_SCRIPT];
|
|
987
|
-
if (resolveGradleDistribution && gradleInitArgs.length) {
|
|
988
|
-
gradleInitArgs.push("-PcdxgenResolveDistribution=true");
|
|
989
|
-
}
|
|
990
|
-
parallelPropTaskOut = executeParallelGradleProperties(
|
|
991
|
-
gradleRootPath,
|
|
992
|
-
[null].concat(includedBuilds),
|
|
993
|
-
gradleInitArgs,
|
|
994
|
-
);
|
|
995
|
-
if (readEnvironmentVariable("GRADLE_INCLUDED_BUILDS") === undefined) {
|
|
996
|
-
const outputLines = parallelPropTaskOut.split("\n");
|
|
997
|
-
for (const [_i, line] of outputLines.entries()) {
|
|
998
|
-
if (line.startsWith("Root project '") || line.startsWith("Project '")) {
|
|
999
|
-
break;
|
|
1000
|
-
}
|
|
1001
|
-
if (line.startsWith("<CDXGEN:includedBuild>")) {
|
|
1002
|
-
const includedBuild = line.split(">");
|
|
1003
|
-
if (!includedBuilds.includes(includedBuild[1].trim())) {
|
|
1004
|
-
includedBuilds.push(includedBuild[1].trim());
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
if (includedBuilds.length > 0) {
|
|
1009
|
-
thoughtLog(
|
|
1010
|
-
`Wait, this gradle project uses composite builds. I must carefully process these ${includedBuilds.length} projects, in addition to the root.`,
|
|
1011
|
-
);
|
|
1012
|
-
if (DEBUG_MODE) {
|
|
1013
|
-
console.log(`Composite builds: ${includedBuilds.join(" ").trim()}.`);
|
|
1014
|
-
}
|
|
1015
|
-
parallelPropTaskOut = parallelPropTaskOut.concat(
|
|
1016
|
-
"\n",
|
|
1017
|
-
executeParallelGradleProperties(gradleRootPath, includedBuilds),
|
|
1018
|
-
);
|
|
1019
1026
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
"
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
const rootProject = retMap.rootProject;
|
|
1027
|
-
if (rootProject) {
|
|
1028
|
-
const rootComponent = await buildObjectForGradleModule(
|
|
1029
|
-
rootProject,
|
|
1030
|
-
retMap.metadata,
|
|
1031
|
-
);
|
|
1032
|
-
if (!includedBuilds.includes(key)) {
|
|
1033
|
-
parentComponent = rootComponent;
|
|
1034
|
-
}
|
|
1035
|
-
gradleModules.set(key, rootComponent);
|
|
1036
|
-
if (!allProjectsAddedPurls.includes(rootComponent["purl"])) {
|
|
1037
|
-
allProjects.push(rootComponent);
|
|
1038
|
-
rootDependsOn.add(rootComponent["bom-ref"]);
|
|
1039
|
-
allProjectsAddedPurls.push(rootComponent["purl"]);
|
|
1040
|
-
}
|
|
1041
|
-
allProjectsStr = allProjectsStr.concat(retMap.projects);
|
|
1027
|
+
let includedBuilds = [];
|
|
1028
|
+
let allProjectsStr = [];
|
|
1029
|
+
if (readEnvironmentVariable("GRADLE_INCLUDED_BUILDS")) {
|
|
1030
|
+
includedBuilds = readEnvironmentVariable("GRADLE_INCLUDED_BUILDS")
|
|
1031
|
+
.split(",")
|
|
1032
|
+
.map((b) => (!b.startsWith(":") ? `:${b}` : b));
|
|
1042
1033
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
);
|
|
1034
|
+
const resolveGradleDistribution = isFeatureEnabled(
|
|
1035
|
+
options,
|
|
1036
|
+
"resolve-gradle-distribution",
|
|
1037
|
+
);
|
|
1038
|
+
const gradleInitArgs = readEnvironmentVariable("GRADLE_INCLUDED_BUILDS")
|
|
1039
|
+
? []
|
|
1040
|
+
: ["--init-script", GRADLE_INIT_SCRIPT];
|
|
1041
|
+
if (resolveGradleDistribution && gradleInitArgs.length) {
|
|
1042
|
+
gradleInitArgs.push("-PcdxgenResolveDistribution=true");
|
|
1053
1043
|
}
|
|
1054
1044
|
parallelPropTaskOut = executeParallelGradleProperties(
|
|
1055
1045
|
gradleRootPath,
|
|
1056
|
-
|
|
1046
|
+
[null].concat(includedBuilds),
|
|
1047
|
+
gradleInitArgs,
|
|
1057
1048
|
);
|
|
1049
|
+
if (readEnvironmentVariable("GRADLE_INCLUDED_BUILDS") === undefined) {
|
|
1050
|
+
const outputLines = parallelPropTaskOut.split("\n");
|
|
1051
|
+
for (const [_i, line] of outputLines.entries()) {
|
|
1052
|
+
if (
|
|
1053
|
+
line.startsWith("Root project '") ||
|
|
1054
|
+
line.startsWith("Project '")
|
|
1055
|
+
) {
|
|
1056
|
+
break;
|
|
1057
|
+
}
|
|
1058
|
+
if (line.startsWith("<CDXGEN:includedBuild>")) {
|
|
1059
|
+
const includedBuild = line.split(">");
|
|
1060
|
+
if (!includedBuilds.includes(includedBuild[1].trim())) {
|
|
1061
|
+
includedBuilds.push(includedBuild[1].trim());
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (includedBuilds.length > 0) {
|
|
1066
|
+
thoughtLog(
|
|
1067
|
+
`Wait, this gradle project uses composite builds. I must carefully process these ${includedBuilds.length} projects, in addition to the root.`,
|
|
1068
|
+
);
|
|
1069
|
+
if (DEBUG_MODE) {
|
|
1070
|
+
console.log(
|
|
1071
|
+
`Composite builds: ${includedBuilds.join(" ").trim()}.`,
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
parallelPropTaskOut = parallelPropTaskOut.concat(
|
|
1075
|
+
"\n",
|
|
1076
|
+
executeParallelGradleProperties(gradleRootPath, includedBuilds),
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1058
1080
|
const splitPropTaskOut = splitOutputByGradleProjects(
|
|
1059
1081
|
parallelPropTaskOut,
|
|
1060
1082
|
["properties"],
|
|
1061
1083
|
);
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
const rootSubProject = retMap.rootProject;
|
|
1069
|
-
if (rootSubProject) {
|
|
1070
|
-
const rootSubProjectObj = await buildObjectForGradleModule(
|
|
1071
|
-
rootSubProject === "root" ? subProject : rootSubProject,
|
|
1084
|
+
for (const [key, propTaskOut] of splitPropTaskOut.entries()) {
|
|
1085
|
+
const retMap = parseGradleProperties(propTaskOut);
|
|
1086
|
+
const rootProject = retMap.rootProject;
|
|
1087
|
+
if (rootProject) {
|
|
1088
|
+
const rootComponent = await buildObjectForGradleModule(
|
|
1089
|
+
rootProject,
|
|
1072
1090
|
retMap.metadata,
|
|
1073
1091
|
);
|
|
1074
|
-
if (!
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1092
|
+
if (!includedBuilds.includes(key)) {
|
|
1093
|
+
parentComponent = rootComponent;
|
|
1094
|
+
}
|
|
1095
|
+
gradleModules.set(key, rootComponent);
|
|
1096
|
+
if (!allProjectsAddedPurls.includes(rootComponent["purl"])) {
|
|
1097
|
+
allProjects.push(rootComponent);
|
|
1098
|
+
rootDependsOn.add(rootComponent["bom-ref"]);
|
|
1099
|
+
allProjectsAddedPurls.push(rootComponent["purl"]);
|
|
1078
1100
|
}
|
|
1079
|
-
|
|
1101
|
+
allProjectsStr = allProjectsStr.concat(retMap.projects);
|
|
1080
1102
|
}
|
|
1081
1103
|
}
|
|
1082
|
-
//
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
if (
|
|
1095
|
-
gradleFiles?.length &&
|
|
1096
|
-
options.installDeps &&
|
|
1097
|
-
isPackageManagerAllowed(
|
|
1098
|
-
"gradle",
|
|
1099
|
-
["maven", "bazel", "sbt", "mill"],
|
|
1100
|
-
options,
|
|
1101
|
-
)
|
|
1102
|
-
) {
|
|
1103
|
-
allProjects.push(parentComponent);
|
|
1104
|
-
const gradleCmd = getGradleCommand(gradleRootPath, null);
|
|
1105
|
-
const gradleDepTask = readEnvironmentVariable("GRADLE_DEPENDENCY_TASK")
|
|
1106
|
-
? readEnvironmentVariable("GRADLE_DEPENDENCY_TASK")
|
|
1107
|
-
: "dependencies";
|
|
1108
|
-
|
|
1109
|
-
const gradleSubCommands = [];
|
|
1110
|
-
let modulesToSkip = readEnvironmentVariable("GRADLE_SKIP_MODULES")
|
|
1111
|
-
? readEnvironmentVariable("GRADLE_SKIP_MODULES").split(",")
|
|
1112
|
-
: [];
|
|
1113
|
-
if (readEnvironmentVariable("GRADLE_SKIP_MODULE_DEPENDENCIES")) {
|
|
1114
|
-
modulesToSkip = modulesToSkip.concat(
|
|
1115
|
-
readEnvironmentVariable("GRADLE_SKIP_MODULE_DEPENDENCIES").split(","),
|
|
1116
|
-
);
|
|
1117
|
-
}
|
|
1118
|
-
if (!modulesToSkip.includes("root")) {
|
|
1119
|
-
gradleSubCommands.push(gradleDepTask);
|
|
1120
|
-
}
|
|
1121
|
-
for (const [key, sp] of gradleModules) {
|
|
1122
|
-
//create single command for dependencies tasks on all subprojects
|
|
1123
|
-
if (sp.purl !== parentComponent.purl && !modulesToSkip.includes(key)) {
|
|
1124
|
-
gradleSubCommands.push(`${key}:${gradleDepTask}`);
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
const gradleArguments = buildGradleCommandArguments(
|
|
1128
|
-
readEnvironmentVariable("GRADLE_ARGS")
|
|
1129
|
-
? readEnvironmentVariable("GRADLE_ARGS").split(" ")
|
|
1130
|
-
: [],
|
|
1131
|
-
gradleSubCommands,
|
|
1132
|
-
readEnvironmentVariable("GRADLE_ARGS_DEPENDENCIES")
|
|
1133
|
-
? readEnvironmentVariable("GRADLE_ARGS_DEPENDENCIES").split(" ")
|
|
1134
|
-
: [],
|
|
1135
|
-
gradleCmd.length,
|
|
1136
|
-
);
|
|
1137
|
-
const allOutputs = [];
|
|
1138
|
-
for (const gradleArg of gradleArguments) {
|
|
1139
|
-
if (DEBUG_MODE) {
|
|
1140
|
-
console.log(
|
|
1141
|
-
`Executing ${gradleCmd} with arguments ${gradleArg.join(" ").substring(0, 150)}... in ${gradleRootPath}`,
|
|
1142
|
-
);
|
|
1143
|
-
}
|
|
1144
|
-
thoughtLog(
|
|
1145
|
-
`Let's invoke '${basename(gradleCmd)}' with the arguments '${gradleArg.join(" ").substring(0, 100)} ...'.`,
|
|
1146
|
-
);
|
|
1147
|
-
const finalGradleArg = gradleArg.includes("--info")
|
|
1148
|
-
? gradleArg
|
|
1149
|
-
: gradleArg.concat(["--info"]);
|
|
1150
|
-
const sresult = safeSpawnSync(gradleCmd, finalGradleArg, {
|
|
1151
|
-
cwd: gradleRootPath,
|
|
1152
|
-
shell: isWin,
|
|
1153
|
-
});
|
|
1154
|
-
if (sresult.status !== 0 || sresult.error) {
|
|
1155
|
-
if (options.failOnError || DEBUG_MODE) {
|
|
1156
|
-
console.error(sresult.stdout, sresult.stderr);
|
|
1104
|
+
// Get the sub-project properties and set the root dependencies
|
|
1105
|
+
if (allProjectsStr?.length) {
|
|
1106
|
+
const modulesToSkip = readEnvironmentVariable("GRADLE_SKIP_MODULES")
|
|
1107
|
+
? readEnvironmentVariable("GRADLE_SKIP_MODULES").split(",")
|
|
1108
|
+
: [];
|
|
1109
|
+
if (modulesToSkip.length) {
|
|
1110
|
+
thoughtLog(
|
|
1111
|
+
`Good news. I know there are ${allProjectsStr.length} gradle modules at ${gradleRootPath}. I must skip ${modulesToSkip.length} out of these.`,
|
|
1112
|
+
);
|
|
1157
1113
|
}
|
|
1158
|
-
|
|
1159
|
-
}
|
|
1160
|
-
if (sresult.stdout !== null) {
|
|
1161
|
-
allOutputs.push(sresult.stdout);
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
const sstdout = allOutputs.join("\n");
|
|
1165
|
-
if (sstdout) {
|
|
1166
|
-
const cmdOutput = Buffer.from(sstdout).toString();
|
|
1167
|
-
const perProjectOutput = splitOutputByGradleProjects(cmdOutput, [
|
|
1168
|
-
gradleDepTask,
|
|
1169
|
-
]);
|
|
1170
|
-
for (const key of gradleModules.keys()) {
|
|
1171
|
-
const parsedList = await parseGradleDep(
|
|
1172
|
-
perProjectOutput.has(key) ? perProjectOutput.get(key) : "",
|
|
1173
|
-
key,
|
|
1174
|
-
gradleModules,
|
|
1114
|
+
parallelPropTaskOut = executeParallelGradleProperties(
|
|
1175
1115
|
gradleRootPath,
|
|
1116
|
+
allProjectsStr.filter((module) => !modulesToSkip.includes(module)),
|
|
1117
|
+
);
|
|
1118
|
+
const splitPropTaskOut = splitOutputByGradleProjects(
|
|
1119
|
+
parallelPropTaskOut,
|
|
1120
|
+
["properties"],
|
|
1176
1121
|
);
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
parentComponent,
|
|
1122
|
+
|
|
1123
|
+
for (const subProject of allProjectsStr) {
|
|
1124
|
+
const retMap = parseGradleProperties(
|
|
1125
|
+
splitPropTaskOut.get(subProject),
|
|
1126
|
+
subProject,
|
|
1183
1127
|
);
|
|
1184
|
-
|
|
1185
|
-
if (
|
|
1186
|
-
|
|
1187
|
-
|
|
1128
|
+
const rootSubProject = retMap.rootProject;
|
|
1129
|
+
if (rootSubProject) {
|
|
1130
|
+
const rootSubProjectObj = await buildObjectForGradleModule(
|
|
1131
|
+
rootSubProject === "root" ? subProject : rootSubProject,
|
|
1132
|
+
retMap.metadata,
|
|
1188
1133
|
);
|
|
1134
|
+
if (!allProjectsAddedPurls.includes(rootSubProjectObj["purl"])) {
|
|
1135
|
+
allProjects.push(rootSubProjectObj);
|
|
1136
|
+
rootDependsOn.add(rootSubProjectObj["bom-ref"]);
|
|
1137
|
+
allProjectsAddedPurls.push(rootSubProjectObj["purl"]);
|
|
1138
|
+
}
|
|
1139
|
+
gradleModules.set(subProject, rootSubProjectObj);
|
|
1189
1140
|
}
|
|
1190
1141
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1142
|
+
// Bug #317 fix
|
|
1143
|
+
parentComponent.components = allProjects.flatMap((s) => {
|
|
1144
|
+
delete s.qualifiers;
|
|
1145
|
+
delete s.evidence;
|
|
1146
|
+
return s;
|
|
1147
|
+
});
|
|
1148
|
+
dependencies.push({
|
|
1149
|
+
ref: parentComponent["bom-ref"],
|
|
1150
|
+
dependsOn: [...rootDependsOn].sort(),
|
|
1151
|
+
});
|
|
1194
1152
|
}
|
|
1195
1153
|
}
|
|
1196
|
-
if (
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1154
|
+
if (
|
|
1155
|
+
gradleFiles?.length &&
|
|
1156
|
+
options.installDeps &&
|
|
1157
|
+
isPackageManagerAllowed(
|
|
1158
|
+
"gradle",
|
|
1159
|
+
["maven", "bazel", "sbt", "mill"],
|
|
1160
|
+
options,
|
|
1161
|
+
)
|
|
1162
|
+
) {
|
|
1163
|
+
allProjects.push(parentComponent);
|
|
1164
|
+
const gradleCmd = getGradleCommand(gradleRootPath, null);
|
|
1165
|
+
const gradleDepTask = readEnvironmentVariable("GRADLE_DEPENDENCY_TASK")
|
|
1166
|
+
? readEnvironmentVariable("GRADLE_DEPENDENCY_TASK")
|
|
1167
|
+
: "dependencies";
|
|
1203
1168
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1169
|
+
const gradleSubCommands = [];
|
|
1170
|
+
let modulesToSkip = readEnvironmentVariable("GRADLE_SKIP_MODULES")
|
|
1171
|
+
? readEnvironmentVariable("GRADLE_SKIP_MODULES").split(",")
|
|
1172
|
+
: [];
|
|
1173
|
+
if (readEnvironmentVariable("GRADLE_SKIP_MODULE_DEPENDENCIES")) {
|
|
1174
|
+
modulesToSkip = modulesToSkip.concat(
|
|
1175
|
+
readEnvironmentVariable("GRADLE_SKIP_MODULE_DEPENDENCIES").split(","),
|
|
1176
|
+
);
|
|
1177
|
+
}
|
|
1178
|
+
if (!modulesToSkip.includes("root")) {
|
|
1179
|
+
gradleSubCommands.push(gradleDepTask);
|
|
1180
|
+
}
|
|
1181
|
+
for (const [key, sp] of gradleModules) {
|
|
1182
|
+
//create single command for dependencies tasks on all subprojects
|
|
1183
|
+
if (sp.purl !== parentComponent.purl && !modulesToSkip.includes(key)) {
|
|
1184
|
+
gradleSubCommands.push(`${key}:${gradleDepTask}`);
|
|
1207
1185
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1186
|
+
}
|
|
1187
|
+
const gradleArguments = buildGradleCommandArguments(
|
|
1188
|
+
readEnvironmentVariable("GRADLE_ARGS")
|
|
1189
|
+
? readEnvironmentVariable("GRADLE_ARGS").split(" ")
|
|
1190
|
+
: [],
|
|
1191
|
+
gradleSubCommands,
|
|
1192
|
+
readEnvironmentVariable("GRADLE_ARGS_DEPENDENCIES")
|
|
1193
|
+
? readEnvironmentVariable("GRADLE_ARGS_DEPENDENCIES").split(" ")
|
|
1194
|
+
: [],
|
|
1195
|
+
gradleCmd.length,
|
|
1196
|
+
);
|
|
1197
|
+
const allOutputs = [];
|
|
1198
|
+
for (const gradleArg of gradleArguments) {
|
|
1199
|
+
if (DEBUG_MODE) {
|
|
1200
|
+
console.log(
|
|
1201
|
+
`Executing ${gradleCmd} with arguments ${gradleArg.join(" ").substring(0, 150)}... in ${gradleRootPath}`,
|
|
1213
1202
|
);
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1203
|
+
}
|
|
1204
|
+
thoughtLog(
|
|
1205
|
+
`Let's invoke '${basename(gradleCmd)}' with the arguments '${gradleArg.join(" ").substring(0, 100)} ...'.`,
|
|
1206
|
+
);
|
|
1207
|
+
const finalGradleArg = gradleArg.includes("--info")
|
|
1208
|
+
? gradleArg
|
|
1209
|
+
: gradleArg.concat(["--info"]);
|
|
1210
|
+
const sresult = safeSpawnSync(gradleCmd, finalGradleArg, {
|
|
1211
|
+
cwd: gradleRootPath,
|
|
1212
|
+
shell: isWin,
|
|
1213
|
+
});
|
|
1214
|
+
if (sresult.status !== 0 || sresult.error) {
|
|
1215
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
1216
|
+
console.error(sresult.stdout, sresult.stderr);
|
|
1223
1217
|
}
|
|
1218
|
+
recordGradleInvocationFailure(sresult, {
|
|
1219
|
+
command: `${gradleCmd} ${finalGradleArg.join(" ")}`,
|
|
1220
|
+
});
|
|
1221
|
+
deferFailOnError(options, {
|
|
1222
|
+
ecosystem: "java",
|
|
1223
|
+
tool: "gradle",
|
|
1224
|
+
exitCode:
|
|
1225
|
+
typeof sresult.status === "number" ? sresult.status : undefined,
|
|
1226
|
+
command: `${gradleCmd} ${finalGradleArg.join(" ")}`,
|
|
1227
|
+
detail: "the gradle dependencies task failed",
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
if (sresult.stdout !== null) {
|
|
1231
|
+
allOutputs.push(sresult.stdout);
|
|
1224
1232
|
}
|
|
1225
1233
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1234
|
+
const sstdout = allOutputs.join("\n");
|
|
1235
|
+
if (sstdout) {
|
|
1236
|
+
const cmdOutput = Buffer.from(sstdout).toString();
|
|
1237
|
+
const perProjectOutput = splitOutputByGradleProjects(cmdOutput, [
|
|
1238
|
+
gradleDepTask,
|
|
1239
|
+
]);
|
|
1240
|
+
for (const key of gradleModules.keys()) {
|
|
1241
|
+
const parsedList = await parseGradleDep(
|
|
1242
|
+
perProjectOutput.has(key) ? perProjectOutput.get(key) : "",
|
|
1243
|
+
key,
|
|
1244
|
+
gradleModules,
|
|
1245
|
+
gradleRootPath,
|
|
1232
1246
|
);
|
|
1247
|
+
const dlist = parsedList.pkgList;
|
|
1248
|
+
if (parsedList?.dependenciesList?.length) {
|
|
1249
|
+
dependencies = mergeDependencies(
|
|
1250
|
+
dependencies,
|
|
1251
|
+
parsedList.dependenciesList,
|
|
1252
|
+
parentComponent,
|
|
1253
|
+
);
|
|
1254
|
+
} else {
|
|
1255
|
+
if (dlist?.length) {
|
|
1256
|
+
thoughtLog(
|
|
1257
|
+
`Hmm, I didn't find any dependencies after executing '${basename(gradleCmd)}' for the project ${key}. However, I did get ${dlist.length} components, which is confusing.`,
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
if (dlist?.length) {
|
|
1262
|
+
pkgList = pkgList.concat(dlist);
|
|
1263
|
+
}
|
|
1233
1264
|
}
|
|
1234
1265
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1266
|
+
if (pkgList.length) {
|
|
1267
|
+
const fileToUrlMap = parseGradleInfoLogsForUrls(sstdout);
|
|
1268
|
+
// When the `resolve-gradle-distribution` feature flag is enabled, the init script
|
|
1269
|
+
// probes each repository for the actual artifact and emits the resolved URL keyed
|
|
1270
|
+
// by `group:name:version`. This is more accurate than guessing the first repository.
|
|
1271
|
+
const resolvedDistributions =
|
|
1272
|
+
parseGradleResolvedDistributions(parallelPropTaskOut);
|
|
1273
|
+
|
|
1274
|
+
for (const p of pkgList) {
|
|
1275
|
+
if (!p.externalReferences) {
|
|
1276
|
+
p.externalReferences = [];
|
|
1277
|
+
}
|
|
1278
|
+
if (!p.externalReferences.find((r) => r.type === "distribution")) {
|
|
1279
|
+
const groupPath = p.group.replace(/\./g, "/");
|
|
1280
|
+
const matchPath = `${groupPath}/${p.name}/${p.version}/`;
|
|
1281
|
+
let matchedUrl = Object.values(fileToUrlMap).find((url) =>
|
|
1282
|
+
url.includes(matchPath),
|
|
1283
|
+
);
|
|
1284
|
+
if (!matchedUrl) {
|
|
1285
|
+
matchedUrl =
|
|
1286
|
+
resolvedDistributions[`${p.group}:${p.name}:${p.version}`];
|
|
1287
|
+
}
|
|
1288
|
+
if (matchedUrl) {
|
|
1289
|
+
p.externalReferences.push({
|
|
1290
|
+
type: "distribution",
|
|
1291
|
+
url: matchedUrl,
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if (parentComponent.components?.length) {
|
|
1297
|
+
for (const subProj of parentComponent.components) {
|
|
1298
|
+
pkgList = pkgList.filter(
|
|
1299
|
+
(pkg) =>
|
|
1300
|
+
pkg["bom-ref"] !== subProj["bom-ref"] &&
|
|
1301
|
+
pkg["bom-ref"] !== parentComponent["bom-ref"],
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
thoughtLog(
|
|
1306
|
+
`Obtained ${pkgList.length} components by executing the '${basename(gradleCmd)}' command.`,
|
|
1248
1307
|
);
|
|
1308
|
+
if (DEBUG_MODE) {
|
|
1309
|
+
console.log("Obtained", pkgList.length, "from this gradle project.");
|
|
1310
|
+
}
|
|
1311
|
+
} else if (hasGradleInvocationFailure()) {
|
|
1312
|
+
// The invocation failure is already ranked, and an empty
|
|
1313
|
+
// configuration is what that failure looks like from here: reporting
|
|
1314
|
+
// both would rank two repairs for one broken toolchain.
|
|
1315
|
+
thoughtLog(
|
|
1316
|
+
"**GRADLE:** No configuration resolved, which the recorded invocation failure already explains.",
|
|
1317
|
+
);
|
|
1318
|
+
} else {
|
|
1319
|
+
recordDegradation("jvm.gradle.unresolved-config", {
|
|
1320
|
+
ecosystem: "java",
|
|
1321
|
+
tool: "gradle",
|
|
1322
|
+
impact: "transitive-deps",
|
|
1323
|
+
command: `${basename(gradleCmd)} ${gradleDepTask}`,
|
|
1324
|
+
detail:
|
|
1325
|
+
"Gradle did not resolve any dependency configuration, so the gradle components carry no resolved dependencies.",
|
|
1326
|
+
});
|
|
1327
|
+
thoughtLog(
|
|
1328
|
+
"**GRADLE:** SBOM is incomplete. I recommend troubleshooting the issue to improve the BOM precision.",
|
|
1329
|
+
);
|
|
1330
|
+
if (!DEBUG_MODE) {
|
|
1331
|
+
console.log(
|
|
1332
|
+
"No packages found. Set the environment variable 'CDXGEN_DEBUG_MODE=debug' to troubleshoot any gradle related errors.",
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
deferFailOnError(options, {
|
|
1336
|
+
ecosystem: "java",
|
|
1337
|
+
tool: "gradle",
|
|
1338
|
+
detail: "gradle resolved no dependency configuration",
|
|
1339
|
+
});
|
|
1249
1340
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1341
|
+
if (
|
|
1342
|
+
(!readEnvironmentVariable("GRADLE_STOP_DAEMON") &&
|
|
1343
|
+
(!readEnvironmentVariable("GRADLE_USE_DAEMON") ||
|
|
1344
|
+
["true", "1"].includes(
|
|
1345
|
+
readEnvironmentVariable("GRADLE_USE_DAEMON"),
|
|
1346
|
+
))) ||
|
|
1347
|
+
["true", "1"].includes(readEnvironmentVariable("GRADLE_STOP_DAEMON"))
|
|
1348
|
+
) {
|
|
1349
|
+
if (DEBUG_MODE) {
|
|
1350
|
+
console.log("Stopping gradle daemon...");
|
|
1351
|
+
}
|
|
1352
|
+
const sresult = safeSpawnSync(gradleCmd, ["--stop"], {
|
|
1353
|
+
cwd: gradleRootPath,
|
|
1354
|
+
shell: isWin,
|
|
1355
|
+
});
|
|
1356
|
+
if (sresult.status !== 0 || sresult.error) {
|
|
1357
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
1358
|
+
console.error(sresult.stdout, sresult.stderr);
|
|
1359
|
+
}
|
|
1360
|
+
deferFailOnError(options, {
|
|
1361
|
+
ecosystem: "java",
|
|
1362
|
+
tool: "gradle",
|
|
1363
|
+
exitCode:
|
|
1364
|
+
typeof sresult.status === "number" ? sresult.status : undefined,
|
|
1365
|
+
detail: "stopping the gradle daemon failed",
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1262
1368
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
if (options.failOnError || DEBUG_MODE) {
|
|
1269
|
-
console.error(sresult.stdout, sresult.stderr);
|
|
1369
|
+
// Should we attempt to resolve class names
|
|
1370
|
+
if (options.resolveClass || options.deep) {
|
|
1371
|
+
const tmpjarNSMapping = await collectJarNS(GRADLE_CACHE_DIR);
|
|
1372
|
+
if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
|
|
1373
|
+
jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
|
|
1270
1374
|
}
|
|
1271
|
-
options.failOnError && process.exit(1);
|
|
1272
1375
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1376
|
+
|
|
1377
|
+
// A version catalog declares coordinates without resolving them, so it
|
|
1378
|
+
// is used only when Gradle produced no dependency information at all.
|
|
1379
|
+
// The emitted components carry cdx:gradle:catalog=true to mark them as
|
|
1380
|
+
// declared rather than resolved.
|
|
1381
|
+
if (!pkgList.length) {
|
|
1382
|
+
const catalogFiles = getAllFiles(
|
|
1383
|
+
path,
|
|
1384
|
+
`${options.multiProject ? "**/" : ""}gradle/libs.versions.toml`,
|
|
1385
|
+
options,
|
|
1386
|
+
);
|
|
1387
|
+
for (const catalogFile of catalogFiles) {
|
|
1388
|
+
if (DEBUG_MODE) {
|
|
1389
|
+
console.log(`Parsing ${catalogFile}`);
|
|
1390
|
+
}
|
|
1391
|
+
const dlist = parseGradleVersionCatalog(catalogFile);
|
|
1392
|
+
if (dlist?.length) {
|
|
1393
|
+
pkgList = pkgList.concat(dlist);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1279
1396
|
}
|
|
1280
1397
|
}
|
|
1281
|
-
}
|
|
1282
1398
|
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
options,
|
|
1290
|
-
);
|
|
1291
|
-
if (
|
|
1292
|
-
bazelFiles?.length &&
|
|
1293
|
-
!hasAnyProjectType(["docker", "oci", "container", "os"], options, false) &&
|
|
1294
|
-
isPackageManagerAllowed(
|
|
1295
|
-
"bazel",
|
|
1296
|
-
["maven", "gradle", "sbt", "mill"],
|
|
1399
|
+
// Bazel
|
|
1400
|
+
// Look for the BUILD file only in the root directory
|
|
1401
|
+
// NOTE: This can match BUILD files used by perl, so could lead to errors in some projects
|
|
1402
|
+
const bazelFiles = getAllFiles(
|
|
1403
|
+
path,
|
|
1404
|
+
`${options.multiProject ? "**/" : ""}{WORKSPACE{,.bazel},MODULE.bazel}`,
|
|
1297
1405
|
options,
|
|
1298
|
-
)
|
|
1299
|
-
) {
|
|
1300
|
-
let BAZEL_CMD = "bazel";
|
|
1301
|
-
const bazelHome = readEnvironmentVariable("BAZEL_HOME");
|
|
1302
|
-
if (bazelHome) {
|
|
1303
|
-
BAZEL_CMD = join(bazelHome, "bin", "bazel");
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
// bzlmod: parse MODULE.bazel and MODULE.bazel.lock for BCR modules and
|
|
1307
|
-
// ecosystem dependencies (Maven, etc.). This works from the manifest and
|
|
1308
|
-
// lock alone, so BCR/Maven packages are extracted even when the bazel
|
|
1309
|
-
// toolchain is absent. Resolved third-party deps map to their true
|
|
1310
|
-
// ecosystem purl (pkg:maven, ...); only BCR modules use pkg:bazel.
|
|
1311
|
-
const moduleBazelFiles = bazelFiles.filter(
|
|
1312
|
-
(f) => basename(f) === "MODULE.bazel",
|
|
1313
1406
|
);
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1407
|
+
if (
|
|
1408
|
+
bazelFiles?.length &&
|
|
1409
|
+
!hasAnyProjectType(
|
|
1410
|
+
["docker", "oci", "container", "os"],
|
|
1411
|
+
options,
|
|
1412
|
+
false,
|
|
1413
|
+
) &&
|
|
1414
|
+
isPackageManagerAllowed(
|
|
1415
|
+
"bazel",
|
|
1416
|
+
["maven", "gradle", "sbt", "mill"],
|
|
1417
|
+
options,
|
|
1418
|
+
)
|
|
1419
|
+
) {
|
|
1420
|
+
let BAZEL_CMD = "bazel";
|
|
1421
|
+
const bazelHome = readEnvironmentVariable("BAZEL_HOME");
|
|
1422
|
+
if (bazelHome) {
|
|
1423
|
+
BAZEL_CMD = join(bazelHome, "bin", "bazel");
|
|
1322
1424
|
}
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1425
|
+
|
|
1426
|
+
// bzlmod: parse MODULE.bazel and MODULE.bazel.lock for BCR modules and
|
|
1427
|
+
// ecosystem dependencies (Maven, etc.). This works from the manifest and
|
|
1428
|
+
// lock alone, so BCR/Maven packages are extracted even when the bazel
|
|
1429
|
+
// toolchain is absent. Resolved third-party deps map to their true
|
|
1430
|
+
// ecosystem purl (pkg:maven, ...); only BCR modules use pkg:bazel.
|
|
1431
|
+
const moduleBazelFiles = bazelFiles.filter(
|
|
1432
|
+
(f) => basename(f) === "MODULE.bazel",
|
|
1433
|
+
);
|
|
1434
|
+
for (const moduleFile of moduleBazelFiles) {
|
|
1435
|
+
const baseDir = dirname(moduleFile);
|
|
1436
|
+
const moduleResult = parseModuleBazel(moduleFile);
|
|
1437
|
+
if (moduleResult.pkgList?.length) {
|
|
1438
|
+
pkgList = pkgList.concat(moduleResult.pkgList);
|
|
1328
1439
|
}
|
|
1329
|
-
if (
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1440
|
+
if (moduleResult.parentComponent?.name) {
|
|
1441
|
+
parentComponent = moduleResult.parentComponent;
|
|
1442
|
+
}
|
|
1443
|
+
const lockFile = join(baseDir, "MODULE.bazel.lock");
|
|
1444
|
+
if (safeExistsSync(lockFile)) {
|
|
1445
|
+
const lockResult = parseModuleBazelLock(lockFile);
|
|
1446
|
+
if (lockResult.pkgList?.length) {
|
|
1447
|
+
pkgList = pkgList.concat(lockResult.pkgList);
|
|
1448
|
+
}
|
|
1449
|
+
if (lockResult.dependencies?.length) {
|
|
1450
|
+
dependencies = mergeDependencies(
|
|
1451
|
+
dependencies,
|
|
1452
|
+
lockResult.dependencies,
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1334
1455
|
}
|
|
1335
1456
|
}
|
|
1336
|
-
}
|
|
1337
1457
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
...(readEnvironmentVariable("BAZEL_ARGS")?.split(" ") || []),
|
|
1349
|
-
"build",
|
|
1350
|
-
bazelTarget,
|
|
1351
|
-
];
|
|
1352
|
-
// Automatically load any bazelrc file
|
|
1353
|
-
if (
|
|
1354
|
-
!readEnvironmentVariable("BAZEL_ARGS") &&
|
|
1355
|
-
safeExistsSync(join(basePath, ".bazelrc"))
|
|
1356
|
-
) {
|
|
1357
|
-
bArgs = ["--bazelrc=.bazelrc", "build", bazelTarget];
|
|
1358
|
-
}
|
|
1359
|
-
console.log("Executing", BAZEL_CMD, "in", basePath);
|
|
1360
|
-
let result = safeSpawnSync(BAZEL_CMD, bArgs, {
|
|
1361
|
-
cwd: basePath,
|
|
1362
|
-
shell: isWin,
|
|
1363
|
-
});
|
|
1364
|
-
if (result.status !== 0 || result.error) {
|
|
1365
|
-
if (result.stderr) {
|
|
1366
|
-
console.error(result.stdout, result.stderr);
|
|
1367
|
-
}
|
|
1368
|
-
console.log(
|
|
1369
|
-
"1. Check if bazel is installed and available in PATH.\n2. Try building your app with bazel prior to invoking cdxgen",
|
|
1370
|
-
);
|
|
1371
|
-
options.failOnError && process.exit(1);
|
|
1372
|
-
} else {
|
|
1373
|
-
const target = readEnvironmentVariable("BAZEL_TARGET") || "//...";
|
|
1374
|
-
let query = [
|
|
1458
|
+
// Querying bazel means building the project first, which is the most
|
|
1459
|
+
// expensive thing cdxgen can do and needs network access. Like every other
|
|
1460
|
+
// package-manager invocation it therefore runs only when dependency
|
|
1461
|
+
// installation is permitted; `--lifecycle pre-build` and secure mode both
|
|
1462
|
+
// turn it off, leaving the manifest and lock as the inventory source.
|
|
1463
|
+
for (const f of options.installDeps ? bazelFiles : []) {
|
|
1464
|
+
const basePath = dirname(f);
|
|
1465
|
+
// Invoke bazel build first
|
|
1466
|
+
const bazelTarget = readEnvironmentVariable("BAZEL_TARGET") || "//...";
|
|
1467
|
+
let bArgs = [
|
|
1375
1468
|
...(readEnvironmentVariable("BAZEL_ARGS")?.split(" ") || []),
|
|
1469
|
+
"build",
|
|
1470
|
+
bazelTarget,
|
|
1376
1471
|
];
|
|
1377
|
-
let bazelParser;
|
|
1378
1472
|
// Automatically load any bazelrc file
|
|
1379
1473
|
if (
|
|
1380
1474
|
!readEnvironmentVariable("BAZEL_ARGS") &&
|
|
1381
1475
|
safeExistsSync(join(basePath, ".bazelrc"))
|
|
1382
1476
|
) {
|
|
1383
|
-
|
|
1384
|
-
}
|
|
1385
|
-
if (
|
|
1386
|
-
["true", "1"].includes(
|
|
1387
|
-
readEnvironmentVariable("BAZEL_USE_ACTION_GRAPH"),
|
|
1388
|
-
)
|
|
1389
|
-
) {
|
|
1390
|
-
query = query.concat(["query", `deps(${target})`, "--output=label"]);
|
|
1391
|
-
bazelParser = parseBazelActionGraph;
|
|
1392
|
-
} else {
|
|
1393
|
-
query = query.concat([
|
|
1394
|
-
"aquery",
|
|
1395
|
-
"--output=textproto",
|
|
1396
|
-
"--skyframe_state",
|
|
1397
|
-
]);
|
|
1398
|
-
bazelParser = parseBazelSkyframe;
|
|
1477
|
+
bArgs = ["--bazelrc=.bazelrc", "build", bazelTarget];
|
|
1399
1478
|
}
|
|
1400
|
-
console.log("Executing", BAZEL_CMD,
|
|
1401
|
-
result = safeSpawnSync(BAZEL_CMD,
|
|
1479
|
+
console.log("Executing", BAZEL_CMD, "in", basePath);
|
|
1480
|
+
let result = safeSpawnSync(BAZEL_CMD, bArgs, {
|
|
1402
1481
|
cwd: basePath,
|
|
1482
|
+
shell: isWin,
|
|
1403
1483
|
});
|
|
1404
1484
|
if (result.status !== 0 || result.error) {
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
}
|
|
1408
|
-
const stdout = result.stdout;
|
|
1409
|
-
if (stdout) {
|
|
1410
|
-
const cmdOutput = Buffer.from(stdout).toString();
|
|
1411
|
-
const dlist = bazelParser(cmdOutput);
|
|
1412
|
-
if (dlist?.length) {
|
|
1413
|
-
pkgList = pkgList.concat(dlist);
|
|
1414
|
-
} else {
|
|
1415
|
-
console.log(
|
|
1416
|
-
"No packages were detected.\n1. Build your project using bazel build command before running cdxgen\n2. Try running the bazel aquery command manually to see if skyframe state can be retrieved.",
|
|
1417
|
-
);
|
|
1418
|
-
console.log(
|
|
1419
|
-
"If your project requires a different query, please file a bug at cyclonedx/cdxgen repo!",
|
|
1420
|
-
);
|
|
1421
|
-
options.failOnError && process.exit(1);
|
|
1485
|
+
if (result.stderr) {
|
|
1486
|
+
console.error(result.stdout, result.stderr);
|
|
1422
1487
|
}
|
|
1488
|
+
console.log(
|
|
1489
|
+
"1. Check if bazel is installed and available in PATH.\n2. Try building your app with bazel prior to invoking cdxgen",
|
|
1490
|
+
);
|
|
1491
|
+
deferFailOnError(options, {
|
|
1492
|
+
ecosystem: "java",
|
|
1493
|
+
tool: "bazel",
|
|
1494
|
+
exitCode:
|
|
1495
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1496
|
+
detail: "the bazel build failed",
|
|
1497
|
+
});
|
|
1423
1498
|
} else {
|
|
1424
|
-
|
|
1425
|
-
|
|
1499
|
+
const target = readEnvironmentVariable("BAZEL_TARGET") || "//...";
|
|
1500
|
+
let query = [
|
|
1501
|
+
...(readEnvironmentVariable("BAZEL_ARGS")?.split(" ") || []),
|
|
1502
|
+
];
|
|
1503
|
+
let bazelParser;
|
|
1504
|
+
// Automatically load any bazelrc file
|
|
1505
|
+
if (
|
|
1506
|
+
!readEnvironmentVariable("BAZEL_ARGS") &&
|
|
1507
|
+
safeExistsSync(join(basePath, ".bazelrc"))
|
|
1508
|
+
) {
|
|
1509
|
+
query = ["--bazelrc=.bazelrc"];
|
|
1510
|
+
}
|
|
1511
|
+
if (
|
|
1512
|
+
["true", "1"].includes(
|
|
1513
|
+
readEnvironmentVariable("BAZEL_USE_ACTION_GRAPH"),
|
|
1514
|
+
)
|
|
1515
|
+
) {
|
|
1516
|
+
query = query.concat([
|
|
1517
|
+
"query",
|
|
1518
|
+
`deps(${target})`,
|
|
1519
|
+
"--output=label",
|
|
1520
|
+
]);
|
|
1521
|
+
bazelParser = parseBazelActionGraph;
|
|
1522
|
+
} else {
|
|
1523
|
+
query = query.concat([
|
|
1524
|
+
"aquery",
|
|
1525
|
+
"--output=textproto",
|
|
1526
|
+
"--skyframe_state",
|
|
1527
|
+
]);
|
|
1528
|
+
bazelParser = parseBazelSkyframe;
|
|
1529
|
+
}
|
|
1530
|
+
console.log(
|
|
1531
|
+
"Executing",
|
|
1532
|
+
BAZEL_CMD,
|
|
1533
|
+
`${query.join(" ")} in`,
|
|
1534
|
+
basePath,
|
|
1535
|
+
);
|
|
1536
|
+
result = safeSpawnSync(BAZEL_CMD, query, {
|
|
1537
|
+
cwd: basePath,
|
|
1538
|
+
});
|
|
1539
|
+
if (result.status !== 0 || result.error) {
|
|
1540
|
+
console.error(result.stdout, result.stderr);
|
|
1541
|
+
deferFailOnError(options, {
|
|
1542
|
+
ecosystem: "java",
|
|
1543
|
+
tool: "bazel",
|
|
1544
|
+
exitCode:
|
|
1545
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1546
|
+
detail: "the bazel graph query failed",
|
|
1547
|
+
});
|
|
1548
|
+
}
|
|
1549
|
+
const stdout = result.stdout;
|
|
1550
|
+
if (stdout) {
|
|
1551
|
+
const cmdOutput = Buffer.from(stdout).toString();
|
|
1552
|
+
const dlist = bazelParser(cmdOutput);
|
|
1553
|
+
if (dlist?.length) {
|
|
1554
|
+
pkgList = pkgList.concat(dlist);
|
|
1555
|
+
} else {
|
|
1556
|
+
console.log(
|
|
1557
|
+
"No packages were detected.\n1. Build your project using bazel build command before running cdxgen\n2. Try running the bazel aquery command manually to see if skyframe state can be retrieved.",
|
|
1558
|
+
);
|
|
1559
|
+
console.log(
|
|
1560
|
+
"If your project requires a different query, please file a bug at cyclonedx/cdxgen repo!",
|
|
1561
|
+
);
|
|
1562
|
+
deferFailOnError(options, {
|
|
1563
|
+
ecosystem: "java",
|
|
1564
|
+
tool: "bazel",
|
|
1565
|
+
detail: "the bazel graph query produced no packages",
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
} else {
|
|
1569
|
+
console.log("Bazel unexpectedly didn't produce any output");
|
|
1570
|
+
deferFailOnError(options, {
|
|
1571
|
+
ecosystem: "java",
|
|
1572
|
+
tool: "bazel",
|
|
1573
|
+
detail: "the bazel graph query unexpectedly produced no output",
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1426
1576
|
}
|
|
1427
1577
|
}
|
|
1428
1578
|
}
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
// scala sbt
|
|
1432
|
-
// Identify sbt projects via its `project` directory:
|
|
1433
|
-
// - all SBT project _should_ define build.properties file with sbt version info
|
|
1434
|
-
// - SBT projects _typically_ have some configs/plugins defined in .sbt files
|
|
1435
|
-
// - SBT projects that are still on 0.13.x, can still use the old approach,
|
|
1436
|
-
// where configs are defined via Scala files
|
|
1437
|
-
// Detecting one of those should be enough to determine an SBT project.
|
|
1438
|
-
let sbtProjectFiles = getAllFiles(
|
|
1439
|
-
path,
|
|
1440
|
-
`${
|
|
1441
|
-
options.multiProject ? "**/" : ""
|
|
1442
|
-
}project/{build.properties,*.sbt,*.scala}`,
|
|
1443
|
-
options,
|
|
1444
|
-
);
|
|
1445
|
-
|
|
1446
|
-
let sbtProjects = [];
|
|
1447
|
-
for (const i in sbtProjectFiles) {
|
|
1448
|
-
// parent dir of sbtProjectFile is the `project` directory
|
|
1449
|
-
// parent dir of `project` is the sbt root project directory
|
|
1450
|
-
const baseDir = dirname(dirname(sbtProjectFiles[i]));
|
|
1451
|
-
sbtProjects = sbtProjects.concat(baseDir);
|
|
1452
|
-
}
|
|
1453
1579
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1580
|
+
// scala sbt
|
|
1581
|
+
// Identify sbt projects via its `project` directory:
|
|
1582
|
+
// - all SBT project _should_ define build.properties file with sbt version info
|
|
1583
|
+
// - SBT projects _typically_ have some configs/plugins defined in .sbt files
|
|
1584
|
+
// - SBT projects that are still on 0.13.x, can still use the old approach,
|
|
1585
|
+
// where configs are defined via Scala files
|
|
1586
|
+
// Detecting one of those should be enough to determine an SBT project.
|
|
1587
|
+
let sbtProjectFiles = getAllFiles(
|
|
1457
1588
|
path,
|
|
1458
|
-
`${
|
|
1589
|
+
`${
|
|
1590
|
+
options.multiProject ? "**/" : ""
|
|
1591
|
+
}project/{build.properties,*.sbt,*.scala}`,
|
|
1459
1592
|
options,
|
|
1460
1593
|
);
|
|
1594
|
+
|
|
1595
|
+
let sbtProjects = [];
|
|
1461
1596
|
for (const i in sbtProjectFiles) {
|
|
1462
|
-
|
|
1597
|
+
// parent dir of sbtProjectFile is the `project` directory
|
|
1598
|
+
// parent dir of `project` is the sbt root project directory
|
|
1599
|
+
const baseDir = dirname(dirname(sbtProjectFiles[i]));
|
|
1463
1600
|
sbtProjects = sbtProjects.concat(baseDir);
|
|
1464
1601
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1602
|
+
|
|
1603
|
+
// Fallback in case sbt's project directory is non-existent
|
|
1604
|
+
if (!sbtProjects.length) {
|
|
1605
|
+
sbtProjectFiles = getAllFiles(
|
|
1606
|
+
path,
|
|
1607
|
+
`${options.multiProject ? "**/" : ""}*.sbt`,
|
|
1608
|
+
options,
|
|
1609
|
+
);
|
|
1610
|
+
for (const i in sbtProjectFiles) {
|
|
1611
|
+
const baseDir = dirname(sbtProjectFiles[i]);
|
|
1612
|
+
sbtProjects = sbtProjects.concat(baseDir);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
// eliminate duplicates and ignore project directories
|
|
1616
|
+
sbtProjects = [...new Set(sbtProjects)].filter(
|
|
1617
|
+
(p) => !p.endsWith(`${sep}project`) && !p.includes(`target${sep}`),
|
|
1618
|
+
);
|
|
1619
|
+
const sbtLockFiles = getAllFiles(
|
|
1620
|
+
path,
|
|
1621
|
+
`${options.multiProject ? "**/" : ""}build.sbt.lock`,
|
|
1482
1622
|
options,
|
|
1483
|
-
)
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
if (
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1623
|
+
);
|
|
1624
|
+
const tempCacheDir = safeMkdtempSync(join(getTmpDir(), "sbt-cache-"));
|
|
1625
|
+
safeMkdirSync(tempCacheDir, { recursive: true });
|
|
1626
|
+
if (
|
|
1627
|
+
sbtProjects?.length &&
|
|
1628
|
+
isPackageManagerAllowed(
|
|
1629
|
+
"sbt",
|
|
1630
|
+
["bazel", "maven", "gradle", "mill"],
|
|
1631
|
+
options,
|
|
1632
|
+
)
|
|
1633
|
+
) {
|
|
1634
|
+
// If the project use sbt lock files
|
|
1635
|
+
if (sbtLockFiles?.length) {
|
|
1636
|
+
for (const f of sbtLockFiles) {
|
|
1637
|
+
const dlist = await parseSbtLock(f);
|
|
1638
|
+
if (dlist?.length) {
|
|
1639
|
+
pkgList = pkgList.concat(dlist);
|
|
1640
|
+
}
|
|
1491
1641
|
}
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1642
|
+
} else {
|
|
1643
|
+
const SBT_CMD = readEnvironmentVariable("SBT_CMD") || "sbt";
|
|
1644
|
+
let sbtVersion = determineSbtVersion(path);
|
|
1645
|
+
// If can't find sbt version at the root of repository then search in
|
|
1646
|
+
// sbt project array too because sometimes the project folder isn't at
|
|
1647
|
+
// root of repository
|
|
1648
|
+
if (sbtVersion == null) {
|
|
1649
|
+
for (const i in sbtProjects) {
|
|
1650
|
+
sbtVersion = determineSbtVersion(sbtProjects[i]);
|
|
1651
|
+
if (sbtVersion != null) {
|
|
1652
|
+
break;
|
|
1653
|
+
}
|
|
1504
1654
|
}
|
|
1505
1655
|
}
|
|
1506
|
-
}
|
|
1507
|
-
if (DEBUG_MODE) {
|
|
1508
|
-
console.log(`Detected sbt version: ${sbtVersion}`);
|
|
1509
|
-
}
|
|
1510
|
-
// Introduced in 1.2.0 https://www.scala-sbt.org/1.x/docs/sbt-1.2-Release-Notes.html#addPluginSbtFile+command,
|
|
1511
|
-
// however working properly for real only since 1.3.4: https://github.com/sbt/sbt/releases/tag/v1.3.4
|
|
1512
|
-
const standalonePluginFile =
|
|
1513
|
-
sbtVersion != null &&
|
|
1514
|
-
gte(sbtVersion, "1.3.4") &&
|
|
1515
|
-
lte(sbtVersion, "1.4.0");
|
|
1516
|
-
const useSlashSyntax = !sbtVersion || gte(sbtVersion, "1.5.0");
|
|
1517
|
-
const isDependencyTreeBuiltIn =
|
|
1518
|
-
sbtVersion != null && gte(sbtVersion, "1.4.0");
|
|
1519
|
-
const tempDir = safeMkdtempSync(join(getTmpDir(), "cdxsbt-"));
|
|
1520
|
-
const tempSbtgDir = safeMkdtempSync(join(getTmpDir(), "cdxsbtg-"));
|
|
1521
|
-
safeMkdirSync(tempSbtgDir, { recursive: true });
|
|
1522
|
-
// Create temporary plugins file
|
|
1523
|
-
const tempSbtPlugins = join(tempSbtgDir, "dep-plugins.sbt");
|
|
1524
|
-
|
|
1525
|
-
// Requires a custom version of `sbt-dependency-graph` that
|
|
1526
|
-
// supports `--append` for `toFile` subtask.
|
|
1527
|
-
let sbtPluginDefinition = `\naddSbtPlugin("io.shiftleft" % "sbt-dependency-graph" % "0.10.0-append-to-file3")\n`;
|
|
1528
|
-
if (isDependencyTreeBuiltIn) {
|
|
1529
|
-
sbtPluginDefinition = "\naddDependencyTreePlugin\n";
|
|
1530
1656
|
if (DEBUG_MODE) {
|
|
1531
|
-
console.log(
|
|
1657
|
+
console.log(`Detected sbt version: ${sbtVersion}`);
|
|
1532
1658
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
subDlFiles.push(subDlFile);
|
|
1574
|
-
sbtArgs.push(
|
|
1575
|
-
`"${sp}/dependencyTree / toFile ${subDlFile} --force"`,
|
|
1576
|
-
);
|
|
1577
|
-
}
|
|
1578
|
-
} else if (useSlashSyntax) {
|
|
1659
|
+
// Introduced in 1.2.0 https://www.scala-sbt.org/1.x/docs/sbt-1.2-Release-Notes.html#addPluginSbtFile+command,
|
|
1660
|
+
// however working properly for real only since 1.3.4: https://github.com/sbt/sbt/releases/tag/v1.3.4
|
|
1661
|
+
const standalonePluginFile =
|
|
1662
|
+
sbtVersion != null &&
|
|
1663
|
+
gte(sbtVersion, "1.3.4") &&
|
|
1664
|
+
lte(sbtVersion, "1.4.0");
|
|
1665
|
+
const useSlashSyntax = !sbtVersion || gte(sbtVersion, "1.5.0");
|
|
1666
|
+
const isDependencyTreeBuiltIn =
|
|
1667
|
+
sbtVersion != null && gte(sbtVersion, "1.4.0");
|
|
1668
|
+
const tempDir = safeMkdtempSync(join(getTmpDir(), "cdxsbt-"));
|
|
1669
|
+
const tempSbtgDir = safeMkdtempSync(join(getTmpDir(), "cdxsbtg-"));
|
|
1670
|
+
safeMkdirSync(tempSbtgDir, { recursive: true });
|
|
1671
|
+
// Create temporary plugins file
|
|
1672
|
+
const tempSbtPlugins = join(tempSbtgDir, "dep-plugins.sbt");
|
|
1673
|
+
|
|
1674
|
+
// Requires a custom version of `sbt-dependency-graph` that
|
|
1675
|
+
// supports `--append` for `toFile` subtask.
|
|
1676
|
+
let sbtPluginDefinition = `\naddSbtPlugin("io.shiftleft" % "sbt-dependency-graph" % "0.10.0-append-to-file3")\n`;
|
|
1677
|
+
if (isDependencyTreeBuiltIn) {
|
|
1678
|
+
sbtPluginDefinition = "\naddDependencyTreePlugin\n";
|
|
1679
|
+
if (DEBUG_MODE) {
|
|
1680
|
+
console.log("Using addDependencyTreePlugin as the custom plugin");
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
safeWriteSync(tempSbtPlugins, sbtPluginDefinition);
|
|
1684
|
+
let sbtExtraArgs = "";
|
|
1685
|
+
const env = { ...process.env };
|
|
1686
|
+
// We need to collect the jars from the cache
|
|
1687
|
+
if (options.deep) {
|
|
1688
|
+
sbtExtraArgs = " updateClassifiers";
|
|
1689
|
+
env["COURSIER_CACHE"] = tempCacheDir;
|
|
1690
|
+
env["SBT_IVY_HOME"] = tempCacheDir;
|
|
1691
|
+
}
|
|
1692
|
+
for (const i in sbtProjects) {
|
|
1693
|
+
const basePath = sbtProjects[i];
|
|
1694
|
+
const dlFile = join(tempDir, `dl-${i}.tmp`);
|
|
1695
|
+
let sbtArgs = [];
|
|
1696
|
+
let pluginFile = null;
|
|
1697
|
+
const subDlFiles = [];
|
|
1698
|
+
if (standalonePluginFile) {
|
|
1579
1699
|
sbtArgs = [
|
|
1580
|
-
|
|
1700
|
+
`-addPluginSbtFile=${tempSbtPlugins}`,
|
|
1701
|
+
`"dependencyList::toFile ${dlFile} --force"`,
|
|
1581
1702
|
];
|
|
1582
1703
|
subDlFiles.push(dlFile);
|
|
1583
1704
|
} else {
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1705
|
+
// write to the existing plugins file
|
|
1706
|
+
// Discover the real sbt project ids by asking sbt itself, falling
|
|
1707
|
+
// back to the regex-based heuristic if that fails. A per-subproject
|
|
1708
|
+
// `dependencyTree` fan-out yields a far more complete tree than a
|
|
1709
|
+
// single root resolution (which only captures the root project's own
|
|
1710
|
+
// libraryDependencies). See https://github.com/cdxgen/cdxgen/issues/4291
|
|
1711
|
+
const subprojects = discoverSbtProjectsFromCmd(
|
|
1712
|
+
basePath,
|
|
1713
|
+
SBT_CMD,
|
|
1714
|
+
env,
|
|
1715
|
+
);
|
|
1716
|
+
if (subprojects.length > 0 && useSlashSyntax) {
|
|
1717
|
+
sbtArgs = [
|
|
1718
|
+
`'set ThisBuild / asciiGraphWidth := 800'${sbtExtraArgs}`,
|
|
1719
|
+
];
|
|
1720
|
+
for (const sp of subprojects) {
|
|
1721
|
+
const subDlFile = join(tempDir, `dl-${i}-${sp}.tmp`);
|
|
1722
|
+
subDlFiles.push(subDlFile);
|
|
1723
|
+
sbtArgs.push(
|
|
1724
|
+
`"${sp}/dependencyTree / toFile ${subDlFile} --force"`,
|
|
1725
|
+
);
|
|
1726
|
+
}
|
|
1727
|
+
} else if (useSlashSyntax) {
|
|
1728
|
+
sbtArgs = [
|
|
1729
|
+
`'set ThisBuild / asciiGraphWidth := 800'${sbtExtraArgs} "dependencyTree / toFile ${dlFile} --force"`,
|
|
1730
|
+
];
|
|
1731
|
+
subDlFiles.push(dlFile);
|
|
1732
|
+
} else {
|
|
1733
|
+
sbtArgs = [
|
|
1734
|
+
`'set asciiGraphWidth in ThisBuild := 800'${sbtExtraArgs} "dependencyTree::toFile ${dlFile} --force"`,
|
|
1735
|
+
];
|
|
1736
|
+
subDlFiles.push(dlFile);
|
|
1737
|
+
}
|
|
1738
|
+
pluginFile = addPlugin(basePath, sbtPluginDefinition);
|
|
1588
1739
|
}
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
// does not emit ANSI colour codes into the dependency tree output.
|
|
1594
|
-
sbtArgs = ["-batch", "-no-colors", ...sbtArgs];
|
|
1595
|
-
console.log(
|
|
1596
|
-
"Executing",
|
|
1597
|
-
SBT_CMD,
|
|
1598
|
-
sbtArgs.join(" "),
|
|
1599
|
-
"in",
|
|
1600
|
-
basePath,
|
|
1601
|
-
"using plugins",
|
|
1602
|
-
tempSbtgDir,
|
|
1603
|
-
);
|
|
1604
|
-
// Note that the command has to be invoked with `shell: true` to properly execut sbt
|
|
1605
|
-
const result = safeSpawnSync(SBT_CMD, sbtArgs, {
|
|
1606
|
-
cwd: basePath,
|
|
1607
|
-
shell: true,
|
|
1608
|
-
env,
|
|
1609
|
-
});
|
|
1610
|
-
if (result.status !== 0 || result.error) {
|
|
1611
|
-
console.error(result.stdout, result.stderr);
|
|
1740
|
+
// Run sbt in non-interactive batch mode so it never blocks waiting for
|
|
1741
|
+
// input on stdin (which can otherwise appear as an indefinite hang) and
|
|
1742
|
+
// does not emit ANSI colour codes into the dependency tree output.
|
|
1743
|
+
sbtArgs = ["-batch", "-no-colors", ...sbtArgs];
|
|
1612
1744
|
console.log(
|
|
1613
|
-
"
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
"
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1745
|
+
"Executing",
|
|
1746
|
+
SBT_CMD,
|
|
1747
|
+
sbtArgs.join(" "),
|
|
1748
|
+
"in",
|
|
1749
|
+
basePath,
|
|
1750
|
+
"using plugins",
|
|
1751
|
+
tempSbtgDir,
|
|
1620
1752
|
);
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1753
|
+
// Note that the command has to be invoked with `shell: true` to properly execut sbt
|
|
1754
|
+
const result = safeSpawnSync(SBT_CMD, sbtArgs, {
|
|
1755
|
+
cwd: basePath,
|
|
1756
|
+
shell: true,
|
|
1757
|
+
env,
|
|
1758
|
+
});
|
|
1759
|
+
if (result.status !== 0 || result.error) {
|
|
1760
|
+
console.error(result.stdout, result.stderr);
|
|
1761
|
+
recordDegradation("jvm.sbt.no-lockfile", {
|
|
1762
|
+
ecosystem: "java",
|
|
1763
|
+
tool: "sbt",
|
|
1764
|
+
impact: "versions",
|
|
1765
|
+
command: `${SBT_CMD} ${sbtArgs.join(" ")}`,
|
|
1766
|
+
exitCode:
|
|
1767
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1768
|
+
detail:
|
|
1769
|
+
"The sbt dependencyTree command did not succeed, so the declared sbt dependencies are the only evidence available.",
|
|
1770
|
+
});
|
|
1771
|
+
console.log(
|
|
1772
|
+
"1. Check if scala and sbt is installed and available in PATH. Only scala 2.10 + sbt 0.13.6+ and 2.12 + sbt 1.0+ is supported for now.",
|
|
1773
|
+
);
|
|
1774
|
+
console.log(
|
|
1775
|
+
"2. Check if the plugin net.virtual-void:sbt-dependency-graph 0.10.0-RC1 can be used in the environment",
|
|
1776
|
+
);
|
|
1777
|
+
console.log(
|
|
1778
|
+
"3. Consider creating a lockfile using sbt-dependency-lock plugin. See https://github.com/stringbean/sbt-dependency-lock",
|
|
1779
|
+
);
|
|
1780
|
+
deferFailOnError(options, {
|
|
1781
|
+
ecosystem: "java",
|
|
1782
|
+
tool: "sbt",
|
|
1783
|
+
exitCode:
|
|
1784
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1785
|
+
command: `${SBT_CMD} ${sbtArgs.join(" ")}`,
|
|
1786
|
+
detail: "the sbt dependencyTree command failed",
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
if (!standalonePluginFile) {
|
|
1790
|
+
cleanupPlugin(basePath, pluginFile);
|
|
1791
|
+
}
|
|
1792
|
+
const sbtSubprojectRoots = [];
|
|
1793
|
+
for (const subDlFile of subDlFiles) {
|
|
1794
|
+
if (safeExistsSync(subDlFile)) {
|
|
1795
|
+
const retMap = await parseSbtTree(subDlFile);
|
|
1796
|
+
if (retMap.pkgList?.length) {
|
|
1797
|
+
const tmpParentComponent = retMap.pkgList.splice(0, 1)[0];
|
|
1798
|
+
tmpParentComponent.type = "application";
|
|
1799
|
+
pkgList = pkgList.concat(retMap.pkgList);
|
|
1800
|
+
pkgList.push(tmpParentComponent);
|
|
1801
|
+
sbtSubprojectRoots.push(tmpParentComponent);
|
|
1802
|
+
}
|
|
1803
|
+
if (retMap.dependenciesList) {
|
|
1804
|
+
dependencies = mergeDependencies(
|
|
1805
|
+
dependencies,
|
|
1806
|
+
retMap.dependenciesList,
|
|
1807
|
+
parentComponent,
|
|
1808
|
+
);
|
|
1809
|
+
}
|
|
1810
|
+
} else {
|
|
1811
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
1812
|
+
console.log(`sbt dependencyList did not yield ${subDlFile}`);
|
|
1813
|
+
}
|
|
1814
|
+
if (options.failOnError) {
|
|
1815
|
+
deferFailOnError(options, {
|
|
1816
|
+
ecosystem: "java",
|
|
1817
|
+
tool: "sbt",
|
|
1818
|
+
detail: "the sbt dependencyList output was missing",
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1647
1821
|
}
|
|
1648
|
-
|
|
1822
|
+
}
|
|
1823
|
+
// Construct a synthetic root component from build.sbt name/org/version
|
|
1824
|
+
// that aggregates all subproject roots as its direct dependencies.
|
|
1825
|
+
const sbtRoot = parseSbtRootProject(basePath);
|
|
1826
|
+
if (sbtRoot && sbtSubprojectRoots.length > 0) {
|
|
1827
|
+
const rootPurl = build({
|
|
1828
|
+
type: "maven",
|
|
1829
|
+
namespace: sbtRoot.group || null,
|
|
1830
|
+
name: sbtRoot.name,
|
|
1831
|
+
version: sbtRoot.version || null,
|
|
1832
|
+
qualifiers: { type: "jar" } || null,
|
|
1833
|
+
});
|
|
1834
|
+
const rootComponent = {
|
|
1835
|
+
group: sbtRoot.group,
|
|
1836
|
+
name: sbtRoot.name,
|
|
1837
|
+
version: sbtRoot.version,
|
|
1838
|
+
type: "application",
|
|
1839
|
+
purl: rootPurl,
|
|
1840
|
+
"bom-ref": decodeURIComponent(rootPurl),
|
|
1841
|
+
};
|
|
1842
|
+
parentComponent = rootComponent;
|
|
1843
|
+
dependencies.push({
|
|
1844
|
+
ref: decodeURIComponent(rootPurl),
|
|
1845
|
+
dependsOn: sbtSubprojectRoots
|
|
1846
|
+
.map((c) => c["bom-ref"])
|
|
1847
|
+
.filter(Boolean),
|
|
1848
|
+
});
|
|
1849
|
+
} else if (sbtSubprojectRoots.length > 0 && !parentComponent) {
|
|
1850
|
+
parentComponent = sbtSubprojectRoots[0];
|
|
1649
1851
|
}
|
|
1650
1852
|
}
|
|
1651
|
-
// Construct a synthetic root component from build.sbt name/org/version
|
|
1652
|
-
// that aggregates all subproject roots as its direct dependencies.
|
|
1653
|
-
const sbtRoot = parseSbtRootProject(basePath);
|
|
1654
|
-
if (sbtRoot && sbtSubprojectRoots.length > 0) {
|
|
1655
|
-
const rootPurl = build({
|
|
1656
|
-
type: "maven",
|
|
1657
|
-
namespace: sbtRoot.group || null,
|
|
1658
|
-
name: sbtRoot.name,
|
|
1659
|
-
version: sbtRoot.version || null,
|
|
1660
|
-
qualifiers: { type: "jar" } || null,
|
|
1661
|
-
});
|
|
1662
|
-
const rootComponent = {
|
|
1663
|
-
group: sbtRoot.group,
|
|
1664
|
-
name: sbtRoot.name,
|
|
1665
|
-
version: sbtRoot.version,
|
|
1666
|
-
type: "application",
|
|
1667
|
-
purl: rootPurl,
|
|
1668
|
-
"bom-ref": decodeURIComponent(rootPurl),
|
|
1669
|
-
};
|
|
1670
|
-
parentComponent = rootComponent;
|
|
1671
|
-
dependencies.push({
|
|
1672
|
-
ref: decodeURIComponent(rootPurl),
|
|
1673
|
-
dependsOn: sbtSubprojectRoots
|
|
1674
|
-
.map((c) => c["bom-ref"])
|
|
1675
|
-
.filter(Boolean),
|
|
1676
|
-
});
|
|
1677
|
-
} else if (sbtSubprojectRoots.length > 0 && !parentComponent) {
|
|
1678
|
-
parentComponent = sbtSubprojectRoots[0];
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
1853
|
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1854
|
+
// Cleanup
|
|
1855
|
+
safeUnlinkSync(tempSbtPlugins);
|
|
1856
|
+
} // else
|
|
1685
1857
|
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
}
|
|
1689
|
-
// Should we attempt to resolve class names
|
|
1690
|
-
if (options.resolveClass || options.deep) {
|
|
1691
|
-
const tmpjarNSMapping = await collectJarNS(tempCacheDir);
|
|
1692
|
-
if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
|
|
1693
|
-
jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
|
|
1858
|
+
if (DEBUG_MODE) {
|
|
1859
|
+
console.log(`Found ${pkgList.length} packages`);
|
|
1694
1860
|
}
|
|
1695
|
-
//
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
)
|
|
1707
|
-
|
|
1708
|
-
|
|
1861
|
+
// Should we attempt to resolve class names
|
|
1862
|
+
if (options.resolveClass || options.deep) {
|
|
1863
|
+
const tmpjarNSMapping = await collectJarNS(tempCacheDir);
|
|
1864
|
+
if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
|
|
1865
|
+
jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
|
|
1866
|
+
}
|
|
1867
|
+
// sbt can store jars in the target directory
|
|
1868
|
+
const jarNSData = await createJarBom(path, options);
|
|
1869
|
+
if (jarNSData?.bomJson?.components) {
|
|
1870
|
+
pkgList = pkgList.concat(jarNSData?.bomJson?.components);
|
|
1871
|
+
const targetJarNSMapping = {};
|
|
1872
|
+
for (const p of jarNSData.bomJson.components) {
|
|
1873
|
+
if (!p?.purl || !p?.properties?.length) {
|
|
1874
|
+
continue;
|
|
1875
|
+
}
|
|
1876
|
+
const nsProp = p.properties.filter(
|
|
1877
|
+
(prop) => prop.name === "internal:Namespaces",
|
|
1878
|
+
);
|
|
1879
|
+
if (nsProp.length) {
|
|
1880
|
+
targetJarNSMapping[p.purl] = nsProp[0].value;
|
|
1881
|
+
}
|
|
1709
1882
|
}
|
|
1883
|
+
jarNSMapping = { ...jarNSMapping, ...targetJarNSMapping };
|
|
1710
1884
|
}
|
|
1711
|
-
jarNSMapping = { ...jarNSMapping, ...targetJarNSMapping };
|
|
1712
1885
|
}
|
|
1713
1886
|
}
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
});
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
|
-
if (
|
|
1723
|
-
millFiles?.length &&
|
|
1724
|
-
isPackageManagerAllowed(
|
|
1725
|
-
"mill",
|
|
1726
|
-
["bazel", "sbt", "gradle", "maven"],
|
|
1727
|
-
options,
|
|
1728
|
-
)
|
|
1729
|
-
) {
|
|
1730
|
-
const millRootPath = dirname(millFiles[0]);
|
|
1731
|
-
parentComponent = createDefaultParentComponent(
|
|
1732
|
-
millRootPath,
|
|
1733
|
-
"maven",
|
|
1734
|
-
options,
|
|
1735
|
-
);
|
|
1736
|
-
const millCmd = getMillCommand(millRootPath);
|
|
1737
|
-
const millCommonArgs = [
|
|
1738
|
-
"--color",
|
|
1739
|
-
"false",
|
|
1740
|
-
"--disable-callgraph",
|
|
1741
|
-
"--disable-prompt",
|
|
1742
|
-
"--keep-going",
|
|
1743
|
-
"--silent",
|
|
1744
|
-
];
|
|
1745
|
-
if (!["true", "1"].includes(readEnvironmentVariable("MILL_USE_SERVER"))) {
|
|
1746
|
-
millCommonArgs.unshift("--no-server");
|
|
1747
|
-
}
|
|
1748
|
-
const millArgs = [...millCommonArgs, "__.ivyDepsTree"];
|
|
1749
|
-
if (DEBUG_MODE) {
|
|
1750
|
-
console.log("Executing", millCmd, "in", millRootPath);
|
|
1751
|
-
}
|
|
1752
|
-
let sresult = safeSpawnSync(millCmd, millArgs, {
|
|
1753
|
-
cwd: millRootPath,
|
|
1754
|
-
shell: isWin,
|
|
1755
|
-
maxBuffer: MAX_BUFFER * 10,
|
|
1756
|
-
});
|
|
1757
|
-
if (sresult.status !== 0 || sresult.error) {
|
|
1758
|
-
if (options.failOnError || DEBUG_MODE) {
|
|
1759
|
-
console.error(sresult.stdout, sresult.stderr);
|
|
1760
|
-
}
|
|
1761
|
-
options.failOnError && process.exit(1);
|
|
1887
|
+
if (tempCacheDir?.startsWith(getTmpDir())) {
|
|
1888
|
+
safeRmSync(tempCacheDir, {
|
|
1889
|
+
recursive: true,
|
|
1890
|
+
force: true,
|
|
1891
|
+
});
|
|
1762
1892
|
}
|
|
1763
|
-
|
|
1764
|
-
if (
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1893
|
+
|
|
1894
|
+
if (
|
|
1895
|
+
millFiles?.length &&
|
|
1896
|
+
isPackageManagerAllowed(
|
|
1897
|
+
"mill",
|
|
1898
|
+
["bazel", "sbt", "gradle", "maven"],
|
|
1899
|
+
options,
|
|
1900
|
+
)
|
|
1901
|
+
) {
|
|
1902
|
+
const millRootPath = dirname(millFiles[0]);
|
|
1903
|
+
parentComponent = createDefaultParentComponent(
|
|
1770
1904
|
millRootPath,
|
|
1905
|
+
"maven",
|
|
1906
|
+
options,
|
|
1771
1907
|
);
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
const sstdout = sresult.stdout;
|
|
1784
|
-
if (sstdout) {
|
|
1785
|
-
parentComponent.components = [];
|
|
1786
|
-
const modules = sstdout
|
|
1787
|
-
.trim()
|
|
1788
|
-
.split("\n")
|
|
1789
|
-
.map((a) => a.substring(0, a.lastIndexOf(".")))
|
|
1790
|
-
.filter((a) =>
|
|
1791
|
-
["true", "1"].includes(readEnvironmentVariable("MILL_EXCLUDE_TEST"))
|
|
1792
|
-
? !a.endsWith(".test")
|
|
1793
|
-
: true,
|
|
1794
|
-
);
|
|
1795
|
-
const moduleBomRefs = [];
|
|
1796
|
-
const packages = new Map();
|
|
1797
|
-
const relations = new Map();
|
|
1798
|
-
relations.set(parentComponent["bom-ref"], []);
|
|
1799
|
-
for (const module of modules) {
|
|
1800
|
-
moduleBomRefs.push(
|
|
1801
|
-
parseMillDependency(module, packages, relations, millRootPath),
|
|
1802
|
-
);
|
|
1908
|
+
const millCmd = getMillCommand(millRootPath);
|
|
1909
|
+
const millCommonArgs = [
|
|
1910
|
+
"--color",
|
|
1911
|
+
"false",
|
|
1912
|
+
"--disable-callgraph",
|
|
1913
|
+
"--disable-prompt",
|
|
1914
|
+
"--keep-going",
|
|
1915
|
+
"--silent",
|
|
1916
|
+
];
|
|
1917
|
+
if (!["true", "1"].includes(readEnvironmentVariable("MILL_USE_SERVER"))) {
|
|
1918
|
+
millCommonArgs.unshift("--no-server");
|
|
1803
1919
|
}
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
packages.delete(module);
|
|
1920
|
+
const millArgs = [...millCommonArgs, "__.ivyDepsTree"];
|
|
1921
|
+
if (DEBUG_MODE) {
|
|
1922
|
+
console.log("Executing", millCmd, "in", millRootPath);
|
|
1808
1923
|
}
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1924
|
+
let sresult = safeSpawnSync(millCmd, millArgs, {
|
|
1925
|
+
cwd: millRootPath,
|
|
1926
|
+
shell: isWin,
|
|
1927
|
+
maxBuffer: MAX_BUFFER * 10,
|
|
1928
|
+
});
|
|
1929
|
+
if (sresult.status !== 0 || sresult.error) {
|
|
1930
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
1931
|
+
console.error(sresult.stdout, sresult.stderr);
|
|
1932
|
+
}
|
|
1933
|
+
deferFailOnError(options, {
|
|
1934
|
+
ecosystem: "java",
|
|
1935
|
+
tool: "mill",
|
|
1936
|
+
exitCode:
|
|
1937
|
+
typeof sresult.status === "number" ? sresult.status : undefined,
|
|
1938
|
+
detail: "the mill ivyDepsTree command failed",
|
|
1814
1939
|
});
|
|
1815
1940
|
}
|
|
1941
|
+
const millResolveArgs = [...millCommonArgs, "resolve", "__.ivyDepsTree"];
|
|
1816
1942
|
if (DEBUG_MODE) {
|
|
1817
1943
|
console.log(
|
|
1818
|
-
|
|
1944
|
+
"Executing",
|
|
1945
|
+
millCmd,
|
|
1946
|
+
millResolveArgs.join(" "),
|
|
1947
|
+
"in",
|
|
1948
|
+
millRootPath,
|
|
1819
1949
|
);
|
|
1820
1950
|
}
|
|
1821
|
-
|
|
1822
|
-
dependencies = mergeDependencies(
|
|
1823
|
-
dependencies,
|
|
1824
|
-
newDependencies,
|
|
1825
|
-
parentComponent,
|
|
1826
|
-
);
|
|
1827
|
-
}
|
|
1828
|
-
if (
|
|
1829
|
-
(!readEnvironmentVariable("MILL_SHUTDOWN_SERVER") &&
|
|
1830
|
-
["true", "1"].includes(readEnvironmentVariable("MILL_USE_SERVER"))) ||
|
|
1831
|
-
["true", "1"].includes(readEnvironmentVariable("MILL_SHUTDOWN_SERVER"))
|
|
1832
|
-
) {
|
|
1833
|
-
if (DEBUG_MODE) {
|
|
1834
|
-
console.log("Shutting down mill server...");
|
|
1835
|
-
}
|
|
1836
|
-
const sresult = safeSpawnSync(millCmd, ["shutdown"], {
|
|
1951
|
+
sresult = safeSpawnSync(millCmd, millResolveArgs, {
|
|
1837
1952
|
cwd: millRootPath,
|
|
1838
1953
|
shell: isWin,
|
|
1839
1954
|
});
|
|
@@ -1841,11 +1956,97 @@ export async function createJavaBom(path, options) {
|
|
|
1841
1956
|
if (options.failOnError || DEBUG_MODE) {
|
|
1842
1957
|
console.error(sresult.stdout, sresult.stderr);
|
|
1843
1958
|
}
|
|
1844
|
-
options
|
|
1959
|
+
deferFailOnError(options, {
|
|
1960
|
+
ecosystem: "java",
|
|
1961
|
+
tool: "mill",
|
|
1962
|
+
exitCode:
|
|
1963
|
+
typeof sresult.status === "number" ? sresult.status : undefined,
|
|
1964
|
+
detail: "the mill resolve command failed",
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
const sstdout = sresult.stdout;
|
|
1968
|
+
if (sstdout) {
|
|
1969
|
+
parentComponent.components = [];
|
|
1970
|
+
const modules = sstdout
|
|
1971
|
+
.trim()
|
|
1972
|
+
.split("\n")
|
|
1973
|
+
.map((a) => a.substring(0, a.lastIndexOf(".")))
|
|
1974
|
+
.filter((a) =>
|
|
1975
|
+
["true", "1"].includes(readEnvironmentVariable("MILL_EXCLUDE_TEST"))
|
|
1976
|
+
? !a.endsWith(".test")
|
|
1977
|
+
: true,
|
|
1978
|
+
);
|
|
1979
|
+
const moduleBomRefs = [];
|
|
1980
|
+
const packages = new Map();
|
|
1981
|
+
const relations = new Map();
|
|
1982
|
+
relations.set(parentComponent["bom-ref"], []);
|
|
1983
|
+
for (const module of modules) {
|
|
1984
|
+
moduleBomRefs.push(
|
|
1985
|
+
parseMillDependency(module, packages, relations, millRootPath),
|
|
1986
|
+
);
|
|
1987
|
+
}
|
|
1988
|
+
for (const module of moduleBomRefs) {
|
|
1989
|
+
parentComponent.components.push(packages.get(module));
|
|
1990
|
+
relations.get(parentComponent["bom-ref"]).push(module);
|
|
1991
|
+
packages.delete(module);
|
|
1992
|
+
}
|
|
1993
|
+
const newDependencies = [];
|
|
1994
|
+
for (const [ref, dependsOn] of relations.entries()) {
|
|
1995
|
+
newDependencies.push({
|
|
1996
|
+
ref,
|
|
1997
|
+
dependsOn,
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
if (DEBUG_MODE) {
|
|
2001
|
+
console.log(
|
|
2002
|
+
`Obtained ${packages.size} components and ${relations.size} dependencies from mill.`,
|
|
2003
|
+
);
|
|
2004
|
+
}
|
|
2005
|
+
pkgList = pkgList.concat(...packages.values());
|
|
2006
|
+
dependencies = mergeDependencies(
|
|
2007
|
+
dependencies,
|
|
2008
|
+
newDependencies,
|
|
2009
|
+
parentComponent,
|
|
2010
|
+
);
|
|
1845
2011
|
}
|
|
2012
|
+
if (
|
|
2013
|
+
(!readEnvironmentVariable("MILL_SHUTDOWN_SERVER") &&
|
|
2014
|
+
["true", "1"].includes(readEnvironmentVariable("MILL_USE_SERVER"))) ||
|
|
2015
|
+
["true", "1"].includes(readEnvironmentVariable("MILL_SHUTDOWN_SERVER"))
|
|
2016
|
+
) {
|
|
2017
|
+
if (DEBUG_MODE) {
|
|
2018
|
+
console.log("Shutting down mill server...");
|
|
2019
|
+
}
|
|
2020
|
+
const sresult = safeSpawnSync(millCmd, ["shutdown"], {
|
|
2021
|
+
cwd: millRootPath,
|
|
2022
|
+
shell: isWin,
|
|
2023
|
+
});
|
|
2024
|
+
if (sresult.status !== 0 || sresult.error) {
|
|
2025
|
+
if (options.failOnError || DEBUG_MODE) {
|
|
2026
|
+
console.error(sresult.stdout, sresult.stderr);
|
|
2027
|
+
}
|
|
2028
|
+
deferFailOnError(options, {
|
|
2029
|
+
ecosystem: "java",
|
|
2030
|
+
tool: "mill",
|
|
2031
|
+
exitCode:
|
|
2032
|
+
typeof sresult.status === "number" ? sresult.status : undefined,
|
|
2033
|
+
detail: "shutting down the mill server failed",
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
} catch (err) {
|
|
2039
|
+
if (!isDeferredFailOnError(err)) {
|
|
2040
|
+
throw err;
|
|
1846
2041
|
}
|
|
2042
|
+
// The deferred fail-on-error abort kept everything collected so far;
|
|
2043
|
+
// the incomplete-result fallbacks stayed skipped.
|
|
2044
|
+
thoughtLog(
|
|
2045
|
+
"Completing the java BOM with the components collected before the deferred failure.",
|
|
2046
|
+
);
|
|
1847
2047
|
}
|
|
1848
|
-
|
|
2048
|
+
// Deduplication and metadata belong to whatever was collected, so they run
|
|
2049
|
+
// on the deferred path too.
|
|
1849
2050
|
pkgList = trimComponents(pkgList);
|
|
1850
2051
|
pkgList = await getMvnMetadata(pkgList, jarNSMapping, options.deep);
|
|
1851
2052
|
return buildBomNSData(options, pkgList, "maven", {
|