@cdxgen/cdxgen 13.2.0 → 13.4.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 +33 -14
- package/bin/cdxgen.js +310 -138
- package/bin/evinse.js +22 -0
- package/bin/repl.js +86 -0
- package/bin/sign.js +119 -15
- package/bin/tracebom.js +69 -5
- package/bin/validate.js +107 -19
- package/bin/verify.js +172 -63
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +85 -0
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +38 -6
- package/lib/cli/bomAssembly.js +105 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +156 -58
- package/lib/cli/jsBom.js +99 -53
- package/lib/cli/jvmBom.js +708 -147
- package/lib/cli/managedBom.js +415 -224
- package/lib/cli/nativeBom.js +264 -44
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +22 -15
- package/lib/core/fs.js +176 -7
- package/lib/core/httpClient.js +159 -24
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/runState.js +39 -0
- package/lib/core/toml.js +72 -0
- package/lib/ecosystems/asarutils.js +4 -2
- package/lib/ecosystems/bunutils.js +274 -108
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/caxa.js +434 -2
- package/lib/ecosystems/cmakeResolver.js +76 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +399 -51
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1983 -793
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +356 -37
- package/lib/ecosystems/jsEvidence.js +104 -44
- package/lib/ecosystems/kosi.js +31 -7
- package/lib/ecosystems/npmutils.js +174 -9
- package/lib/ecosystems/parsers-dotnet.js +616 -159
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +371 -70
- package/lib/ecosystems/parsers-js.js +263 -76
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +929 -216
- package/lib/ecosystems/parsers-python.js +167 -24
- package/lib/ecosystems/parsers-rust.js +28 -22
- package/lib/ecosystems/parsers-terraform.js +1573 -19
- package/lib/ecosystems/pythonutils.js +102 -6
- package/lib/ecosystems/remote/huggingface.js +3 -0
- package/lib/ecosystems/rubyutils.js +1492 -182
- package/lib/ecosystems/rusi.js +54 -10
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +342 -0
- package/lib/ecosystems/scalasem.js +2618 -0
- package/lib/ecosystems/terraformRegistry.js +288 -0
- package/lib/ecosystems/utils.js +2 -0
- package/lib/ecosystems/vendoredCode.js +156 -0
- package/lib/evinser/evinser.js +1008 -191
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +1101 -222
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +703 -43
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cdxrs.js +103 -12
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +798 -57
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +90 -6
- package/lib/inventory/evidenceUtils.js +166 -113
- package/lib/inventory/fetchBatch.js +690 -55
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/protobom.js +8 -1
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +45 -27
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +295 -25
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +467 -46
- package/lib/managers/oci.js +63 -3
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +466 -0
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/strictJson.js +322 -0
- package/lib/parsers/terraformConfig.js +1073 -0
- package/lib/parsers/terraformSource.js +652 -0
- package/lib/server/server.js +23 -0
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/postgen.js +83 -16
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +64 -11
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +2 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +23 -23
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- 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 +24 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +7 -4
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +3 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +9 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +13 -8
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/pluginRun.d.ts +97 -0
- package/types/lib/core/pluginRun.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +36 -0
- package/types/lib/core/processTree.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/runState.d.ts +29 -0
- package/types/lib/core/runState.d.ts.map +1 -0
- package/types/lib/core/toml.d.ts +20 -0
- package/types/lib/core/toml.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +12 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +28 -3
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
- package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +17 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +9 -3
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +62 -13
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +70 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +13 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +57 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +54 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +16 -5
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +70 -4
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +53 -3
- 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/parsers-terraform.d.ts +38 -21
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +27 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +164 -3
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/sbtutils.d.ts +88 -2
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
- package/types/lib/ecosystems/scalasem.d.ts +311 -0
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +2 -2
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +132 -15
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +182 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +219 -5
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +3 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
- package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +2 -1
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +59 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +121 -9
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts +53 -7
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +10 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +19 -4
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +83 -14
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
- package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/parentSelection.d.ts +34 -0
- package/types/lib/inventory/parentSelection.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts +80 -0
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +9 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +59 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +42 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +17 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeCache.d.ts +16 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +77 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/cmakePresets.d.ts +78 -0
- package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
- package/types/lib/parsers/compileCommands.d.ts +115 -0
- package/types/lib/parsers/compileCommands.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/parsers/strictJson.d.ts +46 -0
- package/types/lib/parsers/strictJson.d.ts.map +1 -0
- package/types/lib/parsers/terraformConfig.d.ts +70 -0
- package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
- package/types/lib/parsers/terraformSource.d.ts +66 -0
- package/types/lib/parsers/terraformSource.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +18 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
package/lib/cli/jvmBom.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
@@ -7,7 +7,11 @@ import process from "node:process";
|
|
|
7
7
|
import { build } from "@cdxgen/cdx-purl";
|
|
8
8
|
import { gte, lte } from "semver";
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
DEBUG_MODE,
|
|
12
|
+
isSecureMode,
|
|
13
|
+
readEnvironmentVariable,
|
|
14
|
+
} from "../core/activity.js";
|
|
11
15
|
import { commandOutputText, recordDegradation } from "../core/buildLedger.js";
|
|
12
16
|
import {
|
|
13
17
|
deferFailOnError,
|
|
@@ -41,9 +45,11 @@ import {
|
|
|
41
45
|
buildObjectForGradleModule,
|
|
42
46
|
collectGradleDependencies,
|
|
43
47
|
executeParallelGradleProperties,
|
|
48
|
+
findMillDaemons,
|
|
44
49
|
getGradleCommand,
|
|
45
50
|
getMavenCommand,
|
|
46
51
|
getMillCommand,
|
|
52
|
+
getMillVersion,
|
|
47
53
|
hasGradleInvocationFailure,
|
|
48
54
|
parseGradleDep,
|
|
49
55
|
parseGradleInfoLogsForUrls,
|
|
@@ -52,29 +58,41 @@ import {
|
|
|
52
58
|
parseGradleVersionCatalog,
|
|
53
59
|
recordGradleInvocationFailure,
|
|
54
60
|
splitOutputByGradleProjects,
|
|
61
|
+
stopMillDaemons,
|
|
55
62
|
} from "../ecosystems/gradleutils.js";
|
|
56
63
|
import {
|
|
57
64
|
parseModuleBazel,
|
|
58
65
|
parseModuleBazelLock,
|
|
59
66
|
} from "../ecosystems/parsers-bazel.js";
|
|
60
67
|
import {
|
|
68
|
+
MAVEN_MODULE_TREE_STEM,
|
|
61
69
|
parseBazelActionGraph,
|
|
62
70
|
parseBazelSkyframe,
|
|
63
71
|
parseMavenTree,
|
|
64
72
|
parseMavenTreeJson,
|
|
65
73
|
parseMillDependency,
|
|
66
74
|
parsePom,
|
|
75
|
+
readMavenModuleTrees,
|
|
67
76
|
} from "../ecosystems/parsers-jvm.js";
|
|
68
77
|
import {
|
|
69
78
|
addPlugin,
|
|
70
79
|
cleanupPlugin,
|
|
71
80
|
determineSbtVersion,
|
|
72
81
|
discoverSbtProjects,
|
|
82
|
+
isSbt2Version,
|
|
73
83
|
parseSbtLock,
|
|
74
84
|
parseSbtProjects,
|
|
75
85
|
parseSbtRootProject,
|
|
76
86
|
parseSbtTree,
|
|
87
|
+
sbt2DependencyTreeCommand,
|
|
88
|
+
sbtSpawnSync,
|
|
77
89
|
} from "../ecosystems/sbtutils.js";
|
|
90
|
+
import { collectScalaCliComponents } from "../ecosystems/scalacliutils.js";
|
|
91
|
+
import { collectScalaJsNpmComponents } from "../ecosystems/scalasem.js";
|
|
92
|
+
import {
|
|
93
|
+
isBuildToolRateLimited,
|
|
94
|
+
noteBuildToolRateLimit,
|
|
95
|
+
} from "../inventory/buildToolRateLimit.js";
|
|
78
96
|
import {
|
|
79
97
|
collectJarNS,
|
|
80
98
|
collectMvnDependencies,
|
|
@@ -85,8 +103,15 @@ import {
|
|
|
85
103
|
attachIdentityTools,
|
|
86
104
|
extractToolRefs,
|
|
87
105
|
} from "../inventory/evidenceUtils.js";
|
|
106
|
+
import {
|
|
107
|
+
coursierCacheDir,
|
|
108
|
+
findLocalMavenArtifact,
|
|
109
|
+
locateInCoursierCache,
|
|
110
|
+
projectSettingsArgs,
|
|
111
|
+
} from "../inventory/jvmLocalRepos.js";
|
|
88
112
|
import { readGradleWrapperVersion } from "../inventory/jvmToolEnv.js";
|
|
89
113
|
import { applyPurl, mavenPurl } from "../inventory/purl.js";
|
|
114
|
+
import { publishedArtifactId } from "../inventory/scalaCoords.js";
|
|
90
115
|
import {
|
|
91
116
|
buildBomNSData,
|
|
92
117
|
createDefaultParentComponent,
|
|
@@ -136,6 +161,127 @@ export const GRADLE_INIT_SCRIPT = resolve(
|
|
|
136
161
|
export const SBT_CACHE_DIR =
|
|
137
162
|
readEnvironmentVariable("SBT_CACHE_DIR") || join(homedir(), ".ivy2", "cache");
|
|
138
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Collect the jars in the caches sbt uses: the Coursier cache, which sbt 1.3
|
|
166
|
+
* and later resolve into, and the Ivy cache named by SBT_CACHE_DIR, which
|
|
167
|
+
* older builds used.
|
|
168
|
+
*
|
|
169
|
+
* @returns {Promise<Object>} Jar namespace mapping, as collectJarNS returns.
|
|
170
|
+
*/
|
|
171
|
+
async function collectSbtCacheDependencies() {
|
|
172
|
+
let nsMapping = {};
|
|
173
|
+
for (const cacheDir of [coursierCacheDir(), SBT_CACHE_DIR]) {
|
|
174
|
+
if (cacheDir && safeExistsSync(cacheDir)) {
|
|
175
|
+
nsMapping = { ...nsMapping, ...(await collectJarNS(cacheDir)) };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return nsMapping;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The jar of a resolved sbt dependency in the user's own caches: the Coursier
|
|
183
|
+
* cache sbt 1.3 and later resolve into, the other local Maven caches, and the
|
|
184
|
+
* Ivy cache older builds used.
|
|
185
|
+
*
|
|
186
|
+
* @param {Object} pkg Package with group, name and version.
|
|
187
|
+
* @returns {string|undefined} Path of the jar.
|
|
188
|
+
*/
|
|
189
|
+
function localSbtDependencyJar(pkg) {
|
|
190
|
+
// Scala components are named without their binary suffix; the jar has it.
|
|
191
|
+
const name = publishedArtifactId(pkg);
|
|
192
|
+
const coursierDir = locateInCoursierCache(pkg.group, name, pkg.version)?.dir;
|
|
193
|
+
const coursierJar =
|
|
194
|
+
coursierDir && join(coursierDir, `${name}-${pkg.version}.jar`);
|
|
195
|
+
if (coursierJar && safeExistsSync(coursierJar)) {
|
|
196
|
+
return coursierJar;
|
|
197
|
+
}
|
|
198
|
+
const jarPath = findLocalMavenArtifact(pkg.group, name, pkg.version)?.jarPath;
|
|
199
|
+
if (jarPath) {
|
|
200
|
+
return jarPath;
|
|
201
|
+
}
|
|
202
|
+
for (const typeDir of ["jars", "bundles"]) {
|
|
203
|
+
const ivyJar = join(
|
|
204
|
+
SBT_CACHE_DIR,
|
|
205
|
+
pkg.group,
|
|
206
|
+
name,
|
|
207
|
+
typeDir,
|
|
208
|
+
`${name}-${pkg.version}.jar`,
|
|
209
|
+
);
|
|
210
|
+
if (safeExistsSync(ivyJar)) {
|
|
211
|
+
return ivyJar;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Collect the namespaces of the jars of resolved sbt dependencies. sbt has
|
|
219
|
+
* just downloaded them into the user's caches, so each is read from there;
|
|
220
|
+
* the rest of the cache is never walked.
|
|
221
|
+
*
|
|
222
|
+
* @param {Object[]} pkgList Packages from the sbt dependency trees.
|
|
223
|
+
* @param {Object} [known] Jar namespace mapping already collected. Packages
|
|
224
|
+
* it covers are skipped.
|
|
225
|
+
* @returns {Promise<Object>} Jar namespace mapping keyed by package purl.
|
|
226
|
+
*/
|
|
227
|
+
export async function collectSbtDependencyJars(pkgList, known = {}) {
|
|
228
|
+
const nsMapping = {};
|
|
229
|
+
for (const pkg of pkgList || []) {
|
|
230
|
+
// Applications are the build's own projects, which no cache holds.
|
|
231
|
+
if (
|
|
232
|
+
!pkg?.purl ||
|
|
233
|
+
!pkg.group ||
|
|
234
|
+
!pkg.name ||
|
|
235
|
+
!pkg.version ||
|
|
236
|
+
pkg.type === "application" ||
|
|
237
|
+
known[pkg.purl] ||
|
|
238
|
+
nsMapping[pkg.purl]
|
|
239
|
+
) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const jarPath = localSbtDependencyJar(pkg);
|
|
243
|
+
if (!jarPath) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const [entry] = Object.values(await collectJarNS(jarPath));
|
|
247
|
+
if (entry) {
|
|
248
|
+
nsMapping[pkg.purl] = entry;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return nsMapping;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* The purls and bom-refs of a parent component and the module components
|
|
256
|
+
* nested under it.
|
|
257
|
+
*
|
|
258
|
+
* @param {Object} parentComponent Parent component.
|
|
259
|
+
* @returns {Set<string>} Purls and bom-refs.
|
|
260
|
+
*/
|
|
261
|
+
export function ownComponentRefs(parentComponent) {
|
|
262
|
+
const refs = new Set();
|
|
263
|
+
// The Gradle path nests modules that can point back at the parent, so the
|
|
264
|
+
// walk remembers what it has seen.
|
|
265
|
+
const seen = new Set();
|
|
266
|
+
const stack = parentComponent ? [parentComponent] : [];
|
|
267
|
+
while (stack.length) {
|
|
268
|
+
const component = stack.pop();
|
|
269
|
+
if (!component || typeof component !== "object" || seen.has(component)) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
seen.add(component);
|
|
273
|
+
for (const ref of [component.purl, component["bom-ref"]]) {
|
|
274
|
+
if (ref) {
|
|
275
|
+
refs.add(ref);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (Array.isArray(component.components)) {
|
|
279
|
+
stack.push(...component.components);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return refs;
|
|
283
|
+
}
|
|
284
|
+
|
|
139
285
|
/**
|
|
140
286
|
* Function to create bom string for Java jars
|
|
141
287
|
*
|
|
@@ -167,18 +313,20 @@ export async function createJarBom(path, options) {
|
|
|
167
313
|
const parentComponent = createDefaultParentComponent(path, "maven", options);
|
|
168
314
|
if (options.useGradleCache) {
|
|
169
315
|
nsMapping = await collectGradleDependencies(
|
|
170
|
-
getGradleCommand(path, null),
|
|
316
|
+
getGradleCommand(path, null, options),
|
|
171
317
|
path,
|
|
172
318
|
false,
|
|
173
319
|
true,
|
|
174
320
|
);
|
|
175
321
|
} else if (options.useMavenCache) {
|
|
176
322
|
nsMapping = await collectMvnDependencies(
|
|
177
|
-
getMavenCommand(path, null),
|
|
323
|
+
getMavenCommand(path, null, options),
|
|
178
324
|
null,
|
|
179
325
|
false,
|
|
180
326
|
true,
|
|
181
327
|
);
|
|
328
|
+
} else if (options.useSbtCache) {
|
|
329
|
+
nsMapping = await collectSbtCacheDependencies();
|
|
182
330
|
}
|
|
183
331
|
if (path.endsWith(".jar")) {
|
|
184
332
|
jarFiles = [resolve(path)];
|
|
@@ -207,14 +355,17 @@ export async function createJarBom(path, options) {
|
|
|
207
355
|
if (dlist?.length) {
|
|
208
356
|
pkgList = pkgList.concat(dlist);
|
|
209
357
|
}
|
|
210
|
-
if (pkgList.length) {
|
|
211
|
-
pkgList = await getMvnMetadata(pkgList);
|
|
212
|
-
}
|
|
213
358
|
// Clean up
|
|
214
359
|
if (tempDir?.startsWith(getTmpDir())) {
|
|
215
360
|
safeRmSync(tempDir, { recursive: true, force: true });
|
|
216
361
|
}
|
|
217
362
|
}
|
|
363
|
+
// Metadata is resolved once for the whole scan. Inside the loop every jar
|
|
364
|
+
// re-resolved the packages of all the jars before it, and lookups that had
|
|
365
|
+
// failed were sent again each time.
|
|
366
|
+
if (pkgList.length) {
|
|
367
|
+
pkgList = await getMvnMetadata(pkgList);
|
|
368
|
+
}
|
|
218
369
|
pkgList = pkgList.concat(await convertJarNSToPackages(nsMapping));
|
|
219
370
|
return buildBomNSData(options, pkgList, "maven", {
|
|
220
371
|
src: path,
|
|
@@ -222,6 +373,37 @@ export async function createJarBom(path, options) {
|
|
|
222
373
|
});
|
|
223
374
|
}
|
|
224
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Whether a project keeps source directories of its own. Used to keep the
|
|
378
|
+
* root project of an sbt build: a root with sources is usually the
|
|
379
|
+
* application itself and merely aggregates the modules for convenience,
|
|
380
|
+
* while a root without sources is a pure aggregator whose own resolution
|
|
381
|
+
* adds nothing.
|
|
382
|
+
*
|
|
383
|
+
* @param {string} basePath Directory of the sbt build
|
|
384
|
+
* @returns {boolean} True when the build root has main sources
|
|
385
|
+
*/
|
|
386
|
+
function sbtRootHasOwnSources(basePath) {
|
|
387
|
+
for (const language of ["scala", "java", "kotlin"]) {
|
|
388
|
+
if (safeExistsSync(join(basePath, "src", "main", language))) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// A Play application keeps its sources in app/ instead of src/main
|
|
393
|
+
const appDir = join(basePath, "app");
|
|
394
|
+
if (safeExistsSync(appDir)) {
|
|
395
|
+
try {
|
|
396
|
+
const entries = readdirSync(appDir, { withFileTypes: true });
|
|
397
|
+
return entries.some(
|
|
398
|
+
(entry) => entry.isDirectory() || entry.name.endsWith(".scala"),
|
|
399
|
+
);
|
|
400
|
+
} catch (_err) {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
|
|
225
407
|
/**
|
|
226
408
|
* Discover the real sbt project ids for a build by invoking sbt's own
|
|
227
409
|
* `projects` command. This is more reliable than scraping the build files with
|
|
@@ -229,32 +411,46 @@ export async function createJarBom(path, options) {
|
|
|
229
411
|
* (commented-out code, examples, values that merely resemble project defs) that
|
|
230
412
|
* can lead to hangs when those bogus scopes are later passed to `dependencyTree`.
|
|
231
413
|
*
|
|
414
|
+
* The root project is kept when it has sources of its own, since it is then
|
|
415
|
+
* the application rather than a pure aggregator.
|
|
416
|
+
*
|
|
232
417
|
* Falls back to the regex-based {@link discoverSbtProjects} heuristic when the
|
|
233
418
|
* sbt invocation fails or yields nothing useful.
|
|
234
419
|
*
|
|
235
420
|
* @param {string} basePath Directory of the sbt build
|
|
236
421
|
* @param {string} sbtCmd sbt executable
|
|
237
422
|
* @param {Object} env Environment for the spawned process
|
|
423
|
+
* @param {string[]} [launcherArgs] Launcher options placed before the sbt arguments
|
|
238
424
|
* @returns {string[]} List of sbt project ids
|
|
239
425
|
*/
|
|
240
|
-
export function discoverSbtProjectsFromCmd(
|
|
426
|
+
export function discoverSbtProjectsFromCmd(
|
|
427
|
+
basePath,
|
|
428
|
+
sbtCmd,
|
|
429
|
+
env,
|
|
430
|
+
launcherArgs = [],
|
|
431
|
+
) {
|
|
241
432
|
try {
|
|
242
|
-
const result =
|
|
433
|
+
const result = sbtSpawnSync(
|
|
243
434
|
sbtCmd,
|
|
244
|
-
["-batch", "-no-colors", '"projects"'],
|
|
435
|
+
[...launcherArgs, "-batch", "-no-colors", '"projects"'],
|
|
245
436
|
{
|
|
246
437
|
cwd: basePath,
|
|
247
438
|
shell: true,
|
|
439
|
+
// The quotes around the sbt command are for the shell to strip.
|
|
440
|
+
cdxgenRawShellArgs: true,
|
|
248
441
|
env,
|
|
249
442
|
encoding: "utf-8",
|
|
250
443
|
},
|
|
444
|
+
true,
|
|
251
445
|
);
|
|
252
446
|
if (result.status === 0 && !result.error) {
|
|
253
447
|
const { projects, root } = parseSbtProjects(result.stdout || "");
|
|
254
|
-
// Exclude the aggregating root project
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
const subprojects =
|
|
448
|
+
// Exclude the aggregating root project unless it has sources of its
|
|
449
|
+
// own: its dependencyTree is then the application's own dependencies
|
|
450
|
+
// and not a subset of the aggregated subprojects.
|
|
451
|
+
const subprojects = sbtRootHasOwnSources(basePath)
|
|
452
|
+
? projects
|
|
453
|
+
: projects.filter((p) => p !== root);
|
|
258
454
|
if (subprojects.length > 0) {
|
|
259
455
|
if (DEBUG_MODE) {
|
|
260
456
|
console.log(
|
|
@@ -318,6 +514,8 @@ export async function createJavaBom(path, options) {
|
|
|
318
514
|
let tools;
|
|
319
515
|
let possible_misses = false;
|
|
320
516
|
let parallelPropTaskOut = "";
|
|
517
|
+
// Mill daemons already running belong to the user and outlive the scan
|
|
518
|
+
const millDaemonsBefore = findMillDaemons(path);
|
|
321
519
|
try {
|
|
322
520
|
// war/ear mode
|
|
323
521
|
if (path.endsWith(".war") || path.endsWith(".jar")) {
|
|
@@ -327,8 +525,10 @@ export async function createJavaBom(path, options) {
|
|
|
327
525
|
console.log(`Retrieving packages from ${path}`);
|
|
328
526
|
}
|
|
329
527
|
const tempDir = safeMkdtempSync(join(getTmpDir(), "war-deps-"));
|
|
330
|
-
|
|
331
|
-
|
|
528
|
+
// The nested jars are read from the extraction directory and their
|
|
529
|
+
// namespaces attached to each component there; the directory is
|
|
530
|
+
// removed afterwards, so no jar paths are kept for evinse.
|
|
531
|
+
pkgList = await extractJarArchive(path, tempDir);
|
|
332
532
|
if (pkgList.length) {
|
|
333
533
|
pkgList = await getMvnMetadata(pkgList);
|
|
334
534
|
}
|
|
@@ -453,9 +653,21 @@ export async function createJavaBom(path, options) {
|
|
|
453
653
|
}
|
|
454
654
|
}
|
|
455
655
|
const firstPom = pomFiles.length ? pomFiles[0] : undefined;
|
|
456
|
-
let mavenCmd = getMavenCommand(path, path);
|
|
656
|
+
let mavenCmd = getMavenCommand(path, path, options);
|
|
657
|
+
// Pom files whose own tree a recursive run of their aggregator already
|
|
658
|
+
// returned. Each is skipped when its turn comes, so a reactor is run
|
|
659
|
+
// once from its aggregator instead of once per member.
|
|
660
|
+
const coveredPomFiles = new Set();
|
|
457
661
|
for (const f of pomFiles) {
|
|
458
662
|
const basePath = dirname(f);
|
|
663
|
+
if (coveredPomFiles.has(f)) {
|
|
664
|
+
if (DEBUG_MODE) {
|
|
665
|
+
console.log(
|
|
666
|
+
`Skipped ${basePath}: the recursive dependency:tree run of its aggregator already resolved it.`,
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
459
671
|
if (
|
|
460
672
|
isQuarkus &&
|
|
461
673
|
!options.deep &&
|
|
@@ -466,14 +678,39 @@ export async function createJavaBom(path, options) {
|
|
|
466
678
|
}
|
|
467
679
|
continue;
|
|
468
680
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
681
|
+
if (isBuildToolRateLimited("maven")) {
|
|
682
|
+
// Every further Maven run in this scan would pay the same back-off
|
|
683
|
+
// for the same empty answer, so Maven is not asked again. The
|
|
684
|
+
// direct dependencies the pom declares are kept, as for a pom whose
|
|
685
|
+
// run failed.
|
|
686
|
+
if (DEBUG_MODE) {
|
|
687
|
+
console.log(
|
|
688
|
+
`Parsing ${f} without Maven: the repository rate limited Maven in this scan.`,
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
const dlist = parsePom(f)?.dependencies || [];
|
|
692
|
+
if (dlist.length) {
|
|
693
|
+
pkgList = pkgList.concat(dlist);
|
|
694
|
+
}
|
|
695
|
+
continue;
|
|
474
696
|
}
|
|
475
697
|
if (mavenCmd?.endsWith("mvn")) {
|
|
476
|
-
mavenCmd = getMavenCommand(basePath, path);
|
|
698
|
+
mavenCmd = getMavenCommand(basePath, path, options);
|
|
699
|
+
}
|
|
700
|
+
// A settings.xml beside the pom is passed as global settings, so the
|
|
701
|
+
// user's own settings.xml, with its mirrors and credentials, still
|
|
702
|
+
// applies on top of it.
|
|
703
|
+
const projectSettings = projectSettingsArgs(basePath, {
|
|
704
|
+
secureMode: isSecureMode,
|
|
705
|
+
});
|
|
706
|
+
if (projectSettings.args.length) {
|
|
707
|
+
console.log(
|
|
708
|
+
`Passing ${projectSettings.settingsFile} to Maven as global settings (-gs), so your own settings.xml still applies. Name a settings file in MVN_ARGS to change this.`,
|
|
709
|
+
);
|
|
710
|
+
} else if (projectSettings.skipped === "secure-mode") {
|
|
711
|
+
console.log(
|
|
712
|
+
`Ignoring ${projectSettings.settingsFile} in secure mode. If you trust it, pass it with MVN_ARGS='--global-settings ${projectSettings.settingsFile}'.`,
|
|
713
|
+
);
|
|
477
714
|
}
|
|
478
715
|
// Should we attempt to resolve class names
|
|
479
716
|
if (options.resolveClass || options.deep) {
|
|
@@ -488,13 +725,18 @@ export async function createJavaBom(path, options) {
|
|
|
488
725
|
}
|
|
489
726
|
}
|
|
490
727
|
// Use the cyclonedx maven plugin if there is no preference for maven deps tree
|
|
491
|
-
if (!useMavenDepsTree) {
|
|
728
|
+
if (!useMavenDepsTree && !isBuildToolRateLimited("maven")) {
|
|
492
729
|
thoughtLog("The user wants me to use the cyclonedx-maven plugin.");
|
|
493
730
|
console.log(`Executing '${mavenCmd}' in`, basePath);
|
|
494
731
|
result = safeSpawnSync(mavenCmd, mvnArgs, {
|
|
495
732
|
cwd: basePath,
|
|
496
733
|
shell: isWin,
|
|
497
734
|
});
|
|
735
|
+
noteBuildToolRateLimit("maven", commandOutputText(result), {
|
|
736
|
+
command: `${basename(mavenCmd)} ${mvnArgs.join(" ")}`,
|
|
737
|
+
exitCode:
|
|
738
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
739
|
+
});
|
|
498
740
|
// Check if the cyclonedx plugin created the required bom.json file
|
|
499
741
|
// Sometimes the plugin fails silently for complex maven projects
|
|
500
742
|
bomJsonFiles = getAllFiles(
|
|
@@ -520,8 +762,20 @@ export async function createJavaBom(path, options) {
|
|
|
520
762
|
) {
|
|
521
763
|
const tempRoot = getTmpDir();
|
|
522
764
|
const tempDir = safeMkdtempSync(join(tempRoot, "cdxgen-mvn-"));
|
|
523
|
-
|
|
524
|
-
|
|
765
|
+
// The recursive run writes one tree per reactor module into these
|
|
766
|
+
// directories, so the aggregator's run returns every module.
|
|
767
|
+
const treeJsonDir = join(tempDir, "tree-json");
|
|
768
|
+
const treeTextDir = join(tempDir, "tree-text");
|
|
769
|
+
safeMkdirSync(treeJsonDir);
|
|
770
|
+
safeMkdirSync(treeTextDir);
|
|
771
|
+
const tempMvnTree = join(
|
|
772
|
+
treeJsonDir,
|
|
773
|
+
`${MAVEN_MODULE_TREE_STEM}.json`,
|
|
774
|
+
);
|
|
775
|
+
const tempMvnTreeText = join(
|
|
776
|
+
treeTextDir,
|
|
777
|
+
`${MAVEN_MODULE_TREE_STEM}.txt`,
|
|
778
|
+
);
|
|
525
779
|
const tempMvnParentTree = join(
|
|
526
780
|
tempDir,
|
|
527
781
|
"cdxgen-mvn-parent-tree.json",
|
|
@@ -541,14 +795,11 @@ export async function createJavaBom(path, options) {
|
|
|
541
795
|
addArgs = parseMavenArgs(readEnvironmentVariable("MVN_ARGS"));
|
|
542
796
|
mvnTreeArgs = mvnTreeArgs.concat(addArgs);
|
|
543
797
|
}
|
|
544
|
-
|
|
545
|
-
if (safeExistsSync(settingsXml)) {
|
|
546
|
-
mvnTreeArgs.push("-s");
|
|
547
|
-
mvnTreeArgs.push(settingsXml);
|
|
548
|
-
}
|
|
798
|
+
mvnTreeArgs = mvnTreeArgs.concat(projectSettings.args);
|
|
549
799
|
// For the first pom alone, we need to execute first in non-recursive mode to capture
|
|
550
800
|
// the parent component. Then, we execute all of them in recursive mode
|
|
551
|
-
|
|
801
|
+
// A rate limit the --deep copy-dependencies run met ends it too.
|
|
802
|
+
if (f === firstPom && !isBuildToolRateLimited("maven")) {
|
|
552
803
|
thoughtLog(
|
|
553
804
|
"What is the parent component here? Let's use maven command to find out.",
|
|
554
805
|
);
|
|
@@ -558,14 +809,19 @@ export async function createJavaBom(path, options) {
|
|
|
558
809
|
`-DoutputFile=${tempMvnParentTree}`,
|
|
559
810
|
"-DoutputType=json",
|
|
560
811
|
];
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
812
|
+
findParentComponentArgs = findParentComponentArgs.concat(
|
|
813
|
+
addArgs,
|
|
814
|
+
projectSettings.args,
|
|
815
|
+
);
|
|
565
816
|
result = safeSpawnSync(mavenCmd, findParentComponentArgs, {
|
|
566
817
|
cwd: basePath,
|
|
567
818
|
shell: isWin,
|
|
568
819
|
});
|
|
820
|
+
noteBuildToolRateLimit("maven", commandOutputText(result), {
|
|
821
|
+
command: `${basename(mavenCmd)} ${findParentComponentArgs.join(" ")}`,
|
|
822
|
+
exitCode:
|
|
823
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
824
|
+
});
|
|
569
825
|
// If json is empty or unparseable, fallback to text parsing
|
|
570
826
|
let emptyJson = !safeExistsSync(tempMvnParentTree);
|
|
571
827
|
if (safeExistsSync(tempMvnParentTree)) {
|
|
@@ -581,16 +837,15 @@ export async function createJavaBom(path, options) {
|
|
|
581
837
|
}
|
|
582
838
|
}
|
|
583
839
|
}
|
|
584
|
-
if (
|
|
840
|
+
if (
|
|
841
|
+
!isBuildToolRateLimited("maven") &&
|
|
842
|
+
(result.status !== 0 || result.error || emptyJson)
|
|
843
|
+
) {
|
|
585
844
|
findParentComponentArgs = [
|
|
586
845
|
"dependency:tree",
|
|
587
846
|
"-N",
|
|
588
847
|
`-DoutputFile=${tempMvnParentTreeText}`,
|
|
589
|
-
];
|
|
590
|
-
if (addArgs.length) {
|
|
591
|
-
findParentComponentArgs =
|
|
592
|
-
findParentComponentArgs.concat(addArgs);
|
|
593
|
-
}
|
|
848
|
+
].concat(addArgs, projectSettings.args);
|
|
594
849
|
result = safeSpawnSync(mavenCmd, findParentComponentArgs, {
|
|
595
850
|
cwd: basePath,
|
|
596
851
|
shell: isWin,
|
|
@@ -630,49 +885,60 @@ export async function createJavaBom(path, options) {
|
|
|
630
885
|
`Executing '${basename(mavenCmd)} dependency:tree ...' in ${basePath}`,
|
|
631
886
|
);
|
|
632
887
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (safeExistsSync(tempMvnTree)) {
|
|
639
|
-
const mvnTreeString = readFileSync(tempMvnTree, {
|
|
640
|
-
encoding: "utf-8",
|
|
641
|
-
});
|
|
642
|
-
const parsedList = parseMavenTreeJson(mvnTreeString, f);
|
|
643
|
-
if (!parsedList?.pkgList?.length) {
|
|
644
|
-
emptyJson = true;
|
|
645
|
-
// Remove the invalid json file so text output is preferred below.
|
|
646
|
-
if (safeExistsSync(tempMvnTree)) {
|
|
647
|
-
safeUnlinkSync(tempMvnTree);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
if (result.status !== 0 || result.error || emptyJson) {
|
|
652
|
-
mvnTreeArgs = [
|
|
653
|
-
"dependency:tree",
|
|
654
|
-
`-DoutputFile=${tempMvnTreeText}`,
|
|
655
|
-
];
|
|
656
|
-
if (addArgs.length) {
|
|
657
|
-
mvnTreeArgs = mvnTreeArgs.concat(addArgs);
|
|
658
|
-
}
|
|
659
|
-
if (safeExistsSync(settingsXml)) {
|
|
660
|
-
mvnTreeArgs.push("-s");
|
|
661
|
-
mvnTreeArgs.push(settingsXml);
|
|
662
|
-
}
|
|
888
|
+
if (isBuildToolRateLimited("maven")) {
|
|
889
|
+
// The parent or plugin run already met the rate limit, so the
|
|
890
|
+
// tree run is skipped rather than repeated for the same answer.
|
|
891
|
+
result = { status: 1 };
|
|
892
|
+
} else {
|
|
663
893
|
result = safeSpawnSync(mavenCmd, mvnTreeArgs, {
|
|
664
894
|
cwd: basePath,
|
|
665
895
|
shell: isWin,
|
|
666
896
|
});
|
|
897
|
+
noteBuildToolRateLimit("maven", commandOutputText(result), {
|
|
898
|
+
command: `${basename(mavenCmd)} ${mvnTreeArgs.join(" ")}`,
|
|
899
|
+
exitCode:
|
|
900
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
901
|
+
});
|
|
902
|
+
const emptyJson = !readMavenModuleTrees(
|
|
903
|
+
treeJsonDir,
|
|
904
|
+
f,
|
|
905
|
+
).trees.some(
|
|
906
|
+
(tree) =>
|
|
907
|
+
parseMavenTreeJson(tree.text, tree.pomFile)?.pkgList?.length,
|
|
908
|
+
);
|
|
909
|
+
if (emptyJson) {
|
|
910
|
+
// Remove the invalid json trees so the text output is
|
|
911
|
+
// preferred below.
|
|
912
|
+
safeRmSync(treeJsonDir, { recursive: true, force: true });
|
|
913
|
+
}
|
|
914
|
+
if (
|
|
915
|
+
!isBuildToolRateLimited("maven") &&
|
|
916
|
+
(result.status !== 0 || result.error || emptyJson)
|
|
917
|
+
) {
|
|
918
|
+
mvnTreeArgs = [
|
|
919
|
+
"dependency:tree",
|
|
920
|
+
`-DoutputFile=${tempMvnTreeText}`,
|
|
921
|
+
].concat(addArgs, projectSettings.args);
|
|
922
|
+
result = safeSpawnSync(mavenCmd, mvnTreeArgs, {
|
|
923
|
+
cwd: basePath,
|
|
924
|
+
shell: isWin,
|
|
925
|
+
});
|
|
926
|
+
noteBuildToolRateLimit("maven", commandOutputText(result), {
|
|
927
|
+
command: `${basename(mavenCmd)} ${mvnTreeArgs.join(" ")}`,
|
|
928
|
+
exitCode:
|
|
929
|
+
typeof result.status === "number"
|
|
930
|
+
? result.status
|
|
931
|
+
: undefined,
|
|
932
|
+
});
|
|
933
|
+
}
|
|
667
934
|
}
|
|
668
935
|
if (result.status !== 0 || result.error) {
|
|
669
936
|
possible_misses = true;
|
|
670
937
|
// Our approach to recursively invoking the maven plugin for each sub-module is bound to result in failures
|
|
671
938
|
// These could be due to a range of reasons that are covered below.
|
|
672
939
|
if (
|
|
673
|
-
|
|
674
|
-
DEBUG_MODE ||
|
|
675
|
-
PREFER_MAVEN_DEPS_TREE
|
|
940
|
+
!isBuildToolRateLimited("maven") &&
|
|
941
|
+
(pomFiles.length === 1 || DEBUG_MODE || PREFER_MAVEN_DEPS_TREE)
|
|
676
942
|
) {
|
|
677
943
|
if (result.stdout) {
|
|
678
944
|
console.log(result.stdout);
|
|
@@ -771,16 +1037,18 @@ export async function createJavaBom(path, options) {
|
|
|
771
1037
|
pkgList = pkgList.concat(dlist);
|
|
772
1038
|
}
|
|
773
1039
|
} else {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1040
|
+
// The text trees exist only when the JSON run failed and was
|
|
1041
|
+
// retried. Each reactor module has its own tree, and a member
|
|
1042
|
+
// whose tree is here is not run again on its own.
|
|
1043
|
+
let moduleTrees = readMavenModuleTrees(treeTextDir, f);
|
|
1044
|
+
if (!moduleTrees.trees.length) {
|
|
1045
|
+
moduleTrees = readMavenModuleTrees(treeJsonDir, f);
|
|
1046
|
+
}
|
|
1047
|
+
for (const modulePom of moduleTrees.covered) {
|
|
1048
|
+
coveredPomFiles.add(modulePom);
|
|
1049
|
+
}
|
|
1050
|
+
for (const tree of moduleTrees.trees) {
|
|
1051
|
+
const parsedList = parseMavenTree(tree.text, tree.pomFile);
|
|
784
1052
|
const dlist = parsedList.pkgList || [];
|
|
785
1053
|
const tmpParentComponent = dlist.splice(0, 1)[0];
|
|
786
1054
|
if (tmpParentComponent) {
|
|
@@ -1041,11 +1309,23 @@ export async function createJavaBom(path, options) {
|
|
|
1041
1309
|
if (resolveGradleDistribution && gradleInitArgs.length) {
|
|
1042
1310
|
gradleInitArgs.push("-PcdxgenResolveDistribution=true");
|
|
1043
1311
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1312
|
+
// `gradle properties` evaluates the project's build scripts, so it is
|
|
1313
|
+
// part of the dependency-tooling surface gated by --no-install-deps
|
|
1314
|
+
// (and the pre-build lifecycle).
|
|
1315
|
+
const gradleToolingAllowed =
|
|
1316
|
+
options.installDeps !== false || options.introspect === true;
|
|
1317
|
+
if (gradleToolingAllowed) {
|
|
1318
|
+
parallelPropTaskOut = executeParallelGradleProperties(
|
|
1319
|
+
gradleRootPath,
|
|
1320
|
+
[null].concat(includedBuilds),
|
|
1321
|
+
gradleInitArgs,
|
|
1322
|
+
options,
|
|
1323
|
+
);
|
|
1324
|
+
} else {
|
|
1325
|
+
console.log(
|
|
1326
|
+
"Skipping 'gradle properties' since dependency installation is disabled. Only statically parsed Gradle metadata will be used.",
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1049
1329
|
if (readEnvironmentVariable("GRADLE_INCLUDED_BUILDS") === undefined) {
|
|
1050
1330
|
const outputLines = parallelPropTaskOut.split("\n");
|
|
1051
1331
|
for (const [_i, line] of outputLines.entries()) {
|
|
@@ -1073,7 +1353,14 @@ export async function createJavaBom(path, options) {
|
|
|
1073
1353
|
}
|
|
1074
1354
|
parallelPropTaskOut = parallelPropTaskOut.concat(
|
|
1075
1355
|
"\n",
|
|
1076
|
-
|
|
1356
|
+
gradleToolingAllowed
|
|
1357
|
+
? executeParallelGradleProperties(
|
|
1358
|
+
gradleRootPath,
|
|
1359
|
+
includedBuilds,
|
|
1360
|
+
[],
|
|
1361
|
+
options,
|
|
1362
|
+
)
|
|
1363
|
+
: "",
|
|
1077
1364
|
);
|
|
1078
1365
|
}
|
|
1079
1366
|
}
|
|
@@ -1114,6 +1401,8 @@ export async function createJavaBom(path, options) {
|
|
|
1114
1401
|
parallelPropTaskOut = executeParallelGradleProperties(
|
|
1115
1402
|
gradleRootPath,
|
|
1116
1403
|
allProjectsStr.filter((module) => !modulesToSkip.includes(module)),
|
|
1404
|
+
[],
|
|
1405
|
+
options,
|
|
1117
1406
|
);
|
|
1118
1407
|
const splitPropTaskOut = splitOutputByGradleProjects(
|
|
1119
1408
|
parallelPropTaskOut,
|
|
@@ -1161,7 +1450,7 @@ export async function createJavaBom(path, options) {
|
|
|
1161
1450
|
)
|
|
1162
1451
|
) {
|
|
1163
1452
|
allProjects.push(parentComponent);
|
|
1164
|
-
const gradleCmd = getGradleCommand(gradleRootPath, null);
|
|
1453
|
+
const gradleCmd = getGradleCommand(gradleRootPath, null, options);
|
|
1165
1454
|
const gradleDepTask = readEnvironmentVariable("GRADLE_DEPENDENCY_TASK")
|
|
1166
1455
|
? readEnvironmentVariable("GRADLE_DEPENDENCY_TASK")
|
|
1167
1456
|
: "dependencies";
|
|
@@ -1196,6 +1485,14 @@ export async function createJavaBom(path, options) {
|
|
|
1196
1485
|
);
|
|
1197
1486
|
const allOutputs = [];
|
|
1198
1487
|
for (const gradleArg of gradleArguments) {
|
|
1488
|
+
if (isBuildToolRateLimited("gradle")) {
|
|
1489
|
+
if (DEBUG_MODE) {
|
|
1490
|
+
console.log(
|
|
1491
|
+
"Skipping the remaining gradle dependency tasks: the repository rate limited gradle in this scan.",
|
|
1492
|
+
);
|
|
1493
|
+
}
|
|
1494
|
+
break;
|
|
1495
|
+
}
|
|
1199
1496
|
if (DEBUG_MODE) {
|
|
1200
1497
|
console.log(
|
|
1201
1498
|
`Executing ${gradleCmd} with arguments ${gradleArg.join(" ").substring(0, 150)}... in ${gradleRootPath}`,
|
|
@@ -1211,6 +1508,13 @@ export async function createJavaBom(path, options) {
|
|
|
1211
1508
|
cwd: gradleRootPath,
|
|
1212
1509
|
shell: isWin,
|
|
1213
1510
|
});
|
|
1511
|
+
// The dependencies task exits 0 even when resolution failed under a
|
|
1512
|
+
// rate limit, so the output decides, not the status.
|
|
1513
|
+
noteBuildToolRateLimit("gradle", commandOutputText(sresult), {
|
|
1514
|
+
command: `${basename(gradleCmd)} ${finalGradleArg.join(" ")}`,
|
|
1515
|
+
exitCode:
|
|
1516
|
+
typeof sresult.status === "number" ? sresult.status : undefined,
|
|
1517
|
+
});
|
|
1214
1518
|
if (sresult.status !== 0 || sresult.error) {
|
|
1215
1519
|
if (options.failOnError || DEBUG_MODE) {
|
|
1216
1520
|
console.error(sresult.stdout, sresult.stderr);
|
|
@@ -1621,8 +1925,6 @@ export async function createJavaBom(path, options) {
|
|
|
1621
1925
|
`${options.multiProject ? "**/" : ""}build.sbt.lock`,
|
|
1622
1926
|
options,
|
|
1623
1927
|
);
|
|
1624
|
-
const tempCacheDir = safeMkdtempSync(join(getTmpDir(), "sbt-cache-"));
|
|
1625
|
-
safeMkdirSync(tempCacheDir, { recursive: true });
|
|
1626
1928
|
if (
|
|
1627
1929
|
sbtProjects?.length &&
|
|
1628
1930
|
isPackageManagerAllowed(
|
|
@@ -1639,6 +1941,13 @@ export async function createJavaBom(path, options) {
|
|
|
1639
1941
|
pkgList = pkgList.concat(dlist);
|
|
1640
1942
|
}
|
|
1641
1943
|
}
|
|
1944
|
+
} else if (options.installDeps === false && !options.introspect) {
|
|
1945
|
+
// Executing sbt evaluates the project's build definition (arbitrary
|
|
1946
|
+
// Scala), so it belongs to the dependency-tooling surface disabled
|
|
1947
|
+
// by --no-install-deps and the pre-build lifecycle.
|
|
1948
|
+
console.log(
|
|
1949
|
+
"Skipping sbt dependencyTree since dependency installation is disabled. Only statically parsed sbt metadata will be used.",
|
|
1950
|
+
);
|
|
1642
1951
|
} else {
|
|
1643
1952
|
const SBT_CMD = readEnvironmentVariable("SBT_CMD") || "sbt";
|
|
1644
1953
|
let sbtVersion = determineSbtVersion(path);
|
|
@@ -1681,20 +1990,46 @@ export async function createJavaBom(path, options) {
|
|
|
1681
1990
|
}
|
|
1682
1991
|
}
|
|
1683
1992
|
safeWriteSync(tempSbtPlugins, sbtPluginDefinition);
|
|
1684
|
-
|
|
1993
|
+
// sbt resolves into the user's own caches, so a project that has been
|
|
1994
|
+
// built resolves without the network. The dependency trees download
|
|
1995
|
+
// the jar of every dependency they list, which is all --deep reads.
|
|
1685
1996
|
const env = { ...process.env };
|
|
1686
|
-
// We need to collect the jars from the cache
|
|
1687
|
-
if (options.deep) {
|
|
1688
|
-
sbtExtraArgs = " updateClassifiers";
|
|
1689
|
-
env["COURSIER_CACHE"] = tempCacheDir;
|
|
1690
|
-
env["SBT_IVY_HOME"] = tempCacheDir;
|
|
1691
|
-
}
|
|
1692
1997
|
for (const i in sbtProjects) {
|
|
1998
|
+
if (isBuildToolRateLimited("sbt")) {
|
|
1999
|
+
// Every remaining sbt build in this scan resolves from the same
|
|
2000
|
+
// rate limited repository.
|
|
2001
|
+
if (DEBUG_MODE) {
|
|
2002
|
+
console.log(
|
|
2003
|
+
`Skipping ${sbtProjects[i]}: the repository rate limited sbt in this scan.`,
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
break;
|
|
2007
|
+
}
|
|
1693
2008
|
const basePath = sbtProjects[i];
|
|
1694
2009
|
const dlFile = join(tempDir, `dl-${i}.tmp`);
|
|
2010
|
+
// The sbt version of the project itself decides the invocation
|
|
2011
|
+
// form: sbt 2 both joins separate command-line arguments into a
|
|
2012
|
+
// single command line and no longer offers `dependencyTree / toFile`.
|
|
2013
|
+
const projectSbtVersion = determineSbtVersion(basePath) || sbtVersion;
|
|
2014
|
+
const isSbt2 = isSbt2Version(projectSbtVersion);
|
|
2015
|
+
// The sbt 2 launcher runs a thin client by default. When a server is
|
|
2016
|
+
// already running for the build, an IDE's for example, the client
|
|
2017
|
+
// prints neither the project list nor the trees, so sbt runs
|
|
2018
|
+
// in-process instead. sbt.bat has no `--server` option.
|
|
2019
|
+
const sbtLauncherArgs = isSbt2 && !isWin ? ["--server"] : [];
|
|
2020
|
+
const sbtServerFile = join(
|
|
2021
|
+
basePath,
|
|
2022
|
+
"project",
|
|
2023
|
+
"target",
|
|
2024
|
+
"active.json",
|
|
2025
|
+
);
|
|
2026
|
+
const sbtServerWasRunning = isSbt2 && safeExistsSync(sbtServerFile);
|
|
1695
2027
|
let sbtArgs = [];
|
|
1696
2028
|
let pluginFile = null;
|
|
2029
|
+
let pluginAdded = false;
|
|
1697
2030
|
const subDlFiles = [];
|
|
2031
|
+
const failedSubprojects = [];
|
|
2032
|
+
let subprojects = [];
|
|
1698
2033
|
if (standalonePluginFile) {
|
|
1699
2034
|
sbtArgs = [
|
|
1700
2035
|
`-addPluginSbtFile=${tempSbtPlugins}`,
|
|
@@ -1708,15 +2043,23 @@ export async function createJavaBom(path, options) {
|
|
|
1708
2043
|
// `dependencyTree` fan-out yields a far more complete tree than a
|
|
1709
2044
|
// single root resolution (which only captures the root project's own
|
|
1710
2045
|
// libraryDependencies). See https://github.com/cdxgen/cdxgen/issues/4291
|
|
1711
|
-
|
|
2046
|
+
subprojects = discoverSbtProjectsFromCmd(
|
|
1712
2047
|
basePath,
|
|
1713
2048
|
SBT_CMD,
|
|
1714
2049
|
env,
|
|
2050
|
+
sbtLauncherArgs,
|
|
1715
2051
|
);
|
|
1716
|
-
if (
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
2052
|
+
if (isSbt2) {
|
|
2053
|
+
const trees = subprojects.length
|
|
2054
|
+
? subprojects.map((sp) => ({
|
|
2055
|
+
project: sp,
|
|
2056
|
+
file: join(tempDir, `dl-${i}-${sp}.tmp`),
|
|
2057
|
+
}))
|
|
2058
|
+
: [{ file: dlFile }];
|
|
2059
|
+
subDlFiles.push(...trees.map((t) => t.file));
|
|
2060
|
+
sbtArgs = [sbt2DependencyTreeCommand(trees)];
|
|
2061
|
+
} else if (subprojects.length > 0 && useSlashSyntax) {
|
|
2062
|
+
sbtArgs = ["'set ThisBuild / asciiGraphWidth := 800'"];
|
|
1720
2063
|
for (const sp of subprojects) {
|
|
1721
2064
|
const subDlFile = join(tempDir, `dl-${i}-${sp}.tmp`);
|
|
1722
2065
|
subDlFiles.push(subDlFile);
|
|
@@ -1726,21 +2069,27 @@ export async function createJavaBom(path, options) {
|
|
|
1726
2069
|
}
|
|
1727
2070
|
} else if (useSlashSyntax) {
|
|
1728
2071
|
sbtArgs = [
|
|
1729
|
-
`'set ThisBuild / asciiGraphWidth := 800'
|
|
2072
|
+
`'set ThisBuild / asciiGraphWidth := 800' "dependencyTree / toFile ${dlFile} --force"`,
|
|
1730
2073
|
];
|
|
1731
2074
|
subDlFiles.push(dlFile);
|
|
1732
2075
|
} else {
|
|
1733
2076
|
sbtArgs = [
|
|
1734
|
-
`'set asciiGraphWidth in ThisBuild := 800'
|
|
2077
|
+
`'set asciiGraphWidth in ThisBuild := 800' "dependencyTree::toFile ${dlFile} --force"`,
|
|
1735
2078
|
];
|
|
1736
2079
|
subDlFiles.push(dlFile);
|
|
1737
2080
|
}
|
|
1738
|
-
|
|
2081
|
+
// sbt 2 enables its dependency tree plugin by default, so the
|
|
2082
|
+
// build's plugins.sbt stays untouched and the build definition is
|
|
2083
|
+
// not recompiled for the scan.
|
|
2084
|
+
if (!isSbt2) {
|
|
2085
|
+
pluginFile = addPlugin(basePath, sbtPluginDefinition);
|
|
2086
|
+
pluginAdded = true;
|
|
2087
|
+
}
|
|
1739
2088
|
}
|
|
1740
2089
|
// Run sbt in non-interactive batch mode so it never blocks waiting for
|
|
1741
2090
|
// input on stdin (which can otherwise appear as an indefinite hang) and
|
|
1742
2091
|
// does not emit ANSI colour codes into the dependency tree output.
|
|
1743
|
-
sbtArgs = ["-batch", "-no-colors", ...sbtArgs];
|
|
2092
|
+
sbtArgs = [...sbtLauncherArgs, "-batch", "-no-colors", ...sbtArgs];
|
|
1744
2093
|
console.log(
|
|
1745
2094
|
"Executing",
|
|
1746
2095
|
SBT_CMD,
|
|
@@ -1751,13 +2100,31 @@ export async function createJavaBom(path, options) {
|
|
|
1751
2100
|
tempSbtgDir,
|
|
1752
2101
|
);
|
|
1753
2102
|
// Note that the command has to be invoked with `shell: true` to properly execut sbt
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
2103
|
+
// sbt 2 is the exception: its ;-joined command argument contains a
|
|
2104
|
+
// semicolon, so on POSIX the launcher is spawned directly and the
|
|
2105
|
+
// argument reaches sbt verbatim.
|
|
2106
|
+
const result = sbtSpawnSync(
|
|
2107
|
+
SBT_CMD,
|
|
2108
|
+
sbtArgs,
|
|
2109
|
+
{
|
|
2110
|
+
cwd: basePath,
|
|
2111
|
+
shell: isWin || !isSbt2 || standalonePluginFile,
|
|
2112
|
+
// The sbt commands below embed their own shell quoting, which the
|
|
2113
|
+
// shell must strip for sbt to see separate commands.
|
|
2114
|
+
cdxgenRawShellArgs: true,
|
|
2115
|
+
env,
|
|
2116
|
+
},
|
|
2117
|
+
!isSbt2 || standalonePluginFile,
|
|
2118
|
+
);
|
|
2119
|
+
noteBuildToolRateLimit("sbt", commandOutputText(result), {
|
|
2120
|
+
command: `${SBT_CMD} ${sbtArgs.join(" ")}`,
|
|
2121
|
+
exitCode:
|
|
2122
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
1758
2123
|
});
|
|
1759
2124
|
if (result.status !== 0 || result.error) {
|
|
1760
|
-
|
|
2125
|
+
if (!isBuildToolRateLimited("sbt")) {
|
|
2126
|
+
console.error(result.stdout, result.stderr);
|
|
2127
|
+
}
|
|
1761
2128
|
recordDegradation("jvm.sbt.no-lockfile", {
|
|
1762
2129
|
ecosystem: "java",
|
|
1763
2130
|
tool: "sbt",
|
|
@@ -1768,15 +2135,20 @@ export async function createJavaBom(path, options) {
|
|
|
1768
2135
|
detail:
|
|
1769
2136
|
"The sbt dependencyTree command did not succeed, so the declared sbt dependencies are the only evidence available.",
|
|
1770
2137
|
});
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
2138
|
+
if (!isBuildToolRateLimited("sbt")) {
|
|
2139
|
+
console.log(
|
|
2140
|
+
"1. Check if scala and sbt is installed and available in PATH. Only scala 2.10 + sbt 0.13.6+, 2.12 + sbt 1.0+ and sbt 2.x are supported for now.",
|
|
2141
|
+
);
|
|
2142
|
+
// sbt 2 brings its own dependency tree, so no plugin is added.
|
|
2143
|
+
if (!isSbt2) {
|
|
2144
|
+
console.log(
|
|
2145
|
+
"2. Check if the plugin net.virtual-void:sbt-dependency-graph 0.10.0-RC1 can be used in the environment",
|
|
2146
|
+
);
|
|
2147
|
+
}
|
|
2148
|
+
console.log(
|
|
2149
|
+
`${isSbt2 ? 2 : 3}. Consider creating a lockfile using sbt-dependency-lock plugin. See https://github.com/stringbean/sbt-dependency-lock`,
|
|
2150
|
+
);
|
|
2151
|
+
}
|
|
1780
2152
|
deferFailOnError(options, {
|
|
1781
2153
|
ecosystem: "java",
|
|
1782
2154
|
tool: "sbt",
|
|
@@ -1786,13 +2158,95 @@ export async function createJavaBom(path, options) {
|
|
|
1786
2158
|
detail: "the sbt dependencyTree command failed",
|
|
1787
2159
|
});
|
|
1788
2160
|
}
|
|
1789
|
-
|
|
2161
|
+
// One failing subproject (for example an unresolvable SNAPSHOT)
|
|
2162
|
+
// fails the combined command and would zero the whole SBOM. Retry
|
|
2163
|
+
// each subproject on its own and keep the trees that succeed. A rate
|
|
2164
|
+
// limited repository ends the retries as well: each would download
|
|
2165
|
+
// the same artifacts against the same 429.
|
|
2166
|
+
if (
|
|
2167
|
+
!standalonePluginFile &&
|
|
2168
|
+
subprojects.length > 0 &&
|
|
2169
|
+
(result.status !== 0 || result.error) &&
|
|
2170
|
+
!isBuildToolRateLimited("sbt")
|
|
2171
|
+
) {
|
|
2172
|
+
for (const sp of subprojects) {
|
|
2173
|
+
const subDlFile = join(tempDir, `dl-${i}-${sp}.tmp`);
|
|
2174
|
+
if (safeExistsSync(subDlFile)) {
|
|
2175
|
+
// The combined run produced this tree before it failed.
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
if (DEBUG_MODE) {
|
|
2179
|
+
console.log(`Retrying the sbt tree of ${sp} on its own.`);
|
|
2180
|
+
}
|
|
2181
|
+
let retryArgs;
|
|
2182
|
+
if (isSbt2) {
|
|
2183
|
+
retryArgs = [
|
|
2184
|
+
...sbtLauncherArgs,
|
|
2185
|
+
"-batch",
|
|
2186
|
+
"-no-colors",
|
|
2187
|
+
sbt2DependencyTreeCommand([{ project: sp, file: subDlFile }]),
|
|
2188
|
+
];
|
|
2189
|
+
} else if (useSlashSyntax) {
|
|
2190
|
+
retryArgs = [
|
|
2191
|
+
"-batch",
|
|
2192
|
+
"-no-colors",
|
|
2193
|
+
"'set ThisBuild / asciiGraphWidth := 800'",
|
|
2194
|
+
`"${sp}/dependencyTree / toFile ${subDlFile} --force"`,
|
|
2195
|
+
];
|
|
2196
|
+
} else {
|
|
2197
|
+
continue;
|
|
2198
|
+
}
|
|
2199
|
+
sbtSpawnSync(
|
|
2200
|
+
SBT_CMD,
|
|
2201
|
+
retryArgs,
|
|
2202
|
+
{
|
|
2203
|
+
cwd: basePath,
|
|
2204
|
+
shell: isWin || !isSbt2,
|
|
2205
|
+
cdxgenRawShellArgs: true,
|
|
2206
|
+
env,
|
|
2207
|
+
},
|
|
2208
|
+
!isSbt2,
|
|
2209
|
+
);
|
|
2210
|
+
// The tree file is already listed for parsing, so a retry only
|
|
2211
|
+
// has to produce it.
|
|
2212
|
+
if (!safeExistsSync(subDlFile)) {
|
|
2213
|
+
failedSubprojects.push(sp);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
if (failedSubprojects.length) {
|
|
2217
|
+
console.log(
|
|
2218
|
+
`The sbt dependency trees of ${failedSubprojects.join(", ")} could not be resolved.`,
|
|
2219
|
+
);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
// Where the thin client still runs, it leaves the server it started
|
|
2223
|
+
// behind. A server that was running before the scan is not ours to
|
|
2224
|
+
// stop.
|
|
2225
|
+
if (isSbt2 && !sbtServerWasRunning && safeExistsSync(sbtServerFile)) {
|
|
2226
|
+
sbtSpawnSync(SBT_CMD, ["-batch", "-no-colors", "shutdown"], {
|
|
2227
|
+
cwd: basePath,
|
|
2228
|
+
shell: isWin,
|
|
2229
|
+
env,
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
// Only a plugins.sbt cdxgen amended is restored: without a backup,
|
|
2233
|
+
// the cleanup removes the file as one cdxgen created.
|
|
2234
|
+
if (pluginAdded) {
|
|
1790
2235
|
cleanupPlugin(basePath, pluginFile);
|
|
1791
2236
|
}
|
|
1792
2237
|
const sbtSubprojectRoots = [];
|
|
1793
2238
|
for (const subDlFile of subDlFiles) {
|
|
1794
2239
|
if (safeExistsSync(subDlFile)) {
|
|
1795
|
-
|
|
2240
|
+
// The trees are temporary files that go away with the scan, so
|
|
2241
|
+
// the components name the build definition they were resolved
|
|
2242
|
+
// from instead: build.sbt, or for a build written only in
|
|
2243
|
+
// project/*.scala, the build.properties beside it.
|
|
2244
|
+
const sbtBuildFile =
|
|
2245
|
+
[
|
|
2246
|
+
join(basePath, "build.sbt"),
|
|
2247
|
+
join(basePath, "project", "build.properties"),
|
|
2248
|
+
].find((f) => safeExistsSync(f)) || basePath;
|
|
2249
|
+
const retMap = await parseSbtTree(subDlFile, sbtBuildFile);
|
|
1796
2250
|
if (retMap.pkgList?.length) {
|
|
1797
2251
|
const tmpParentComponent = retMap.pkgList.splice(0, 1)[0];
|
|
1798
2252
|
tmpParentComponent.type = "application";
|
|
@@ -1840,19 +2294,38 @@ export async function createJavaBom(path, options) {
|
|
|
1840
2294
|
"bom-ref": decodeURIComponent(rootPurl),
|
|
1841
2295
|
};
|
|
1842
2296
|
parentComponent = rootComponent;
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2297
|
+
// Merged rather than appended, so the root lists every subproject
|
|
2298
|
+
// once, and a root project with sources of its own, whose tree is
|
|
2299
|
+
// the synthetic root itself, neither depends on itself nor gets a
|
|
2300
|
+
// second dependency entry.
|
|
2301
|
+
dependencies = mergeDependencies(
|
|
2302
|
+
dependencies,
|
|
2303
|
+
[
|
|
2304
|
+
{
|
|
2305
|
+
ref: rootComponent["bom-ref"],
|
|
2306
|
+
dependsOn: sbtSubprojectRoots
|
|
2307
|
+
.map((c) => c["bom-ref"])
|
|
2308
|
+
.filter(Boolean),
|
|
2309
|
+
},
|
|
2310
|
+
],
|
|
2311
|
+
rootComponent,
|
|
2312
|
+
);
|
|
1849
2313
|
} else if (sbtSubprojectRoots.length > 0 && !parentComponent) {
|
|
1850
2314
|
parentComponent = sbtSubprojectRoots[0];
|
|
1851
2315
|
}
|
|
2316
|
+
if (failedSubprojects.length && parentComponent?.name) {
|
|
2317
|
+
parentComponent.properties ??= [];
|
|
2318
|
+
parentComponent.properties.push({
|
|
2319
|
+
name: "cdx:sbt:failedProjects",
|
|
2320
|
+
value: failedSubprojects.join(", "),
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
1852
2323
|
}
|
|
1853
2324
|
|
|
1854
|
-
//
|
|
1855
|
-
|
|
2325
|
+
// The trees were parsed inside the loop, so neither directory is
|
|
2326
|
+
// needed any more.
|
|
2327
|
+
safeRmSync(tempDir, { recursive: true, force: true });
|
|
2328
|
+
safeRmSync(tempSbtgDir, { recursive: true, force: true });
|
|
1856
2329
|
} // else
|
|
1857
2330
|
|
|
1858
2331
|
if (DEBUG_MODE) {
|
|
@@ -1860,7 +2333,10 @@ export async function createJavaBom(path, options) {
|
|
|
1860
2333
|
}
|
|
1861
2334
|
// Should we attempt to resolve class names
|
|
1862
2335
|
if (options.resolveClass || options.deep) {
|
|
1863
|
-
const tmpjarNSMapping = await
|
|
2336
|
+
const tmpjarNSMapping = await collectSbtDependencyJars(
|
|
2337
|
+
pkgList,
|
|
2338
|
+
jarNSMapping,
|
|
2339
|
+
);
|
|
1864
2340
|
if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
|
|
1865
2341
|
jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
|
|
1866
2342
|
}
|
|
@@ -1884,15 +2360,10 @@ export async function createJavaBom(path, options) {
|
|
|
1884
2360
|
}
|
|
1885
2361
|
}
|
|
1886
2362
|
}
|
|
1887
|
-
if (tempCacheDir?.startsWith(getTmpDir())) {
|
|
1888
|
-
safeRmSync(tempCacheDir, {
|
|
1889
|
-
recursive: true,
|
|
1890
|
-
force: true,
|
|
1891
|
-
});
|
|
1892
|
-
}
|
|
1893
2363
|
|
|
1894
2364
|
if (
|
|
1895
2365
|
millFiles?.length &&
|
|
2366
|
+
(options.installDeps !== false || options.introspect === true) &&
|
|
1896
2367
|
isPackageManagerAllowed(
|
|
1897
2368
|
"mill",
|
|
1898
2369
|
["bazel", "sbt", "gradle", "maven"],
|
|
@@ -1905,7 +2376,13 @@ export async function createJavaBom(path, options) {
|
|
|
1905
2376
|
"maven",
|
|
1906
2377
|
options,
|
|
1907
2378
|
);
|
|
1908
|
-
const millCmd = getMillCommand(millRootPath);
|
|
2379
|
+
const millCmd = getMillCommand(millRootPath, options);
|
|
2380
|
+
// Mill 1.0 replaced the ivyDepsTree task with showMvnDepsTree
|
|
2381
|
+
const millVersion = getMillVersion(millRootPath, millCmd);
|
|
2382
|
+
const millTreeTask =
|
|
2383
|
+
millVersion != null && gte(millVersion, "1.0.0")
|
|
2384
|
+
? "showMvnDepsTree"
|
|
2385
|
+
: "ivyDepsTree";
|
|
1909
2386
|
const millCommonArgs = [
|
|
1910
2387
|
"--color",
|
|
1911
2388
|
"false",
|
|
@@ -1917,7 +2394,7 @@ export async function createJavaBom(path, options) {
|
|
|
1917
2394
|
if (!["true", "1"].includes(readEnvironmentVariable("MILL_USE_SERVER"))) {
|
|
1918
2395
|
millCommonArgs.unshift("--no-server");
|
|
1919
2396
|
}
|
|
1920
|
-
const millArgs = [...millCommonArgs,
|
|
2397
|
+
const millArgs = [...millCommonArgs, `__.${millTreeTask}`];
|
|
1921
2398
|
if (DEBUG_MODE) {
|
|
1922
2399
|
console.log("Executing", millCmd, "in", millRootPath);
|
|
1923
2400
|
}
|
|
@@ -1926,6 +2403,11 @@ export async function createJavaBom(path, options) {
|
|
|
1926
2403
|
shell: isWin,
|
|
1927
2404
|
maxBuffer: MAX_BUFFER * 10,
|
|
1928
2405
|
});
|
|
2406
|
+
noteBuildToolRateLimit("mill", commandOutputText(sresult), {
|
|
2407
|
+
command: `${basename(millCmd)} ${millArgs.join(" ")}`,
|
|
2408
|
+
exitCode:
|
|
2409
|
+
typeof sresult.status === "number" ? sresult.status : undefined,
|
|
2410
|
+
});
|
|
1929
2411
|
if (sresult.status !== 0 || sresult.error) {
|
|
1930
2412
|
if (options.failOnError || DEBUG_MODE) {
|
|
1931
2413
|
console.error(sresult.stdout, sresult.stderr);
|
|
@@ -1935,10 +2417,14 @@ export async function createJavaBom(path, options) {
|
|
|
1935
2417
|
tool: "mill",
|
|
1936
2418
|
exitCode:
|
|
1937
2419
|
typeof sresult.status === "number" ? sresult.status : undefined,
|
|
1938
|
-
detail: "the mill
|
|
2420
|
+
detail: "the mill dependency tree command failed",
|
|
1939
2421
|
});
|
|
1940
2422
|
}
|
|
1941
|
-
const millResolveArgs = [
|
|
2423
|
+
const millResolveArgs = [
|
|
2424
|
+
...millCommonArgs,
|
|
2425
|
+
"resolve",
|
|
2426
|
+
`__.${millTreeTask}`,
|
|
2427
|
+
];
|
|
1942
2428
|
if (DEBUG_MODE) {
|
|
1943
2429
|
console.log(
|
|
1944
2430
|
"Executing",
|
|
@@ -1948,6 +2434,9 @@ export async function createJavaBom(path, options) {
|
|
|
1948
2434
|
millRootPath,
|
|
1949
2435
|
);
|
|
1950
2436
|
}
|
|
2437
|
+
// The resolve run only lists the build's tasks and downloads nothing,
|
|
2438
|
+
// so it runs even after a rate limit: the trees of the modules that did
|
|
2439
|
+
// resolve under --keep-going are read through it.
|
|
1951
2440
|
sresult = safeSpawnSync(millCmd, millResolveArgs, {
|
|
1952
2441
|
cwd: millRootPath,
|
|
1953
2442
|
shell: isWin,
|
|
@@ -1970,6 +2459,8 @@ export async function createJavaBom(path, options) {
|
|
|
1970
2459
|
const modules = sstdout
|
|
1971
2460
|
.trim()
|
|
1972
2461
|
.split("\n")
|
|
2462
|
+
// Mill 1.x prints headers around the module list
|
|
2463
|
+
.filter((a) => a.endsWith(`.${millTreeTask}`))
|
|
1973
2464
|
.map((a) => a.substring(0, a.lastIndexOf(".")))
|
|
1974
2465
|
.filter((a) =>
|
|
1975
2466
|
["true", "1"].includes(readEnvironmentVariable("MILL_EXCLUDE_TEST"))
|
|
@@ -1982,7 +2473,13 @@ export async function createJavaBom(path, options) {
|
|
|
1982
2473
|
relations.set(parentComponent["bom-ref"], []);
|
|
1983
2474
|
for (const module of modules) {
|
|
1984
2475
|
moduleBomRefs.push(
|
|
1985
|
-
parseMillDependency(
|
|
2476
|
+
parseMillDependency(
|
|
2477
|
+
module,
|
|
2478
|
+
packages,
|
|
2479
|
+
relations,
|
|
2480
|
+
millRootPath,
|
|
2481
|
+
`${millTreeTask}.log`,
|
|
2482
|
+
),
|
|
1986
2483
|
);
|
|
1987
2484
|
}
|
|
1988
2485
|
for (const module of moduleBomRefs) {
|
|
@@ -2035,6 +2532,34 @@ export async function createJavaBom(path, options) {
|
|
|
2035
2532
|
}
|
|
2036
2533
|
}
|
|
2037
2534
|
}
|
|
2535
|
+
// scala-cli projects declare everything with //> using directives in
|
|
2536
|
+
// their sources and have none of the build files handled above.
|
|
2537
|
+
if (
|
|
2538
|
+
!pomFiles?.length &&
|
|
2539
|
+
!gradleFiles?.length &&
|
|
2540
|
+
!millFiles?.length &&
|
|
2541
|
+
!sbtProjects.length &&
|
|
2542
|
+
isPackageManagerAllowed(
|
|
2543
|
+
"scala-cli",
|
|
2544
|
+
["bazel", "sbt", "gradle", "maven", "mill"],
|
|
2545
|
+
options,
|
|
2546
|
+
)
|
|
2547
|
+
) {
|
|
2548
|
+
const dlist = collectScalaCliComponents(path, options);
|
|
2549
|
+
if (dlist?.length) {
|
|
2550
|
+
thoughtLog(
|
|
2551
|
+
`**SCALA-CLI**: Found ${dlist.length} declared dependencies.`,
|
|
2552
|
+
);
|
|
2553
|
+
pkgList = pkgList.concat(dlist);
|
|
2554
|
+
if (!parentComponent?.name) {
|
|
2555
|
+
parentComponent = createDefaultParentComponent(
|
|
2556
|
+
path,
|
|
2557
|
+
"maven",
|
|
2558
|
+
options,
|
|
2559
|
+
);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2038
2563
|
} catch (err) {
|
|
2039
2564
|
if (!isDeferredFailOnError(err)) {
|
|
2040
2565
|
throw err;
|
|
@@ -2044,11 +2569,47 @@ export async function createJavaBom(path, options) {
|
|
|
2044
2569
|
thoughtLog(
|
|
2045
2570
|
"Completing the java BOM with the components collected before the deferred failure.",
|
|
2046
2571
|
);
|
|
2572
|
+
} finally {
|
|
2573
|
+
// A build this scan ran can have started a Mill daemon of its own
|
|
2574
|
+
// without --no-server; stop those even when a later step failed.
|
|
2575
|
+
stopMillDaemons(path, millDaemonsBefore);
|
|
2576
|
+
}
|
|
2577
|
+
// A Scala.js build bundles npm packages: scalajs-bundler installs them
|
|
2578
|
+
// under target, and a bundler workspace keeps its lock file beside the
|
|
2579
|
+
// build. Their npm components join the BOM so module imports can be
|
|
2580
|
+
// attributed to them, and the project depends on the workspaces' direct
|
|
2581
|
+
// dependencies.
|
|
2582
|
+
try {
|
|
2583
|
+
const scalajsNpm = await collectScalaJsNpmComponents(
|
|
2584
|
+
path,
|
|
2585
|
+
pkgList,
|
|
2586
|
+
options,
|
|
2587
|
+
);
|
|
2588
|
+
if (scalajsNpm.components.length) {
|
|
2589
|
+
pkgList = pkgList.concat(scalajsNpm.components);
|
|
2590
|
+
const npmEdges = [...scalajsNpm.dependencies];
|
|
2591
|
+
if (parentComponent?.["bom-ref"] && scalajsNpm.roots.length) {
|
|
2592
|
+
npmEdges.push({
|
|
2593
|
+
ref: parentComponent["bom-ref"],
|
|
2594
|
+
dependsOn: scalajsNpm.roots,
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
dependencies = mergeDependencies(dependencies, npmEdges, parentComponent);
|
|
2598
|
+
}
|
|
2599
|
+
} catch (err) {
|
|
2600
|
+
if (DEBUG_MODE) {
|
|
2601
|
+
console.log("Unable to collect the Scala.js npm dependencies", err);
|
|
2602
|
+
}
|
|
2047
2603
|
}
|
|
2048
2604
|
// Deduplication and metadata belong to whatever was collected, so they run
|
|
2049
2605
|
// on the deferred path too.
|
|
2050
2606
|
pkgList = trimComponents(pkgList);
|
|
2051
|
-
|
|
2607
|
+
// --deep collects namespaces; it does not opt in to remote licence lookups,
|
|
2608
|
+
// which FETCH_LICENSE controls. The project's own modules are never looked
|
|
2609
|
+
// up remotely: they are not published.
|
|
2610
|
+
pkgList = await getMvnMetadata(pkgList, jarNSMapping, false, {
|
|
2611
|
+
skipPurls: ownComponentRefs(parentComponent),
|
|
2612
|
+
});
|
|
2052
2613
|
return buildBomNSData(options, pkgList, "maven", {
|
|
2053
2614
|
src: path,
|
|
2054
2615
|
nsMapping: jarNSMapping,
|