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