@cdxgen/cdxgen 13.3.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 +9 -6
- package/bin/cdxgen.js +90 -26
- package/bin/evinse.js +18 -1
- package/bin/repl.js +86 -0
- package/bin/sign.js +45 -15
- package/bin/tracebom.js +5 -1
- package/bin/validate.js +84 -18
- package/bin/verify.js +155 -86
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +61 -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/index.js +113 -54
- package/lib/cli/jsBom.js +50 -29
- package/lib/cli/jvmBom.js +665 -138
- package/lib/cli/managedBom.js +320 -200
- package/lib/cli/nativeBom.js +139 -30
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +17 -9
- package/lib/core/fs.js +3 -2
- package/lib/core/httpClient.js +159 -24
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- 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 +5 -7
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/cmakeResolver.js +10 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +337 -22
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1967 -788
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +314 -30
- package/lib/ecosystems/jsEvidence.js +85 -24
- package/lib/ecosystems/kosi.js +21 -4
- package/lib/ecosystems/npmutils.js +64 -0
- package/lib/ecosystems/parsers-dotnet.js +567 -151
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +324 -61
- package/lib/ecosystems/parsers-js.js +92 -26
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +182 -55
- package/lib/ecosystems/parsers-python.js +141 -10
- package/lib/ecosystems/parsers-rust.js +4 -4
- 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 +67 -17
- package/lib/ecosystems/rusi.js +22 -9
- 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 +578 -69
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +576 -128
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +171 -48
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/dosai.js +248 -44
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +52 -6
- package/lib/inventory/evidenceUtils.js +154 -109
- package/lib/inventory/fetchBatch.js +574 -38
- 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/rustFormulationParser.js +3 -4
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +8 -8
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +88 -7
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/docker.js +33 -32
- package/lib/managers/oci.js +24 -2
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +16 -2
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- 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 +15 -1
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/index.js +25 -10
- package/lib/validator/reporters/json.js +1 -0
- package/package.json +21 -21
- package/types/lib/audit/index.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.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/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/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.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/cmakeResolver.d.ts +5 -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 +18 -2
- 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 +50 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +15 -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 +44 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +6 -1
- 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 +3 -1
- 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-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 +21 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- 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 +75 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +80 -4
- 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 +82 -10
- 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/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/dosai.d.ts +57 -8
- 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/rustFormulationParser.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 +12 -0
- 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/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +3 -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 +2 -1
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
- 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/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/index.d.ts +8 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.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
|
*
|
|
@@ -179,6 +325,8 @@ export async function createJarBom(path, options) {
|
|
|
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,19 +411,28 @@ 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,
|
|
@@ -250,13 +441,16 @@ export function discoverSbtProjectsFromCmd(basePath, sbtCmd, env) {
|
|
|
250
441
|
env,
|
|
251
442
|
encoding: "utf-8",
|
|
252
443
|
},
|
|
444
|
+
true,
|
|
253
445
|
);
|
|
254
446
|
if (result.status === 0 && !result.error) {
|
|
255
447
|
const { projects, root } = parseSbtProjects(result.stdout || "");
|
|
256
|
-
// Exclude the aggregating root project
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
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);
|
|
260
454
|
if (subprojects.length > 0) {
|
|
261
455
|
if (DEBUG_MODE) {
|
|
262
456
|
console.log(
|
|
@@ -320,6 +514,8 @@ export async function createJavaBom(path, options) {
|
|
|
320
514
|
let tools;
|
|
321
515
|
let possible_misses = false;
|
|
322
516
|
let parallelPropTaskOut = "";
|
|
517
|
+
// Mill daemons already running belong to the user and outlive the scan
|
|
518
|
+
const millDaemonsBefore = findMillDaemons(path);
|
|
323
519
|
try {
|
|
324
520
|
// war/ear mode
|
|
325
521
|
if (path.endsWith(".war") || path.endsWith(".jar")) {
|
|
@@ -329,8 +525,10 @@ export async function createJavaBom(path, options) {
|
|
|
329
525
|
console.log(`Retrieving packages from ${path}`);
|
|
330
526
|
}
|
|
331
527
|
const tempDir = safeMkdtempSync(join(getTmpDir(), "war-deps-"));
|
|
332
|
-
|
|
333
|
-
|
|
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);
|
|
334
532
|
if (pkgList.length) {
|
|
335
533
|
pkgList = await getMvnMetadata(pkgList);
|
|
336
534
|
}
|
|
@@ -456,8 +654,20 @@ export async function createJavaBom(path, options) {
|
|
|
456
654
|
}
|
|
457
655
|
const firstPom = pomFiles.length ? pomFiles[0] : undefined;
|
|
458
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();
|
|
459
661
|
for (const f of pomFiles) {
|
|
460
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
|
+
}
|
|
461
671
|
if (
|
|
462
672
|
isQuarkus &&
|
|
463
673
|
!options.deep &&
|
|
@@ -468,15 +678,40 @@ export async function createJavaBom(path, options) {
|
|
|
468
678
|
}
|
|
469
679
|
continue;
|
|
470
680
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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;
|
|
476
696
|
}
|
|
477
697
|
if (mavenCmd?.endsWith("mvn")) {
|
|
478
698
|
mavenCmd = getMavenCommand(basePath, path, options);
|
|
479
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
|
+
);
|
|
714
|
+
}
|
|
480
715
|
// Should we attempt to resolve class names
|
|
481
716
|
if (options.resolveClass || options.deep) {
|
|
482
717
|
const tmpjarNSMapping = await collectMvnDependencies(
|
|
@@ -490,13 +725,18 @@ export async function createJavaBom(path, options) {
|
|
|
490
725
|
}
|
|
491
726
|
}
|
|
492
727
|
// Use the cyclonedx maven plugin if there is no preference for maven deps tree
|
|
493
|
-
if (!useMavenDepsTree) {
|
|
728
|
+
if (!useMavenDepsTree && !isBuildToolRateLimited("maven")) {
|
|
494
729
|
thoughtLog("The user wants me to use the cyclonedx-maven plugin.");
|
|
495
730
|
console.log(`Executing '${mavenCmd}' in`, basePath);
|
|
496
731
|
result = safeSpawnSync(mavenCmd, mvnArgs, {
|
|
497
732
|
cwd: basePath,
|
|
498
733
|
shell: isWin,
|
|
499
734
|
});
|
|
735
|
+
noteBuildToolRateLimit("maven", commandOutputText(result), {
|
|
736
|
+
command: `${basename(mavenCmd)} ${mvnArgs.join(" ")}`,
|
|
737
|
+
exitCode:
|
|
738
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
739
|
+
});
|
|
500
740
|
// Check if the cyclonedx plugin created the required bom.json file
|
|
501
741
|
// Sometimes the plugin fails silently for complex maven projects
|
|
502
742
|
bomJsonFiles = getAllFiles(
|
|
@@ -522,8 +762,20 @@ export async function createJavaBom(path, options) {
|
|
|
522
762
|
) {
|
|
523
763
|
const tempRoot = getTmpDir();
|
|
524
764
|
const tempDir = safeMkdtempSync(join(tempRoot, "cdxgen-mvn-"));
|
|
525
|
-
|
|
526
|
-
|
|
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
|
+
);
|
|
527
779
|
const tempMvnParentTree = join(
|
|
528
780
|
tempDir,
|
|
529
781
|
"cdxgen-mvn-parent-tree.json",
|
|
@@ -543,14 +795,11 @@ export async function createJavaBom(path, options) {
|
|
|
543
795
|
addArgs = parseMavenArgs(readEnvironmentVariable("MVN_ARGS"));
|
|
544
796
|
mvnTreeArgs = mvnTreeArgs.concat(addArgs);
|
|
545
797
|
}
|
|
546
|
-
|
|
547
|
-
if (safeExistsSync(settingsXml)) {
|
|
548
|
-
mvnTreeArgs.push("-s");
|
|
549
|
-
mvnTreeArgs.push(settingsXml);
|
|
550
|
-
}
|
|
798
|
+
mvnTreeArgs = mvnTreeArgs.concat(projectSettings.args);
|
|
551
799
|
// For the first pom alone, we need to execute first in non-recursive mode to capture
|
|
552
800
|
// the parent component. Then, we execute all of them in recursive mode
|
|
553
|
-
|
|
801
|
+
// A rate limit the --deep copy-dependencies run met ends it too.
|
|
802
|
+
if (f === firstPom && !isBuildToolRateLimited("maven")) {
|
|
554
803
|
thoughtLog(
|
|
555
804
|
"What is the parent component here? Let's use maven command to find out.",
|
|
556
805
|
);
|
|
@@ -560,14 +809,19 @@ export async function createJavaBom(path, options) {
|
|
|
560
809
|
`-DoutputFile=${tempMvnParentTree}`,
|
|
561
810
|
"-DoutputType=json",
|
|
562
811
|
];
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
812
|
+
findParentComponentArgs = findParentComponentArgs.concat(
|
|
813
|
+
addArgs,
|
|
814
|
+
projectSettings.args,
|
|
815
|
+
);
|
|
567
816
|
result = safeSpawnSync(mavenCmd, findParentComponentArgs, {
|
|
568
817
|
cwd: basePath,
|
|
569
818
|
shell: isWin,
|
|
570
819
|
});
|
|
820
|
+
noteBuildToolRateLimit("maven", commandOutputText(result), {
|
|
821
|
+
command: `${basename(mavenCmd)} ${findParentComponentArgs.join(" ")}`,
|
|
822
|
+
exitCode:
|
|
823
|
+
typeof result.status === "number" ? result.status : undefined,
|
|
824
|
+
});
|
|
571
825
|
// If json is empty or unparseable, fallback to text parsing
|
|
572
826
|
let emptyJson = !safeExistsSync(tempMvnParentTree);
|
|
573
827
|
if (safeExistsSync(tempMvnParentTree)) {
|
|
@@ -583,16 +837,15 @@ export async function createJavaBom(path, options) {
|
|
|
583
837
|
}
|
|
584
838
|
}
|
|
585
839
|
}
|
|
586
|
-
if (
|
|
840
|
+
if (
|
|
841
|
+
!isBuildToolRateLimited("maven") &&
|
|
842
|
+
(result.status !== 0 || result.error || emptyJson)
|
|
843
|
+
) {
|
|
587
844
|
findParentComponentArgs = [
|
|
588
845
|
"dependency:tree",
|
|
589
846
|
"-N",
|
|
590
847
|
`-DoutputFile=${tempMvnParentTreeText}`,
|
|
591
|
-
];
|
|
592
|
-
if (addArgs.length) {
|
|
593
|
-
findParentComponentArgs =
|
|
594
|
-
findParentComponentArgs.concat(addArgs);
|
|
595
|
-
}
|
|
848
|
+
].concat(addArgs, projectSettings.args);
|
|
596
849
|
result = safeSpawnSync(mavenCmd, findParentComponentArgs, {
|
|
597
850
|
cwd: basePath,
|
|
598
851
|
shell: isWin,
|
|
@@ -632,49 +885,60 @@ export async function createJavaBom(path, options) {
|
|
|
632
885
|
`Executing '${basename(mavenCmd)} dependency:tree ...' in ${basePath}`,
|
|
633
886
|
);
|
|
634
887
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (safeExistsSync(tempMvnTree)) {
|
|
641
|
-
const mvnTreeString = readFileSync(tempMvnTree, {
|
|
642
|
-
encoding: "utf-8",
|
|
643
|
-
});
|
|
644
|
-
const parsedList = parseMavenTreeJson(mvnTreeString, f);
|
|
645
|
-
if (!parsedList?.pkgList?.length) {
|
|
646
|
-
emptyJson = true;
|
|
647
|
-
// Remove the invalid json file so text output is preferred below.
|
|
648
|
-
if (safeExistsSync(tempMvnTree)) {
|
|
649
|
-
safeUnlinkSync(tempMvnTree);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
if (result.status !== 0 || result.error || emptyJson) {
|
|
654
|
-
mvnTreeArgs = [
|
|
655
|
-
"dependency:tree",
|
|
656
|
-
`-DoutputFile=${tempMvnTreeText}`,
|
|
657
|
-
];
|
|
658
|
-
if (addArgs.length) {
|
|
659
|
-
mvnTreeArgs = mvnTreeArgs.concat(addArgs);
|
|
660
|
-
}
|
|
661
|
-
if (safeExistsSync(settingsXml)) {
|
|
662
|
-
mvnTreeArgs.push("-s");
|
|
663
|
-
mvnTreeArgs.push(settingsXml);
|
|
664
|
-
}
|
|
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 {
|
|
665
893
|
result = safeSpawnSync(mavenCmd, mvnTreeArgs, {
|
|
666
894
|
cwd: basePath,
|
|
667
895
|
shell: isWin,
|
|
668
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
|
+
}
|
|
669
934
|
}
|
|
670
935
|
if (result.status !== 0 || result.error) {
|
|
671
936
|
possible_misses = true;
|
|
672
937
|
// Our approach to recursively invoking the maven plugin for each sub-module is bound to result in failures
|
|
673
938
|
// These could be due to a range of reasons that are covered below.
|
|
674
939
|
if (
|
|
675
|
-
|
|
676
|
-
DEBUG_MODE ||
|
|
677
|
-
PREFER_MAVEN_DEPS_TREE
|
|
940
|
+
!isBuildToolRateLimited("maven") &&
|
|
941
|
+
(pomFiles.length === 1 || DEBUG_MODE || PREFER_MAVEN_DEPS_TREE)
|
|
678
942
|
) {
|
|
679
943
|
if (result.stdout) {
|
|
680
944
|
console.log(result.stdout);
|
|
@@ -773,16 +1037,18 @@ export async function createJavaBom(path, options) {
|
|
|
773
1037
|
pkgList = pkgList.concat(dlist);
|
|
774
1038
|
}
|
|
775
1039
|
} else {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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);
|
|
786
1052
|
const dlist = parsedList.pkgList || [];
|
|
787
1053
|
const tmpParentComponent = dlist.splice(0, 1)[0];
|
|
788
1054
|
if (tmpParentComponent) {
|
|
@@ -1219,6 +1485,14 @@ export async function createJavaBom(path, options) {
|
|
|
1219
1485
|
);
|
|
1220
1486
|
const allOutputs = [];
|
|
1221
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
|
+
}
|
|
1222
1496
|
if (DEBUG_MODE) {
|
|
1223
1497
|
console.log(
|
|
1224
1498
|
`Executing ${gradleCmd} with arguments ${gradleArg.join(" ").substring(0, 150)}... in ${gradleRootPath}`,
|
|
@@ -1234,6 +1508,13 @@ export async function createJavaBom(path, options) {
|
|
|
1234
1508
|
cwd: gradleRootPath,
|
|
1235
1509
|
shell: isWin,
|
|
1236
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
|
+
});
|
|
1237
1518
|
if (sresult.status !== 0 || sresult.error) {
|
|
1238
1519
|
if (options.failOnError || DEBUG_MODE) {
|
|
1239
1520
|
console.error(sresult.stdout, sresult.stderr);
|
|
@@ -1644,8 +1925,6 @@ export async function createJavaBom(path, options) {
|
|
|
1644
1925
|
`${options.multiProject ? "**/" : ""}build.sbt.lock`,
|
|
1645
1926
|
options,
|
|
1646
1927
|
);
|
|
1647
|
-
const tempCacheDir = safeMkdtempSync(join(getTmpDir(), "sbt-cache-"));
|
|
1648
|
-
safeMkdirSync(tempCacheDir, { recursive: true });
|
|
1649
1928
|
if (
|
|
1650
1929
|
sbtProjects?.length &&
|
|
1651
1930
|
isPackageManagerAllowed(
|
|
@@ -1711,20 +1990,46 @@ export async function createJavaBom(path, options) {
|
|
|
1711
1990
|
}
|
|
1712
1991
|
}
|
|
1713
1992
|
safeWriteSync(tempSbtPlugins, sbtPluginDefinition);
|
|
1714
|
-
|
|
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.
|
|
1715
1996
|
const env = { ...process.env };
|
|
1716
|
-
// We need to collect the jars from the cache
|
|
1717
|
-
if (options.deep) {
|
|
1718
|
-
sbtExtraArgs = " updateClassifiers";
|
|
1719
|
-
env["COURSIER_CACHE"] = tempCacheDir;
|
|
1720
|
-
env["SBT_IVY_HOME"] = tempCacheDir;
|
|
1721
|
-
}
|
|
1722
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
|
+
}
|
|
1723
2008
|
const basePath = sbtProjects[i];
|
|
1724
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);
|
|
1725
2027
|
let sbtArgs = [];
|
|
1726
2028
|
let pluginFile = null;
|
|
2029
|
+
let pluginAdded = false;
|
|
1727
2030
|
const subDlFiles = [];
|
|
2031
|
+
const failedSubprojects = [];
|
|
2032
|
+
let subprojects = [];
|
|
1728
2033
|
if (standalonePluginFile) {
|
|
1729
2034
|
sbtArgs = [
|
|
1730
2035
|
`-addPluginSbtFile=${tempSbtPlugins}`,
|
|
@@ -1738,15 +2043,23 @@ export async function createJavaBom(path, options) {
|
|
|
1738
2043
|
// `dependencyTree` fan-out yields a far more complete tree than a
|
|
1739
2044
|
// single root resolution (which only captures the root project's own
|
|
1740
2045
|
// libraryDependencies). See https://github.com/cdxgen/cdxgen/issues/4291
|
|
1741
|
-
|
|
2046
|
+
subprojects = discoverSbtProjectsFromCmd(
|
|
1742
2047
|
basePath,
|
|
1743
2048
|
SBT_CMD,
|
|
1744
2049
|
env,
|
|
2050
|
+
sbtLauncherArgs,
|
|
1745
2051
|
);
|
|
1746
|
-
if (
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
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'"];
|
|
1750
2063
|
for (const sp of subprojects) {
|
|
1751
2064
|
const subDlFile = join(tempDir, `dl-${i}-${sp}.tmp`);
|
|
1752
2065
|
subDlFiles.push(subDlFile);
|
|
@@ -1756,21 +2069,27 @@ export async function createJavaBom(path, options) {
|
|
|
1756
2069
|
}
|
|
1757
2070
|
} else if (useSlashSyntax) {
|
|
1758
2071
|
sbtArgs = [
|
|
1759
|
-
`'set ThisBuild / asciiGraphWidth := 800'
|
|
2072
|
+
`'set ThisBuild / asciiGraphWidth := 800' "dependencyTree / toFile ${dlFile} --force"`,
|
|
1760
2073
|
];
|
|
1761
2074
|
subDlFiles.push(dlFile);
|
|
1762
2075
|
} else {
|
|
1763
2076
|
sbtArgs = [
|
|
1764
|
-
`'set asciiGraphWidth in ThisBuild := 800'
|
|
2077
|
+
`'set asciiGraphWidth in ThisBuild := 800' "dependencyTree::toFile ${dlFile} --force"`,
|
|
1765
2078
|
];
|
|
1766
2079
|
subDlFiles.push(dlFile);
|
|
1767
2080
|
}
|
|
1768
|
-
|
|
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
|
+
}
|
|
1769
2088
|
}
|
|
1770
2089
|
// Run sbt in non-interactive batch mode so it never blocks waiting for
|
|
1771
2090
|
// input on stdin (which can otherwise appear as an indefinite hang) and
|
|
1772
2091
|
// does not emit ANSI colour codes into the dependency tree output.
|
|
1773
|
-
sbtArgs = ["-batch", "-no-colors", ...sbtArgs];
|
|
2092
|
+
sbtArgs = [...sbtLauncherArgs, "-batch", "-no-colors", ...sbtArgs];
|
|
1774
2093
|
console.log(
|
|
1775
2094
|
"Executing",
|
|
1776
2095
|
SBT_CMD,
|
|
@@ -1781,16 +2100,31 @@ export async function createJavaBom(path, options) {
|
|
|
1781
2100
|
tempSbtgDir,
|
|
1782
2101
|
);
|
|
1783
2102
|
// Note that the command has to be invoked with `shell: true` to properly execut sbt
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
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,
|
|
1791
2123
|
});
|
|
1792
2124
|
if (result.status !== 0 || result.error) {
|
|
1793
|
-
|
|
2125
|
+
if (!isBuildToolRateLimited("sbt")) {
|
|
2126
|
+
console.error(result.stdout, result.stderr);
|
|
2127
|
+
}
|
|
1794
2128
|
recordDegradation("jvm.sbt.no-lockfile", {
|
|
1795
2129
|
ecosystem: "java",
|
|
1796
2130
|
tool: "sbt",
|
|
@@ -1801,15 +2135,20 @@ export async function createJavaBom(path, options) {
|
|
|
1801
2135
|
detail:
|
|
1802
2136
|
"The sbt dependencyTree command did not succeed, so the declared sbt dependencies are the only evidence available.",
|
|
1803
2137
|
});
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
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
|
+
}
|
|
1813
2152
|
deferFailOnError(options, {
|
|
1814
2153
|
ecosystem: "java",
|
|
1815
2154
|
tool: "sbt",
|
|
@@ -1819,13 +2158,95 @@ export async function createJavaBom(path, options) {
|
|
|
1819
2158
|
detail: "the sbt dependencyTree command failed",
|
|
1820
2159
|
});
|
|
1821
2160
|
}
|
|
1822
|
-
|
|
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) {
|
|
1823
2235
|
cleanupPlugin(basePath, pluginFile);
|
|
1824
2236
|
}
|
|
1825
2237
|
const sbtSubprojectRoots = [];
|
|
1826
2238
|
for (const subDlFile of subDlFiles) {
|
|
1827
2239
|
if (safeExistsSync(subDlFile)) {
|
|
1828
|
-
|
|
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);
|
|
1829
2250
|
if (retMap.pkgList?.length) {
|
|
1830
2251
|
const tmpParentComponent = retMap.pkgList.splice(0, 1)[0];
|
|
1831
2252
|
tmpParentComponent.type = "application";
|
|
@@ -1873,19 +2294,38 @@ export async function createJavaBom(path, options) {
|
|
|
1873
2294
|
"bom-ref": decodeURIComponent(rootPurl),
|
|
1874
2295
|
};
|
|
1875
2296
|
parentComponent = rootComponent;
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
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
|
+
);
|
|
1882
2313
|
} else if (sbtSubprojectRoots.length > 0 && !parentComponent) {
|
|
1883
2314
|
parentComponent = sbtSubprojectRoots[0];
|
|
1884
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
|
+
}
|
|
1885
2323
|
}
|
|
1886
2324
|
|
|
1887
|
-
//
|
|
1888
|
-
|
|
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 });
|
|
1889
2329
|
} // else
|
|
1890
2330
|
|
|
1891
2331
|
if (DEBUG_MODE) {
|
|
@@ -1893,7 +2333,10 @@ export async function createJavaBom(path, options) {
|
|
|
1893
2333
|
}
|
|
1894
2334
|
// Should we attempt to resolve class names
|
|
1895
2335
|
if (options.resolveClass || options.deep) {
|
|
1896
|
-
const tmpjarNSMapping = await
|
|
2336
|
+
const tmpjarNSMapping = await collectSbtDependencyJars(
|
|
2337
|
+
pkgList,
|
|
2338
|
+
jarNSMapping,
|
|
2339
|
+
);
|
|
1897
2340
|
if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
|
|
1898
2341
|
jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
|
|
1899
2342
|
}
|
|
@@ -1917,12 +2360,6 @@ export async function createJavaBom(path, options) {
|
|
|
1917
2360
|
}
|
|
1918
2361
|
}
|
|
1919
2362
|
}
|
|
1920
|
-
if (tempCacheDir?.startsWith(getTmpDir())) {
|
|
1921
|
-
safeRmSync(tempCacheDir, {
|
|
1922
|
-
recursive: true,
|
|
1923
|
-
force: true,
|
|
1924
|
-
});
|
|
1925
|
-
}
|
|
1926
2363
|
|
|
1927
2364
|
if (
|
|
1928
2365
|
millFiles?.length &&
|
|
@@ -1940,6 +2377,12 @@ export async function createJavaBom(path, options) {
|
|
|
1940
2377
|
options,
|
|
1941
2378
|
);
|
|
1942
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";
|
|
1943
2386
|
const millCommonArgs = [
|
|
1944
2387
|
"--color",
|
|
1945
2388
|
"false",
|
|
@@ -1951,7 +2394,7 @@ export async function createJavaBom(path, options) {
|
|
|
1951
2394
|
if (!["true", "1"].includes(readEnvironmentVariable("MILL_USE_SERVER"))) {
|
|
1952
2395
|
millCommonArgs.unshift("--no-server");
|
|
1953
2396
|
}
|
|
1954
|
-
const millArgs = [...millCommonArgs,
|
|
2397
|
+
const millArgs = [...millCommonArgs, `__.${millTreeTask}`];
|
|
1955
2398
|
if (DEBUG_MODE) {
|
|
1956
2399
|
console.log("Executing", millCmd, "in", millRootPath);
|
|
1957
2400
|
}
|
|
@@ -1960,6 +2403,11 @@ export async function createJavaBom(path, options) {
|
|
|
1960
2403
|
shell: isWin,
|
|
1961
2404
|
maxBuffer: MAX_BUFFER * 10,
|
|
1962
2405
|
});
|
|
2406
|
+
noteBuildToolRateLimit("mill", commandOutputText(sresult), {
|
|
2407
|
+
command: `${basename(millCmd)} ${millArgs.join(" ")}`,
|
|
2408
|
+
exitCode:
|
|
2409
|
+
typeof sresult.status === "number" ? sresult.status : undefined,
|
|
2410
|
+
});
|
|
1963
2411
|
if (sresult.status !== 0 || sresult.error) {
|
|
1964
2412
|
if (options.failOnError || DEBUG_MODE) {
|
|
1965
2413
|
console.error(sresult.stdout, sresult.stderr);
|
|
@@ -1969,10 +2417,14 @@ export async function createJavaBom(path, options) {
|
|
|
1969
2417
|
tool: "mill",
|
|
1970
2418
|
exitCode:
|
|
1971
2419
|
typeof sresult.status === "number" ? sresult.status : undefined,
|
|
1972
|
-
detail: "the mill
|
|
2420
|
+
detail: "the mill dependency tree command failed",
|
|
1973
2421
|
});
|
|
1974
2422
|
}
|
|
1975
|
-
const millResolveArgs = [
|
|
2423
|
+
const millResolveArgs = [
|
|
2424
|
+
...millCommonArgs,
|
|
2425
|
+
"resolve",
|
|
2426
|
+
`__.${millTreeTask}`,
|
|
2427
|
+
];
|
|
1976
2428
|
if (DEBUG_MODE) {
|
|
1977
2429
|
console.log(
|
|
1978
2430
|
"Executing",
|
|
@@ -1982,6 +2434,9 @@ export async function createJavaBom(path, options) {
|
|
|
1982
2434
|
millRootPath,
|
|
1983
2435
|
);
|
|
1984
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.
|
|
1985
2440
|
sresult = safeSpawnSync(millCmd, millResolveArgs, {
|
|
1986
2441
|
cwd: millRootPath,
|
|
1987
2442
|
shell: isWin,
|
|
@@ -2004,6 +2459,8 @@ export async function createJavaBom(path, options) {
|
|
|
2004
2459
|
const modules = sstdout
|
|
2005
2460
|
.trim()
|
|
2006
2461
|
.split("\n")
|
|
2462
|
+
// Mill 1.x prints headers around the module list
|
|
2463
|
+
.filter((a) => a.endsWith(`.${millTreeTask}`))
|
|
2007
2464
|
.map((a) => a.substring(0, a.lastIndexOf(".")))
|
|
2008
2465
|
.filter((a) =>
|
|
2009
2466
|
["true", "1"].includes(readEnvironmentVariable("MILL_EXCLUDE_TEST"))
|
|
@@ -2016,7 +2473,13 @@ export async function createJavaBom(path, options) {
|
|
|
2016
2473
|
relations.set(parentComponent["bom-ref"], []);
|
|
2017
2474
|
for (const module of modules) {
|
|
2018
2475
|
moduleBomRefs.push(
|
|
2019
|
-
parseMillDependency(
|
|
2476
|
+
parseMillDependency(
|
|
2477
|
+
module,
|
|
2478
|
+
packages,
|
|
2479
|
+
relations,
|
|
2480
|
+
millRootPath,
|
|
2481
|
+
`${millTreeTask}.log`,
|
|
2482
|
+
),
|
|
2020
2483
|
);
|
|
2021
2484
|
}
|
|
2022
2485
|
for (const module of moduleBomRefs) {
|
|
@@ -2069,6 +2532,34 @@ export async function createJavaBom(path, options) {
|
|
|
2069
2532
|
}
|
|
2070
2533
|
}
|
|
2071
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
|
+
}
|
|
2072
2563
|
} catch (err) {
|
|
2073
2564
|
if (!isDeferredFailOnError(err)) {
|
|
2074
2565
|
throw err;
|
|
@@ -2078,11 +2569,47 @@ export async function createJavaBom(path, options) {
|
|
|
2078
2569
|
thoughtLog(
|
|
2079
2570
|
"Completing the java BOM with the components collected before the deferred failure.",
|
|
2080
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
|
+
}
|
|
2081
2603
|
}
|
|
2082
2604
|
// Deduplication and metadata belong to whatever was collected, so they run
|
|
2083
2605
|
// on the deferred path too.
|
|
2084
2606
|
pkgList = trimComponents(pkgList);
|
|
2085
|
-
|
|
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
|
+
});
|
|
2086
2613
|
return buildBomNSData(options, pkgList, "maven", {
|
|
2087
2614
|
src: path,
|
|
2088
2615
|
nsMapping: jarNSMapping,
|