@cdxgen/cdxgen 13.0.0 → 13.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -13
- package/bin/audit.js +6 -0
- package/bin/cdxgen.js +140 -8
- package/bin/convert.js +7 -0
- package/bin/evinse.js +1 -1
- package/bin/hbom.js +13 -4
- package/bin/tracebom.js +9 -1
- package/data/component-tags.json +8 -1
- package/data/frameworks-list.json +86 -1
- package/data/remediations.json +1144 -0
- package/data/rules/asar-archives.yaml +1 -1
- package/data/rules/build-fidelity.yaml +782 -0
- package/data/rules/package-integrity.yaml +13 -13
- package/lib/audit/index.js +48 -4
- package/lib/audit/scoring.js +2 -8
- package/lib/cli/bomAssembly.js +83 -15
- package/lib/cli/cliOptions.js +124 -7
- package/lib/cli/index.js +76 -48
- package/lib/cli/jsBom.js +32 -3
- package/lib/cli/jvmBom.js +1494 -1315
- package/lib/cli/managedBom.js +97 -28
- package/lib/cli/nativeBom.js +189 -10
- package/lib/core/buildLedger.js +799 -0
- package/lib/core/deferredExit.js +141 -0
- package/lib/core/env.js +69 -1
- package/lib/core/fs.js +60 -0
- package/lib/core/logger.js +76 -25
- package/lib/core/severity.js +16 -0
- package/lib/core/syncFileWriter.js +106 -0
- package/lib/core/ui.js +46 -2
- package/lib/ecosystems/ecosystems.js +9 -0
- package/lib/ecosystems/gradleutils.js +355 -15
- package/lib/ecosystems/parsers-js.js +26 -0
- package/lib/ecosystems/parsers-jvm.js +9 -0
- package/lib/ecosystems/parsers-misc.js +16 -2
- package/lib/ecosystems/parsers-python.js +171 -35
- package/lib/ecosystems/parsers-rust.js +12 -2
- package/lib/ecosystems/pypiClassifiers.js +326 -0
- package/lib/ecosystems/pythonutils.js +49 -1
- package/lib/ecosystems/rubyutils.js +13 -0
- package/lib/ecosystems/utils.js +9 -1
- package/lib/evinser/evinser.js +30 -12
- package/lib/helpers/remote/dependency-track.js +81 -14
- package/lib/inventory/auditCategories.js +9 -0
- package/lib/inventory/depsUtils.js +72 -0
- package/lib/inventory/dosai.js +431 -22
- package/lib/inventory/envcontext.js +211 -19
- package/lib/inventory/fetchBatch.js +57 -0
- package/lib/inventory/jvmToolEnv.js +593 -0
- package/lib/inventory/osinfo.js +123 -16
- package/lib/inventory/table.js +9 -5
- package/lib/inventory/toolRequirements.js +903 -0
- package/lib/managers/binary.js +216 -40
- package/lib/managers/docker.js +26 -5
- package/lib/server/server.js +1 -1
- package/lib/stages/postgen/annotationBuilder.js +97 -0
- package/lib/stages/postgen/auditBom.js +76 -79
- package/lib/stages/postgen/introspection/annotate.js +320 -0
- package/lib/stages/postgen/introspection/commandFacts.js +293 -0
- package/lib/stages/postgen/introspection/emit.js +192 -0
- package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
- package/lib/stages/postgen/introspection/markers.js +227 -0
- package/lib/stages/postgen/introspection/reflect.js +1214 -0
- package/lib/stages/postgen/introspection/report.js +1208 -0
- package/lib/stages/postgen/introspection/score.js +1067 -0
- package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
- package/lib/stages/postgen/parentEdge.js +156 -0
- package/lib/stages/postgen/postgen.js +115 -2
- package/lib/stages/postgen/ruleEngine.js +17 -0
- package/lib/stages/pregen/envAudit.js +42 -2
- package/lib/stages/pregen/pregen.js +1171 -6
- package/lib/validator/bomValidator.js +11 -2
- package/package.json +14 -14
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/audit/scoring.d.ts +2 -8
- package/types/lib/audit/scoring.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +14 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +2 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +8 -0
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/buildLedger.d.ts +272 -0
- package/types/lib/core/buildLedger.d.ts.map +1 -0
- package/types/lib/core/deferredExit.d.ts +84 -0
- package/types/lib/core/deferredExit.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +21 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/logger.d.ts +14 -1
- package/types/lib/core/logger.d.ts.map +1 -1
- package/types/lib/core/severity.d.ts +11 -0
- package/types/lib/core/severity.d.ts.map +1 -0
- package/types/lib/core/syncFileWriter.d.ts +33 -0
- package/types/lib/core/syncFileWriter.d.ts.map +1 -0
- package/types/lib/core/ui.d.ts +1 -1
- package/types/lib/core/ui.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +33 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
- package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +20 -0
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/utils.d.ts +4 -4
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +5 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +8 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +20 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +9 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +189 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts +83 -2
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/table.d.ts +4 -1
- package/types/lib/inventory/table.d.ts.map +1 -1
- package/types/lib/inventory/toolRequirements.d.ts +216 -0
- package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +71 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
- package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
- package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
- package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
- package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
- package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +57 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
- package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
- package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
- package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
- package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
- package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +2 -2
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +131 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -8,7 +8,16 @@ import {
|
|
|
8
8
|
isDryRun,
|
|
9
9
|
isSecureMode,
|
|
10
10
|
readEnvironmentVariable,
|
|
11
|
+
recordObservedActivity,
|
|
11
12
|
} from "../../core/activity.js";
|
|
13
|
+
import {
|
|
14
|
+
commandOutputText,
|
|
15
|
+
isLedgerEnabled,
|
|
16
|
+
LEDGER_EVENT_IMPACTS,
|
|
17
|
+
LEDGER_EVENT_KINDS,
|
|
18
|
+
recordLedgerEvent,
|
|
19
|
+
} from "../../core/buildLedger.js";
|
|
20
|
+
import { deferFailOnError } from "../../core/deferredExit.js";
|
|
12
21
|
import { hasAnyProjectType, isFeatureEnabled } from "../../core/env.js";
|
|
13
22
|
import {
|
|
14
23
|
getAllFiles,
|
|
@@ -21,6 +30,7 @@ import {
|
|
|
21
30
|
import { isMac, isWin } from "../../core/paths.js";
|
|
22
31
|
import {
|
|
23
32
|
bundleInstallWithDocker,
|
|
33
|
+
collectJavaInfo,
|
|
24
34
|
collectRubyInfo,
|
|
25
35
|
getOrInstallNvmTool,
|
|
26
36
|
installRubyBundler,
|
|
@@ -29,10 +39,33 @@ import {
|
|
|
29
39
|
isNvmAvailable,
|
|
30
40
|
isRbenvAvailable,
|
|
31
41
|
isSdkmanAvailable,
|
|
42
|
+
isSdkmanToolAvailable,
|
|
32
43
|
performBundleInstall,
|
|
33
44
|
runSwiftCommand,
|
|
34
45
|
SDKMAN_JAVA_TOOL_ALIASES,
|
|
35
46
|
} from "../../inventory/envcontext.js";
|
|
47
|
+
import {
|
|
48
|
+
compareSdkmanVersions,
|
|
49
|
+
DEFAULT_JAVA_MAJOR,
|
|
50
|
+
detectProjectJavaMajor,
|
|
51
|
+
determineRequiredJavaVersion,
|
|
52
|
+
extractJavaMajor,
|
|
53
|
+
minimumGradleVersionForJava,
|
|
54
|
+
parseJvmToolProjectType,
|
|
55
|
+
parseSdkmanrc,
|
|
56
|
+
readGradleWrapperVersion,
|
|
57
|
+
readMavenWrapperVersion,
|
|
58
|
+
readSbtBuildPropertiesVersion,
|
|
59
|
+
readToolVersionEnvHint,
|
|
60
|
+
readToolVersionOverride,
|
|
61
|
+
resolvePartialVersion,
|
|
62
|
+
} from "../../inventory/jvmToolEnv.js";
|
|
63
|
+
import {
|
|
64
|
+
classifyProbeResult,
|
|
65
|
+
describeSpawnRestriction,
|
|
66
|
+
parseGemfileLockBundlerVersion,
|
|
67
|
+
readDeclaredToolRequirements,
|
|
68
|
+
} from "../../inventory/toolRequirements.js";
|
|
36
69
|
|
|
37
70
|
/**
|
|
38
71
|
* Method to prepare the build environment for BOM generation purposes.
|
|
@@ -41,18 +74,29 @@ import {
|
|
|
41
74
|
* @param {Object} options CLI options
|
|
42
75
|
*/
|
|
43
76
|
export function prepareEnv(filePath, options) {
|
|
44
|
-
if (!options.projectType || isSecureMode
|
|
77
|
+
if (!options.projectType || isSecureMode) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (filePath) {
|
|
81
|
+
filePath = resolve(filePath);
|
|
82
|
+
}
|
|
83
|
+
// The JVM preparation provisions the JDK ahead of the build tools, so an
|
|
84
|
+
// alias it already installed must not be installed a second time here.
|
|
85
|
+
// It runs under dry-run too, where it reports the toolchain it would set up
|
|
86
|
+
// instead of setting it up.
|
|
87
|
+
const jdkAliasHandled = prepareJvmBuildEnv(filePath, options);
|
|
88
|
+
recordDeclaredToolRequirements(filePath);
|
|
89
|
+
if (isDryRun) {
|
|
45
90
|
return;
|
|
46
91
|
}
|
|
47
92
|
for (const pt of options.projectType) {
|
|
48
93
|
if (SDKMAN_JAVA_TOOL_ALIASES[pt]) {
|
|
49
|
-
|
|
94
|
+
if (pt !== jdkAliasHandled) {
|
|
95
|
+
prepareSdkmanBuild(pt);
|
|
96
|
+
}
|
|
50
97
|
break;
|
|
51
98
|
}
|
|
52
99
|
}
|
|
53
|
-
if (filePath) {
|
|
54
|
-
filePath = resolve(filePath);
|
|
55
|
-
}
|
|
56
100
|
// Check the pre-requisites for various types
|
|
57
101
|
preparePythonEnv(filePath, options);
|
|
58
102
|
prepareNodeEnv(filePath, options);
|
|
@@ -60,6 +104,72 @@ export function prepareEnv(filePath, options) {
|
|
|
60
104
|
prepareRubyEnv(filePath, options);
|
|
61
105
|
}
|
|
62
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Record the tool requirements the project declares through its well-known
|
|
109
|
+
* tool pin files (`.tool-versions`, `.nvmrc`, `package.json`, `go.mod`, …),
|
|
110
|
+
* one `tool.expected` event per requirement. Recording is the only purpose of
|
|
111
|
+
* the file walk, so it is skipped entirely when the ledger is disabled.
|
|
112
|
+
*
|
|
113
|
+
* @param {String} filePath Project path
|
|
114
|
+
*/
|
|
115
|
+
function recordDeclaredToolRequirements(filePath) {
|
|
116
|
+
if (!isLedgerEnabled() || !filePath) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
for (const requirement of readDeclaredToolRequirements(filePath)) {
|
|
120
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
121
|
+
ecosystem: requirement.ecosystem,
|
|
122
|
+
tool: requirement.tool,
|
|
123
|
+
wanted: requirement.wanted,
|
|
124
|
+
source: requirement.source,
|
|
125
|
+
path: requirement.path,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
recordDeclaredJvmToolRequirements(filePath);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Record the JVM tool versions a repository pins through its build tool
|
|
133
|
+
* wrappers and `.sdkmanrc`.
|
|
134
|
+
*
|
|
135
|
+
* These readers live in `jvmToolEnv.js`, which imports the shared comparator
|
|
136
|
+
* from `toolRequirements.js`; reading them here rather than inside
|
|
137
|
+
* `readDeclaredToolRequirements` keeps that dependency one-directional.
|
|
138
|
+
*
|
|
139
|
+
* @param {String} filePath Project path
|
|
140
|
+
* @returns {void}
|
|
141
|
+
*/
|
|
142
|
+
function recordDeclaredJvmToolRequirements(filePath) {
|
|
143
|
+
const declarations = [
|
|
144
|
+
["gradle", readGradleWrapperVersion(filePath), "gradle-wrapper.properties"],
|
|
145
|
+
["maven", readMavenWrapperVersion(filePath), "maven-wrapper.properties"],
|
|
146
|
+
[
|
|
147
|
+
"sbt",
|
|
148
|
+
readSbtBuildPropertiesVersion(filePath),
|
|
149
|
+
"project/build.properties",
|
|
150
|
+
],
|
|
151
|
+
];
|
|
152
|
+
const sdkmanPins = parseSdkmanrc(filePath) || {};
|
|
153
|
+
for (const [tool, wanted] of Object.entries(sdkmanPins)) {
|
|
154
|
+
declarations.push([tool, wanted, ".sdkmanrc"]);
|
|
155
|
+
}
|
|
156
|
+
for (const [tool, declared, source] of declarations) {
|
|
157
|
+
// The wrapper readers return `{ version, distributionUrl }` while the sbt
|
|
158
|
+
// and `.sdkmanrc` readers return the version directly.
|
|
159
|
+
const wanted = typeof declared === "string" ? declared : declared?.version;
|
|
160
|
+
if (!wanted) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
164
|
+
ecosystem: "java",
|
|
165
|
+
tool,
|
|
166
|
+
wanted: `${wanted}`,
|
|
167
|
+
source,
|
|
168
|
+
path: join(filePath, source),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
63
173
|
/**
|
|
64
174
|
* Method to prepare sdkman build environment for BOM generation purposes.
|
|
65
175
|
*
|
|
@@ -76,6 +186,912 @@ export function prepareSdkmanBuild(projectType) {
|
|
|
76
186
|
return installSdkmanTool(toolType, projectType);
|
|
77
187
|
}
|
|
78
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Binary names used to probe and select each JVM build tool, keyed by sdkman
|
|
191
|
+
* candidate name.
|
|
192
|
+
*
|
|
193
|
+
* @type {Object<string, Object<string, string>>}
|
|
194
|
+
*/
|
|
195
|
+
const JVM_TOOL_BINARIES = {
|
|
196
|
+
maven: { env: "MVN_CMD", command: "mvn", windowsCommand: "mvn.cmd" },
|
|
197
|
+
gradle: {
|
|
198
|
+
env: "GRADLE_CMD",
|
|
199
|
+
command: "gradle",
|
|
200
|
+
windowsCommand: "gradle.bat",
|
|
201
|
+
},
|
|
202
|
+
sbt: { env: "SBT_CMD", command: "sbt", windowsCommand: "sbt.bat" },
|
|
203
|
+
// No env entry: scala runs are driven through SCALA_HOME set by the installer.
|
|
204
|
+
scala: { command: "scala", windowsCommand: "scala.bat" },
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Version prefix used to pick a default version when a tool is required but
|
|
209
|
+
* nothing pins it. Maven is restricted to the 3.9 line because it keeps JDK
|
|
210
|
+
* 8-25 compatibility; other tools take the newest stable release.
|
|
211
|
+
*
|
|
212
|
+
* @type {Object<string, string>}
|
|
213
|
+
*/
|
|
214
|
+
const JVM_TOOL_DEFAULT_PREFIXES = {
|
|
215
|
+
maven: "3.9",
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/** sdkman candidates cdxgen can provision for JVM builds. */
|
|
219
|
+
const JVM_BUILD_TOOLS = ["maven", "gradle", "sbt", "scala"];
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* True while `CDXGEN_JVM_TOOL_PINNED` holds a value cdxgen set itself, which
|
|
223
|
+
* makes it safe to clear on the next preparation run. Long-lived processes
|
|
224
|
+
* such as the server prepare the environment once per request.
|
|
225
|
+
*/
|
|
226
|
+
let cdxgenOwnsPinnedToolEnv = false;
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Method to detect and provision JVM build tools (maven, gradle, sbt, scala)
|
|
230
|
+
* plus a compatible JDK before BOM generation.
|
|
231
|
+
*
|
|
232
|
+
* Two styles are supported:
|
|
233
|
+
* - Explicit: a versioned project type such as `-t maven3.9.9` installs that
|
|
234
|
+
* exact sdkman version and pins it for the generation step.
|
|
235
|
+
* - Automatic: with the `jvm-tool-setup` feature flag, tool versions are
|
|
236
|
+
* derived from repo markers (`.sdkmanrc`, build tool wrappers,
|
|
237
|
+
* `project/build.properties`) and missing tools are provisioned. Projects
|
|
238
|
+
* pinning a tool through a wrapper are left alone because the wrapper
|
|
239
|
+
* provisions itself; only JDK compatibility is checked.
|
|
240
|
+
*
|
|
241
|
+
* @param {String} filePath Project path
|
|
242
|
+
* @param {Object} options CLI options
|
|
243
|
+
*
|
|
244
|
+
* @returns {String|undefined} The `javaNN` project type whose JDK this call
|
|
245
|
+
* already provisioned, so the caller can skip installing it again.
|
|
246
|
+
*/
|
|
247
|
+
export function prepareJvmBuildEnv(filePath, options) {
|
|
248
|
+
// Each invocation re-derives the pin, so a signal left behind by an earlier
|
|
249
|
+
// run must not leak into this one. A value the user set stays untouched.
|
|
250
|
+
if (cdxgenOwnsPinnedToolEnv) {
|
|
251
|
+
delete process.env.CDXGEN_JVM_TOOL_PINNED;
|
|
252
|
+
cdxgenOwnsPinnedToolEnv = false;
|
|
253
|
+
}
|
|
254
|
+
const explicitPins = [];
|
|
255
|
+
for (const pt of options.projectType || []) {
|
|
256
|
+
const parsed = parseJvmToolProjectType(pt);
|
|
257
|
+
if (!parsed) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (!parsed.valid) {
|
|
261
|
+
console.log(
|
|
262
|
+
`Invalid version '${parsed.version}' in the project type '${pt}'. Pass an exact sdkman version identifier such as -t maven3.9.9, -t gradle8.14.3, -t sbt1.10.11, or -t scala3.6.4. Run 'sdk list maven' style commands to see the available identifiers.`,
|
|
263
|
+
);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
explicitPins.push({
|
|
267
|
+
tool: parsed.tool,
|
|
268
|
+
version: parsed.version,
|
|
269
|
+
source: "cli",
|
|
270
|
+
});
|
|
271
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
272
|
+
ecosystem: "java",
|
|
273
|
+
tool: parsed.tool,
|
|
274
|
+
wanted: parsed.version,
|
|
275
|
+
source: "cli",
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
const autoMode = isFeatureEnabled(options, "jvm-tool-setup");
|
|
279
|
+
if (!explicitPins.length && !autoMode) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (isWin) {
|
|
283
|
+
console.log(
|
|
284
|
+
"Automatic JVM build tool provisioning requires sdkman, which is unavailable on Windows. Use the project's mvnw/gradlew wrappers or the cdxgen container image, which bundles maven, gradle, sbt, and scala.",
|
|
285
|
+
);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const detected = autoMode
|
|
289
|
+
? detectJvmToolsFromRepo(filePath, explicitPins)
|
|
290
|
+
: { installPins: [], wrapperVersions: [], javaVersionHint: undefined };
|
|
291
|
+
const installPins = [...explicitPins, ...detected.installPins];
|
|
292
|
+
if (!installPins.length && !detected.wrapperVersions.length) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (isDryRun) {
|
|
296
|
+
// Versions are left unresolved: resolving them means asking sdkman over
|
|
297
|
+
// the network, which dry-run does not do.
|
|
298
|
+
recordJvmBuildEnvIntent(options, installPins, detected);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
// Resolve partial versions (e.g. maven3.9 -> 3.9.16) before touching the JDK
|
|
302
|
+
// so the compatibility matrix sees the real versions.
|
|
303
|
+
const resolvedPins = [];
|
|
304
|
+
for (const pin of installPins) {
|
|
305
|
+
const resolved = resolveJvmToolPinVersion(pin);
|
|
306
|
+
if (resolved) {
|
|
307
|
+
resolvedPins.push(resolved);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
// Install the JDK first: build tools and the generation step both need it.
|
|
311
|
+
const knownToolVersions = [...resolvedPins, ...detected.wrapperVersions];
|
|
312
|
+
const javaMajor = ensureCompatibleJdk(
|
|
313
|
+
filePath,
|
|
314
|
+
options,
|
|
315
|
+
knownToolVersions,
|
|
316
|
+
detected.javaVersionHint,
|
|
317
|
+
);
|
|
318
|
+
warnGradleJavaIncompatibility(knownToolVersions, javaMajor);
|
|
319
|
+
let pinnedToolSelected = false;
|
|
320
|
+
for (const pin of resolvedPins) {
|
|
321
|
+
const installed = provisionJvmTool(pin, options);
|
|
322
|
+
if (installed) {
|
|
323
|
+
setJvmToolCommandEnv(pin);
|
|
324
|
+
if (pin.source === "cli") {
|
|
325
|
+
pinnedToolSelected = true;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// Signal the maven/gradle resolvers that an explicit CLI pin must beat the
|
|
330
|
+
// project wrappers.
|
|
331
|
+
if (pinnedToolSelected) {
|
|
332
|
+
process.env.CDXGEN_JVM_TOOL_PINNED = "true";
|
|
333
|
+
cdxgenOwnsPinnedToolEnv = true;
|
|
334
|
+
}
|
|
335
|
+
return (options.projectType || []).find((pt) =>
|
|
336
|
+
Boolean(SDKMAN_JAVA_TOOL_ALIASES[pt]),
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Human-readable explanation of where a tool version came from, used in the
|
|
342
|
+
* dry-run report so the reader can trace every decision back to its input.
|
|
343
|
+
*
|
|
344
|
+
* @type {Object<string, string>}
|
|
345
|
+
*/
|
|
346
|
+
const JVM_PIN_SOURCE_REASONS = {
|
|
347
|
+
cli: "pinned by the versioned project type on the command line",
|
|
348
|
+
"env-override": "pinned by the tool's *_TOOL environment variable",
|
|
349
|
+
sdkmanrc: "pinned by the project's .sdkmanrc",
|
|
350
|
+
"env-hint": "taken from the tool's *_VERSION environment variable",
|
|
351
|
+
default:
|
|
352
|
+
"the default version, because the project uses the tool without a wrapper and no usable command was found",
|
|
353
|
+
wrapper: "pinned by the project's build tool wrapper",
|
|
354
|
+
"build-properties": "declared in the project's project/build.properties",
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Report the JVM toolchain that would be provisioned, without provisioning it.
|
|
359
|
+
*
|
|
360
|
+
* Dry-run exists so that the reader can see every side effect cdxgen would
|
|
361
|
+
* cause before allowing it to run for real, and provisioning a toolchain is
|
|
362
|
+
* the largest side effect this stage has: it downloads and installs software
|
|
363
|
+
* and then rewrites the command environment used for the rest of the run.
|
|
364
|
+
* Skipping the stage silently would hide exactly what dry-run is for, so each
|
|
365
|
+
* install, each JDK decision, and each environment variable that would change
|
|
366
|
+
* is recorded as a blocked activity instead.
|
|
367
|
+
*
|
|
368
|
+
* @param {Object} options CLI options
|
|
369
|
+
* @param {Array} installPins Tools that would be installed
|
|
370
|
+
* @param {Object} detected Repo detection result
|
|
371
|
+
*/
|
|
372
|
+
export function recordJvmBuildEnvIntent(options, installPins, detected) {
|
|
373
|
+
const knownToolVersions = [...installPins, ...detected.wrapperVersions];
|
|
374
|
+
for (const pin of installPins) {
|
|
375
|
+
const version = pin.version || JVM_TOOL_DEFAULT_PREFIXES[pin.tool] || "";
|
|
376
|
+
const versionLabel = pin.version
|
|
377
|
+
? pin.version
|
|
378
|
+
: `${version ? `${version}.x` : "latest"} (resolved from sdkman at run time)`;
|
|
379
|
+
recordObservedActivity("provision", `sdkman:${pin.tool}@${versionLabel}`, {
|
|
380
|
+
metadata: {
|
|
381
|
+
packageType: pin.tool,
|
|
382
|
+
toolSource: pin.source,
|
|
383
|
+
},
|
|
384
|
+
reason: `Would install ${pin.tool} ${versionLabel} with sdkman: ${JVM_PIN_SOURCE_REASONS[pin.source] || pin.source}.`,
|
|
385
|
+
status: "blocked",
|
|
386
|
+
traceDetail: `${pin.tool}:${pin.source}`,
|
|
387
|
+
});
|
|
388
|
+
const commandEnv = JVM_TOOL_BINARIES[pin.tool]?.env;
|
|
389
|
+
if (commandEnv) {
|
|
390
|
+
recordObservedActivity("env", `process.env:${commandEnv}`, {
|
|
391
|
+
metadata: { packageType: pin.tool },
|
|
392
|
+
reason: `Would point ${commandEnv} at the provisioned ${pin.tool}.`,
|
|
393
|
+
status: "blocked",
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
for (const pin of detected.wrapperVersions) {
|
|
398
|
+
recordObservedActivity("discover", `${pin.tool}@${pin.version}`, {
|
|
399
|
+
metadata: {
|
|
400
|
+
packageType: pin.tool,
|
|
401
|
+
toolSource: pin.source,
|
|
402
|
+
},
|
|
403
|
+
reason: `Detected ${pin.tool} ${pin.version} ${JVM_PIN_SOURCE_REASONS[pin.source] || pin.source}. Nothing would be installed because the project provisions it itself.`,
|
|
404
|
+
traceDetail: `${pin.tool}:${pin.source}`,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
const targetMajor = determineTargetJavaMajor(
|
|
408
|
+
knownToolVersions,
|
|
409
|
+
detected.javaVersionHint,
|
|
410
|
+
);
|
|
411
|
+
const explicitJavaType = (options.projectType || []).find((pt) =>
|
|
412
|
+
Boolean(SDKMAN_JAVA_TOOL_ALIASES[pt]),
|
|
413
|
+
);
|
|
414
|
+
if (explicitJavaType) {
|
|
415
|
+
// An explicit javaNN type is always installed, so it is reported as one.
|
|
416
|
+
recordObservedActivity(
|
|
417
|
+
"provision",
|
|
418
|
+
`sdkman:java@${SDKMAN_JAVA_TOOL_ALIASES[explicitJavaType]}`,
|
|
419
|
+
{
|
|
420
|
+
metadata: { packageType: "java" },
|
|
421
|
+
reason: `Would install ${SDKMAN_JAVA_TOOL_ALIASES[explicitJavaType]} with sdkman: requested by the ${explicitJavaType} project type.`,
|
|
422
|
+
status: "blocked",
|
|
423
|
+
traceDetail: explicitJavaType,
|
|
424
|
+
},
|
|
425
|
+
);
|
|
426
|
+
} else {
|
|
427
|
+
// Otherwise the JDK is installed only when the active one is too old, and
|
|
428
|
+
// the active version cannot be read without running java. Report the
|
|
429
|
+
// requirement and the fallback rather than implying a certain install.
|
|
430
|
+
const javaAlias = pickJavaAliasForMajor(targetMajor);
|
|
431
|
+
recordObservedActivity("decision", `java>=${targetMajor}`, {
|
|
432
|
+
metadata: { packageType: "java" },
|
|
433
|
+
reason: javaAlias
|
|
434
|
+
? `The detected build tools need Java ${targetMajor} or higher. The active JDK would be checked first, and ${SDKMAN_JAVA_TOOL_ALIASES[javaAlias]} installed with sdkman only if it is older.`
|
|
435
|
+
: `The detected build tools need Java ${targetMajor} or higher, which cdxgen cannot provision automatically.`,
|
|
436
|
+
status: "blocked",
|
|
437
|
+
traceDetail: `java:${targetMajor}`,
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
if (installPins.some((pin) => pin.source === "cli")) {
|
|
441
|
+
recordObservedActivity("env", "process.env:CDXGEN_JVM_TOOL_PINNED", {
|
|
442
|
+
reason:
|
|
443
|
+
"Would be set so the Maven and Gradle resolvers prefer the pinned command over the project's wrapper scripts.",
|
|
444
|
+
status: "blocked",
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Highest JDK major the detected toolchain needs, falling back to the default
|
|
451
|
+
* when nothing constrains it.
|
|
452
|
+
*
|
|
453
|
+
* @param {Array} knownToolVersions Tool versions that will run
|
|
454
|
+
* @param {Number} javaVersionHint JDK major requested by the repo
|
|
455
|
+
*
|
|
456
|
+
* @returns {Number} JDK major to target.
|
|
457
|
+
*/
|
|
458
|
+
function determineTargetJavaMajor(knownToolVersions, javaVersionHint) {
|
|
459
|
+
const neededJava = determineRequiredJavaVersion(knownToolVersions);
|
|
460
|
+
return Math.max(javaVersionHint || 0, neededJava || 0) || DEFAULT_JAVA_MAJOR;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Detect the JVM build tools a project needs from its own version pins, in
|
|
465
|
+
* precedence order: MAVEN_TOOL style overrides, `.sdkmanrc`, container/CI
|
|
466
|
+
* version hints, then build tool wrappers and repo markers. Wrapper-pinned
|
|
467
|
+
* tools are returned as known versions without an install because the
|
|
468
|
+
* wrapper downloads its own distribution.
|
|
469
|
+
*
|
|
470
|
+
* @param {String} filePath Project path
|
|
471
|
+
* @param {Array} explicitPins Pins collected from versioned project types
|
|
472
|
+
*
|
|
473
|
+
* @returns {Object} `{ installPins, wrapperVersions, javaVersionHint }`
|
|
474
|
+
*/
|
|
475
|
+
export function detectJvmToolsFromRepo(filePath, explicitPins) {
|
|
476
|
+
const installPins = [];
|
|
477
|
+
const wrapperVersions = [];
|
|
478
|
+
const explicitTools = new Set((explicitPins || []).map((p) => p.tool));
|
|
479
|
+
const sdkmanrc = filePath ? parseSdkmanrc(filePath) : undefined;
|
|
480
|
+
const gradleWrapper = filePath
|
|
481
|
+
? readGradleWrapperVersion(filePath)
|
|
482
|
+
: undefined;
|
|
483
|
+
const mavenWrapper = filePath ? readMavenWrapperVersion(filePath) : undefined;
|
|
484
|
+
const sbtBuildVersion = filePath
|
|
485
|
+
? readSbtBuildPropertiesVersion(filePath)
|
|
486
|
+
: undefined;
|
|
487
|
+
for (const tool of JVM_BUILD_TOOLS) {
|
|
488
|
+
if (explicitTools.has(tool)) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const override = readToolVersionOverride(tool);
|
|
492
|
+
if (override) {
|
|
493
|
+
if (DEBUG_MODE) {
|
|
494
|
+
console.log(
|
|
495
|
+
`Using ${tool} ${override} from the ${tool.toUpperCase()}_TOOL environment variable.`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
installPins.push({ tool, version: override, source: "env-override" });
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (sdkmanrc?.[tool]) {
|
|
502
|
+
console.log(`Using ${tool} ${sdkmanrc[tool]} from the .sdkmanrc file.`);
|
|
503
|
+
installPins.push({ tool, version: sdkmanrc[tool], source: "sdkmanrc" });
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
const envHint = readToolVersionEnvHint(tool);
|
|
507
|
+
if (envHint) {
|
|
508
|
+
if (DEBUG_MODE) {
|
|
509
|
+
console.log(
|
|
510
|
+
`Using ${tool} ${envHint} from the ${tool.toUpperCase()}_VERSION environment variable.`,
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
installPins.push({ tool, version: envHint, source: "env-hint" });
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
// Wrapper-first: a project-pinned tool is never installed by cdxgen.
|
|
517
|
+
if (tool === "gradle" && gradleWrapper) {
|
|
518
|
+
if (DEBUG_MODE) {
|
|
519
|
+
console.log(
|
|
520
|
+
`Gradle ${gradleWrapper.version} is pinned by the project wrapper. The wrapper will provision it.`,
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
wrapperVersions.push({
|
|
524
|
+
tool,
|
|
525
|
+
version: gradleWrapper.version,
|
|
526
|
+
source: "wrapper",
|
|
527
|
+
});
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
if (tool === "maven" && mavenWrapper) {
|
|
531
|
+
if (DEBUG_MODE) {
|
|
532
|
+
console.log(
|
|
533
|
+
`Maven ${mavenWrapper.version} is pinned by the project wrapper. The wrapper will provision it.`,
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
wrapperVersions.push({
|
|
537
|
+
tool,
|
|
538
|
+
version: mavenWrapper.version,
|
|
539
|
+
source: "wrapper",
|
|
540
|
+
});
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (tool === "sbt") {
|
|
544
|
+
if (!filePath) {
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
if (sbtBuildVersion) {
|
|
548
|
+
if (DEBUG_MODE) {
|
|
549
|
+
console.log(
|
|
550
|
+
`sbt ${sbtBuildVersion} is declared in project/build.properties. The sbt launcher will provision it.`,
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
wrapperVersions.push({
|
|
554
|
+
tool,
|
|
555
|
+
version: sbtBuildVersion,
|
|
556
|
+
source: "build-properties",
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
if (
|
|
560
|
+
(sbtBuildVersion || safeExistsSync(join(filePath, "build.sbt"))) &&
|
|
561
|
+
!isJvmToolUsable(tool)
|
|
562
|
+
) {
|
|
563
|
+
installPins.push({ tool, version: undefined, source: "default" });
|
|
564
|
+
}
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
if (tool === "scala") {
|
|
568
|
+
// scala has no reliable repo marker; it is only provisioned when
|
|
569
|
+
// explicitly pinned via .sdkmanrc, env hints, or overrides.
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
// maven and gradle fall back to a default install when the repo uses the
|
|
573
|
+
// tool without a wrapper and no usable command exists.
|
|
574
|
+
const toolMarkers =
|
|
575
|
+
tool === "maven"
|
|
576
|
+
? ["pom.xml"]
|
|
577
|
+
: [
|
|
578
|
+
"build.gradle",
|
|
579
|
+
"build.gradle.kts",
|
|
580
|
+
"settings.gradle",
|
|
581
|
+
"settings.gradle.kts",
|
|
582
|
+
];
|
|
583
|
+
const markerFound = filePath
|
|
584
|
+
? toolMarkers.some((marker) => safeExistsSync(join(filePath, marker)))
|
|
585
|
+
: false;
|
|
586
|
+
if (markerFound && !isJvmToolUsable(tool)) {
|
|
587
|
+
installPins.push({ tool, version: undefined, source: "default" });
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
const javaVersionHint =
|
|
591
|
+
(sdkmanrc?.java ? extractJavaMajor(sdkmanrc.java) : undefined) ??
|
|
592
|
+
(filePath ? detectProjectJavaMajor(filePath) : undefined);
|
|
593
|
+
recordDetectedJvmPins(installPins, wrapperVersions);
|
|
594
|
+
return { installPins, wrapperVersions, javaVersionHint };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Record one `tool.expected` event per JVM pin detected from the repo, with
|
|
599
|
+
* the pin's declared version and the source that declared it.
|
|
600
|
+
*
|
|
601
|
+
* @param {Array} installPins Pins that cdxgen would install
|
|
602
|
+
* @param {Array} wrapperVersions Versions the project provisions itself
|
|
603
|
+
*/
|
|
604
|
+
function recordDetectedJvmPins(installPins, wrapperVersions) {
|
|
605
|
+
if (!isLedgerEnabled()) {
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
for (const pin of installPins) {
|
|
609
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
610
|
+
ecosystem: "java",
|
|
611
|
+
tool: pin.tool,
|
|
612
|
+
wanted: pin.version,
|
|
613
|
+
source: pin.source === "env-override" ? "override" : pin.source,
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
for (const pin of wrapperVersions) {
|
|
617
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
618
|
+
ecosystem: "java",
|
|
619
|
+
tool: pin.tool,
|
|
620
|
+
wanted: pin.version,
|
|
621
|
+
source: pin.source,
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Resolve a tool pin to an exact, locally known version. Exact versions that
|
|
628
|
+
* are already installed pass through untouched; otherwise the available
|
|
629
|
+
* sdkman versions are consulted to resolve partial prefixes such as `3.9`
|
|
630
|
+
* and to pick defaults.
|
|
631
|
+
*
|
|
632
|
+
* @param {Object} pin `{ tool, version?, source }`
|
|
633
|
+
*
|
|
634
|
+
* @returns {Object|undefined} Pin with an exact version, or undefined when no
|
|
635
|
+
* version could be determined.
|
|
636
|
+
*/
|
|
637
|
+
export function resolveJvmToolPinVersion(pin) {
|
|
638
|
+
if (pin.version && isSdkmanToolAvailable(pin.tool, pin.version)) {
|
|
639
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
640
|
+
ecosystem: "java",
|
|
641
|
+
tool: pin.tool,
|
|
642
|
+
wanted: pin.version,
|
|
643
|
+
found: pin.version,
|
|
644
|
+
source: "sdkman",
|
|
645
|
+
detail: `${pin.tool} ${pin.version} is already installed.`,
|
|
646
|
+
});
|
|
647
|
+
return pin;
|
|
648
|
+
}
|
|
649
|
+
const versionList = fetchSdkmanVersionList(pin.tool);
|
|
650
|
+
if (versionList === undefined) {
|
|
651
|
+
// sdk list failed (offline or sdkman missing). Exact versions are still
|
|
652
|
+
// worth attempting directly; defaults are not.
|
|
653
|
+
if (pin.version) {
|
|
654
|
+
return pin;
|
|
655
|
+
}
|
|
656
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISSING, {
|
|
657
|
+
ecosystem: "java",
|
|
658
|
+
tool: pin.tool,
|
|
659
|
+
source: "sdkman",
|
|
660
|
+
detail: `No installable ${pin.tool} version could be determined: 'sdk list ${pin.tool}' did not return any versions.`,
|
|
661
|
+
});
|
|
662
|
+
console.log(
|
|
663
|
+
`Could not determine an installable version for ${pin.tool}: 'sdk list ${pin.tool}' did not return any versions.`,
|
|
664
|
+
);
|
|
665
|
+
return undefined;
|
|
666
|
+
}
|
|
667
|
+
const prefix = pin.version || JVM_TOOL_DEFAULT_PREFIXES[pin.tool] || "";
|
|
668
|
+
const resolved = resolvePartialVersion(versionList, prefix);
|
|
669
|
+
if (!resolved) {
|
|
670
|
+
if (pin.version) {
|
|
671
|
+
return pin;
|
|
672
|
+
}
|
|
673
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISSING, {
|
|
674
|
+
ecosystem: "java",
|
|
675
|
+
tool: pin.tool,
|
|
676
|
+
wanted: prefix,
|
|
677
|
+
source: "sdkman",
|
|
678
|
+
detail: `No stable ${pin.tool} version matched the default prefix '${prefix}'.`,
|
|
679
|
+
});
|
|
680
|
+
console.log(
|
|
681
|
+
`No stable ${pin.tool} version matched the default prefix '${prefix}'. Pass an exact version identifier via -t ${pin.tool}<version>.`,
|
|
682
|
+
);
|
|
683
|
+
return undefined;
|
|
684
|
+
}
|
|
685
|
+
if (resolved !== pin.version) {
|
|
686
|
+
console.log(
|
|
687
|
+
`Resolved ${pin.tool} version '${pin.version || "default"}' to '${resolved}' from the available sdkman versions.`,
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
691
|
+
ecosystem: "java",
|
|
692
|
+
tool: pin.tool,
|
|
693
|
+
wanted: pin.version,
|
|
694
|
+
found: resolved,
|
|
695
|
+
source: "sdkman",
|
|
696
|
+
});
|
|
697
|
+
return { ...pin, version: resolved };
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Fetch the versions available for a candidate via `sdk list`.
|
|
702
|
+
*
|
|
703
|
+
* @param {String} tool sdkman candidate name
|
|
704
|
+
*
|
|
705
|
+
* @returns {String|undefined} Command output, or undefined on failure.
|
|
706
|
+
*/
|
|
707
|
+
export function fetchSdkmanVersionList(tool) {
|
|
708
|
+
if (!isSdkmanAvailable()) {
|
|
709
|
+
return undefined;
|
|
710
|
+
}
|
|
711
|
+
const result = safeSpawnSync(
|
|
712
|
+
readEnvironmentVariable("SHELL") || "bash",
|
|
713
|
+
["-i", "-c", `sdk list ${tool}`],
|
|
714
|
+
{
|
|
715
|
+
encoding: "utf-8",
|
|
716
|
+
timeout: TIMEOUT_MS,
|
|
717
|
+
},
|
|
718
|
+
);
|
|
719
|
+
if (result.error || result.status !== 0) {
|
|
720
|
+
return undefined;
|
|
721
|
+
}
|
|
722
|
+
return result.stdout ? result.stdout.toString() : "";
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Check whether a JVM build tool already has a usable command, honouring the
|
|
727
|
+
* existing `MVN_CMD`/`GRADLE_CMD`/`SBT_CMD` overrides.
|
|
728
|
+
*
|
|
729
|
+
* The probe deliberately runs outside the scanned project. Build tool
|
|
730
|
+
* launchers read JVM arguments from the working directory before doing
|
|
731
|
+
* anything else - Maven from `.mvn/jvm.config` and sbt from `.jvmopts` or
|
|
732
|
+
* `.sbtopts` - so a `--version` call inside a project would let that project
|
|
733
|
+
* inject options such as `-javaagent` into the probe. Only the presence of a
|
|
734
|
+
* working command is being established here, which the project cannot affect.
|
|
735
|
+
*
|
|
736
|
+
* @param {String} tool Tool name
|
|
737
|
+
*
|
|
738
|
+
* @returns {Boolean} True when the tool responds to `--version`.
|
|
739
|
+
*/
|
|
740
|
+
export function isJvmToolUsable(tool) {
|
|
741
|
+
const toolBinary = JVM_TOOL_BINARIES[tool];
|
|
742
|
+
if (!toolBinary) {
|
|
743
|
+
return false;
|
|
744
|
+
}
|
|
745
|
+
// Honour explicit command overrides such as MVN_CMD/GRADLE_CMD/SBT_CMD.
|
|
746
|
+
const binaryName = toolBinary[isWin ? "windowsCommand" : "command"];
|
|
747
|
+
const overrideCommand = toolBinary.env
|
|
748
|
+
? readEnvironmentVariable(toolBinary.env)
|
|
749
|
+
: undefined;
|
|
750
|
+
const probeCommand = overrideCommand || binaryName;
|
|
751
|
+
if (isDryRun) {
|
|
752
|
+
// Executing the tool is blocked here, and a blocked probe would report
|
|
753
|
+
// every tool as missing. Looking the binary up on PATH answers the same
|
|
754
|
+
// question without running anything.
|
|
755
|
+
const located = overrideCommand
|
|
756
|
+
? safeExistsSync(overrideCommand) && overrideCommand
|
|
757
|
+
: locateToolBinaryOnPath(binaryName);
|
|
758
|
+
recordObservedActivity("probe", probeCommand, {
|
|
759
|
+
metadata: { packageType: tool, probeType: "build-tool-availability" },
|
|
760
|
+
reason: located
|
|
761
|
+
? `Found ${tool} at ${located} on PATH without executing it.`
|
|
762
|
+
: `No ${tool} command found on PATH. It was not executed.`,
|
|
763
|
+
status: "blocked",
|
|
764
|
+
traceDetail: tool,
|
|
765
|
+
});
|
|
766
|
+
if (located) {
|
|
767
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
768
|
+
ecosystem: "java",
|
|
769
|
+
tool,
|
|
770
|
+
source: "PATH",
|
|
771
|
+
path: located,
|
|
772
|
+
});
|
|
773
|
+
} else {
|
|
774
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISSING, {
|
|
775
|
+
ecosystem: "java",
|
|
776
|
+
tool,
|
|
777
|
+
source: "PATH",
|
|
778
|
+
detail: `No ${tool} command was found on PATH.`,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
return Boolean(located);
|
|
782
|
+
}
|
|
783
|
+
const result = safeSpawnSync(probeCommand, ["--version"], {
|
|
784
|
+
cdxgenActivity: {
|
|
785
|
+
kind: "probe",
|
|
786
|
+
metadata: { tool },
|
|
787
|
+
probeType: "build-tool-availability",
|
|
788
|
+
},
|
|
789
|
+
cwd: getTmpDir(),
|
|
790
|
+
encoding: "utf-8",
|
|
791
|
+
shell: isWin,
|
|
792
|
+
timeout: TIMEOUT_MS,
|
|
793
|
+
});
|
|
794
|
+
const usable = !result.error && result.status === 0;
|
|
795
|
+
if (usable) {
|
|
796
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
797
|
+
ecosystem: "java",
|
|
798
|
+
tool,
|
|
799
|
+
source: "PATH",
|
|
800
|
+
});
|
|
801
|
+
} else if (
|
|
802
|
+
describeSpawnRestriction(probeCommand) ||
|
|
803
|
+
classifyProbeResult(result) === "denied"
|
|
804
|
+
) {
|
|
805
|
+
// A probe that could not run says nothing about the tool's presence.
|
|
806
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.EVIDENCE_DEGRADED, {
|
|
807
|
+
ecosystem: "java",
|
|
808
|
+
tool,
|
|
809
|
+
source: "PATH",
|
|
810
|
+
detail: `The ${tool} availability probe could not run, so the tool's presence is unknown.`,
|
|
811
|
+
});
|
|
812
|
+
} else {
|
|
813
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISSING, {
|
|
814
|
+
ecosystem: "java",
|
|
815
|
+
tool,
|
|
816
|
+
source: "PATH",
|
|
817
|
+
detail: `No working ${tool} command answered the availability probe.`,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
return usable;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Find an executable on PATH without running it.
|
|
825
|
+
*
|
|
826
|
+
* @param {String} binaryName Binary to look for
|
|
827
|
+
*
|
|
828
|
+
* @returns {String|undefined} Full path to the binary, when present.
|
|
829
|
+
*/
|
|
830
|
+
function locateToolBinaryOnPath(binaryName) {
|
|
831
|
+
for (const pathEntry of (readEnvironmentVariable("PATH") || "").split(
|
|
832
|
+
delimiter,
|
|
833
|
+
)) {
|
|
834
|
+
if (!pathEntry) {
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
const candidate = join(pathEntry, binaryName);
|
|
838
|
+
if (safeExistsSync(candidate)) {
|
|
839
|
+
return candidate;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return undefined;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Ensure a JDK compatible with the pinned tools is active. Installs a JDK
|
|
847
|
+
* only when the current java is missing or older than required; an explicit
|
|
848
|
+
* `javaNN` project type is honoured as-is with a warning when it is too old.
|
|
849
|
+
*
|
|
850
|
+
* @param {String} filePath Project path
|
|
851
|
+
* @param {Object} options CLI options
|
|
852
|
+
* @param {Array} knownToolVersions Tool versions that will run
|
|
853
|
+
* @param {Number} javaVersionHint JDK major requested by the repo
|
|
854
|
+
*
|
|
855
|
+
* @returns {Number|undefined} Effective JDK major after the check.
|
|
856
|
+
*/
|
|
857
|
+
export function ensureCompatibleJdk(
|
|
858
|
+
filePath,
|
|
859
|
+
options,
|
|
860
|
+
knownToolVersions,
|
|
861
|
+
javaVersionHint,
|
|
862
|
+
) {
|
|
863
|
+
const neededJava = determineRequiredJavaVersion(knownToolVersions);
|
|
864
|
+
const explicitJavaType = (options.projectType || []).find((pt) =>
|
|
865
|
+
Boolean(SDKMAN_JAVA_TOOL_ALIASES[pt]),
|
|
866
|
+
);
|
|
867
|
+
if (explicitJavaType) {
|
|
868
|
+
const aliasMajor = Number.parseInt(
|
|
869
|
+
explicitJavaType.replace("java", ""),
|
|
870
|
+
10,
|
|
871
|
+
);
|
|
872
|
+
if (neededJava && aliasMajor < neededJava) {
|
|
873
|
+
console.log(
|
|
874
|
+
`WARNING: the pinned JVM build tools require Java ${neededJava} or higher, but Java ${aliasMajor} was requested.`,
|
|
875
|
+
);
|
|
876
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISMATCH, {
|
|
877
|
+
ecosystem: "java",
|
|
878
|
+
tool: "java",
|
|
879
|
+
wanted: `${neededJava}`,
|
|
880
|
+
found: `${aliasMajor}`,
|
|
881
|
+
source: "cli",
|
|
882
|
+
detail: `The pinned JVM build tools require Java ${neededJava} or higher, but Java ${aliasMajor} was requested.`,
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
// The javaNN alias install is idempotent; running it here keeps the JDK
|
|
886
|
+
// ahead of the build tool installs.
|
|
887
|
+
prepareSdkmanBuild(explicitJavaType);
|
|
888
|
+
return Math.max(aliasMajor || 0, neededJava || 0);
|
|
889
|
+
}
|
|
890
|
+
const currentJava = collectJavaInfo(filePath || process.cwd());
|
|
891
|
+
const currentMajor = currentJava?.version
|
|
892
|
+
? extractJavaMajor(currentJava.version)
|
|
893
|
+
: undefined;
|
|
894
|
+
const targetMajor = determineTargetJavaMajor(
|
|
895
|
+
knownToolVersions,
|
|
896
|
+
javaVersionHint,
|
|
897
|
+
);
|
|
898
|
+
if (currentMajor !== undefined && currentMajor >= targetMajor) {
|
|
899
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
900
|
+
ecosystem: "java",
|
|
901
|
+
tool: "java",
|
|
902
|
+
wanted: `${targetMajor}`,
|
|
903
|
+
found: `${currentMajor}`,
|
|
904
|
+
source: "env",
|
|
905
|
+
detail: `The active Java ${currentMajor} satisfies the Java ${targetMajor}+ requirement of the pinned build tools.`,
|
|
906
|
+
});
|
|
907
|
+
return currentMajor;
|
|
908
|
+
}
|
|
909
|
+
const javaAlias = pickJavaAliasForMajor(targetMajor);
|
|
910
|
+
if (!javaAlias) {
|
|
911
|
+
console.log(
|
|
912
|
+
`WARNING: Java ${targetMajor} or higher is required to run the pinned JVM build tools, and it could not be provisioned automatically. Install a suitable JDK or pass a version type such as -t java${targetMajor}.`,
|
|
913
|
+
);
|
|
914
|
+
recordJvmRequirementShortfall(targetMajor, currentMajor);
|
|
915
|
+
return currentMajor;
|
|
916
|
+
}
|
|
917
|
+
console.log(
|
|
918
|
+
`The pinned JVM build tools need Java ${targetMajor} or higher.${currentMajor ? ` The current Java ${currentMajor} is older.` : ""} Installing ${SDKMAN_JAVA_TOOL_ALIASES[javaAlias]} with sdkman.`,
|
|
919
|
+
);
|
|
920
|
+
recordJvmRequirementShortfall(targetMajor, currentMajor);
|
|
921
|
+
prepareSdkmanBuild(javaAlias);
|
|
922
|
+
return targetMajor;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/**
|
|
926
|
+
* Record the JDK shortfall the pinned build tools run into: a mismatch when a
|
|
927
|
+
* live JDK was found and is too old, a missing tool when no JDK answered the
|
|
928
|
+
* probe at all.
|
|
929
|
+
*
|
|
930
|
+
* @param {Number} targetMajor Required JDK major
|
|
931
|
+
* @param {Number} currentMajor Active JDK major, when one was found
|
|
932
|
+
*/
|
|
933
|
+
function recordJvmRequirementShortfall(targetMajor, currentMajor) {
|
|
934
|
+
if (currentMajor === undefined) {
|
|
935
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISSING, {
|
|
936
|
+
ecosystem: "java",
|
|
937
|
+
tool: "java",
|
|
938
|
+
wanted: `${targetMajor}`,
|
|
939
|
+
source: "env",
|
|
940
|
+
detail: `No active JDK answered the version probe, and the pinned build tools need Java ${targetMajor} or higher.`,
|
|
941
|
+
});
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISMATCH, {
|
|
945
|
+
ecosystem: "java",
|
|
946
|
+
tool: "java",
|
|
947
|
+
wanted: `${targetMajor}`,
|
|
948
|
+
found: `${currentMajor}`,
|
|
949
|
+
source: "env",
|
|
950
|
+
detail: `The pinned build tools need Java ${targetMajor} or higher; the active Java ${currentMajor} is older.`,
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/**
|
|
955
|
+
* Pick the closest `javaNN` alias covering a required JDK major.
|
|
956
|
+
*
|
|
957
|
+
* @param {Number} major Required JDK major
|
|
958
|
+
*
|
|
959
|
+
* @returns {String|undefined} Alias such as `java17`.
|
|
960
|
+
*/
|
|
961
|
+
export function pickJavaAliasForMajor(major) {
|
|
962
|
+
if (SDKMAN_JAVA_TOOL_ALIASES[`java${major}`]) {
|
|
963
|
+
return `java${major}`;
|
|
964
|
+
}
|
|
965
|
+
const knownMajors = Object.keys(SDKMAN_JAVA_TOOL_ALIASES)
|
|
966
|
+
.map((pt) => Number.parseInt(pt.replace("java", ""), 10))
|
|
967
|
+
.filter((m) => !Number.isNaN(m) && m >= major)
|
|
968
|
+
.sort((a, b) => a - b);
|
|
969
|
+
return knownMajors.length ? `java${knownMajors[0]}` : undefined;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Warn when the Gradle version in play cannot run on the effective JDK.
|
|
974
|
+
*
|
|
975
|
+
* @param {Array} knownToolVersions Tool versions that will run
|
|
976
|
+
* @param {Number} javaMajor Effective JDK major
|
|
977
|
+
*/
|
|
978
|
+
export function warnGradleJavaIncompatibility(knownToolVersions, javaMajor) {
|
|
979
|
+
const gradleVersion = (knownToolVersions || []).find(
|
|
980
|
+
(pin) => pin.tool === "gradle",
|
|
981
|
+
)?.version;
|
|
982
|
+
if (!gradleVersion || !javaMajor) {
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const minimumGradle = minimumGradleVersionForJava(javaMajor);
|
|
986
|
+
if (
|
|
987
|
+
minimumGradle &&
|
|
988
|
+
compareSdkmanVersions(gradleVersion, minimumGradle) < 0
|
|
989
|
+
) {
|
|
990
|
+
console.log(
|
|
991
|
+
`WARNING: Gradle ${gradleVersion} cannot run on Java ${javaMajor}; Gradle ${minimumGradle} or higher is required. Pass a compatible JDK type such as -t java17, or update the project's Gradle wrapper.`,
|
|
992
|
+
);
|
|
993
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISMATCH, {
|
|
994
|
+
ecosystem: "java",
|
|
995
|
+
tool: "gradle",
|
|
996
|
+
wanted: minimumGradle,
|
|
997
|
+
found: gradleVersion,
|
|
998
|
+
source: "env",
|
|
999
|
+
detail: `Gradle ${gradleVersion} cannot run on Java ${javaMajor}; Gradle ${minimumGradle} or higher is required.`,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* Install a pinned tool unless it is already available, applying the failure
|
|
1006
|
+
* policy used by the ruby preparation: continue with whatever exists unless
|
|
1007
|
+
* both `deep` and `failOnError` are set.
|
|
1008
|
+
*
|
|
1009
|
+
* @param {Object} pin `{ tool, version, source }`
|
|
1010
|
+
* @param {Object} options CLI options
|
|
1011
|
+
*
|
|
1012
|
+
* @returns {Boolean} True when the exact version is active.
|
|
1013
|
+
*/
|
|
1014
|
+
export function provisionJvmTool(pin, options) {
|
|
1015
|
+
if (!isSdkmanAvailable()) {
|
|
1016
|
+
if (isJvmToolUsable(pin.tool)) {
|
|
1017
|
+
console.log(
|
|
1018
|
+
`sdkman is unavailable, but a working ${pin.tool} command exists on PATH. Continuing with it instead of ${pin.version || "the pinned version"}.`,
|
|
1019
|
+
);
|
|
1020
|
+
return false;
|
|
1021
|
+
}
|
|
1022
|
+
console.log(
|
|
1023
|
+
"Install sdkman by following the instructions at https://sdkman.io/install",
|
|
1024
|
+
);
|
|
1025
|
+
if (options.deep) {
|
|
1026
|
+
deferFailOnError(options, {
|
|
1027
|
+
ecosystem: "java",
|
|
1028
|
+
tool: pin.tool,
|
|
1029
|
+
detail:
|
|
1030
|
+
"sdkman is not installed, so the declared JVM tool pin could not be provisioned",
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
return false;
|
|
1034
|
+
}
|
|
1035
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_ATTEMPTED, {
|
|
1036
|
+
ecosystem: "java",
|
|
1037
|
+
tool: pin.tool,
|
|
1038
|
+
command: `sdk install ${pin.tool} ${pin.version}`,
|
|
1039
|
+
detail: `Installing ${pin.tool} ${pin.version} with sdkman.`,
|
|
1040
|
+
});
|
|
1041
|
+
if (!installSdkmanTool(pin.tool, pin.version)) {
|
|
1042
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1043
|
+
ecosystem: "java",
|
|
1044
|
+
tool: pin.tool,
|
|
1045
|
+
command: `sdk install ${pin.tool} ${pin.version}`,
|
|
1046
|
+
detail: `The sdkman install of ${pin.tool} ${pin.version} did not succeed.`,
|
|
1047
|
+
});
|
|
1048
|
+
if (options.deep) {
|
|
1049
|
+
deferFailOnError(options, {
|
|
1050
|
+
ecosystem: "java",
|
|
1051
|
+
tool: pin.tool,
|
|
1052
|
+
detail: `the sdkman install of ${pin.tool} ${pin.version} did not succeed`,
|
|
1053
|
+
});
|
|
1054
|
+
}
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
return true;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
/**
|
|
1061
|
+
* Point the tool command environment variables at the freshly provisioned
|
|
1062
|
+
* binary so the existing resolvers pick it up deterministically.
|
|
1063
|
+
*
|
|
1064
|
+
* @param {Object} pin `{ tool, version }`
|
|
1065
|
+
*/
|
|
1066
|
+
export function setJvmToolCommandEnv(pin) {
|
|
1067
|
+
const toolBinary = JVM_TOOL_BINARIES[pin.tool];
|
|
1068
|
+
if (!toolBinary?.env) {
|
|
1069
|
+
// scala is driven through SCALA_HOME, which the installer already set.
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const toolHome = readEnvironmentVariable(`${pin.tool.toUpperCase()}_HOME`);
|
|
1073
|
+
if (!toolHome) {
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
const toolBin = join(
|
|
1077
|
+
toolHome,
|
|
1078
|
+
"bin",
|
|
1079
|
+
toolBinary[isWin ? "windowsCommand" : "command"],
|
|
1080
|
+
);
|
|
1081
|
+
if (safeExistsSync(toolBin)) {
|
|
1082
|
+
process.env[toolBinary.env] = toolBin;
|
|
1083
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
1084
|
+
ecosystem: "java",
|
|
1085
|
+
tool: pin.tool,
|
|
1086
|
+
wanted: pin.version,
|
|
1087
|
+
found: pin.version,
|
|
1088
|
+
source: "sdkman",
|
|
1089
|
+
path: toolBin,
|
|
1090
|
+
detail: `The ${pin.tool} command environment now points at the provisioned ${pin.version}.`,
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
79
1095
|
/**
|
|
80
1096
|
* Method to check and prepare the environment for python
|
|
81
1097
|
*
|
|
@@ -84,6 +1100,18 @@ export function prepareSdkmanBuild(projectType) {
|
|
|
84
1100
|
*/
|
|
85
1101
|
export function preparePythonEnv(_filePath, options) {
|
|
86
1102
|
if (hasAnyProjectType(["python"], options, false)) {
|
|
1103
|
+
if (
|
|
1104
|
+
isLedgerEnabled() &&
|
|
1105
|
+
!readEnvironmentVariable("VIRTUAL_ENV") &&
|
|
1106
|
+
!readEnvironmentVariable("CONDA_PREFIX")
|
|
1107
|
+
) {
|
|
1108
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.EVIDENCE_DEGRADED, {
|
|
1109
|
+
ecosystem: "python",
|
|
1110
|
+
detail:
|
|
1111
|
+
"No active virtualenv or conda environment was detected; dependency resolution will use the system interpreter.",
|
|
1112
|
+
impact: LEDGER_EVENT_IMPACTS.COMPONENTS,
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
87
1115
|
if (
|
|
88
1116
|
DEBUG_MODE &&
|
|
89
1117
|
readEnvironmentVariable("CDXGEN_IN_CONTAINER") !== "true" &&
|
|
@@ -136,6 +1164,7 @@ export function preparePythonEnv(_filePath, options) {
|
|
|
136
1164
|
*/
|
|
137
1165
|
export function prepareNodeEnv(filePath, options) {
|
|
138
1166
|
// check tool for windows
|
|
1167
|
+
let npmInstallAttempted = false;
|
|
139
1168
|
for (const pt of options.projectType) {
|
|
140
1169
|
const nodeVersion = pt.replace(/\D/g, "");
|
|
141
1170
|
if (
|
|
@@ -177,6 +1206,7 @@ export function prepareNodeEnv(filePath, options) {
|
|
|
177
1206
|
}
|
|
178
1207
|
if (nvmNodePath) {
|
|
179
1208
|
doNpmInstall(filePath, nvmNodePath);
|
|
1209
|
+
npmInstallAttempted = true;
|
|
180
1210
|
} else {
|
|
181
1211
|
console.log(
|
|
182
1212
|
`"node version ${nodeVersion} was not found. Please install it with 'nvm install ${nodeVersion}"`,
|
|
@@ -199,8 +1229,39 @@ export function prepareNodeEnv(filePath, options) {
|
|
|
199
1229
|
continue;
|
|
200
1230
|
}
|
|
201
1231
|
doNpmInstall(filePath, nvmNodePath);
|
|
1232
|
+
npmInstallAttempted = true;
|
|
202
1233
|
}
|
|
203
1234
|
}
|
|
1235
|
+
recordNodeModulesEvidence(filePath, npmInstallAttempted);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Record when the scanned project has no node_modules tree and no npm install
|
|
1240
|
+
* was attempted during preparation, since installed-package evidence will be
|
|
1241
|
+
* unavailable to the generation step.
|
|
1242
|
+
*
|
|
1243
|
+
* @param {String} filePath Project path
|
|
1244
|
+
* @param {Boolean} npmInstallAttempted Whether an npm install ran
|
|
1245
|
+
*/
|
|
1246
|
+
function recordNodeModulesEvidence(filePath, npmInstallAttempted) {
|
|
1247
|
+
if (!isLedgerEnabled() || !filePath || npmInstallAttempted) {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
// Node preparation runs for every project type, so a missing node_modules
|
|
1251
|
+
// only says something about a project that declares npm dependencies at all.
|
|
1252
|
+
if (!safeExistsSync(join(filePath, "package.json"))) {
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
if (safeExistsSync(join(filePath, "node_modules"))) {
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.EVIDENCE_DEGRADED, {
|
|
1259
|
+
ecosystem: "npm",
|
|
1260
|
+
remediationId: "js.no-node-modules",
|
|
1261
|
+
detail:
|
|
1262
|
+
"node_modules is absent and no npm install was attempted, so installed-package evidence will be unavailable.",
|
|
1263
|
+
impact: LEDGER_EVENT_IMPACTS.TRANSITIVE_DEPS,
|
|
1264
|
+
});
|
|
204
1265
|
}
|
|
205
1266
|
|
|
206
1267
|
/**
|
|
@@ -254,6 +1315,13 @@ export function doNpmInstall(filePath, nvmNodePath) {
|
|
|
254
1315
|
if (isSecureMode) {
|
|
255
1316
|
installArgs = `${installArgs} --ignore-scripts --no-audit`;
|
|
256
1317
|
}
|
|
1318
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_ATTEMPTED, {
|
|
1319
|
+
ecosystem: "npm",
|
|
1320
|
+
tool: "npm",
|
|
1321
|
+
command: `npm ${installCommand} ${installArgs}`,
|
|
1322
|
+
path: filePath,
|
|
1323
|
+
detail: "Installing the project dependencies with npm.",
|
|
1324
|
+
});
|
|
257
1325
|
const resultNpmInstall = safeSpawnSync(
|
|
258
1326
|
readEnvironmentVariable("SHELL") || "bash",
|
|
259
1327
|
// The nvm node directory is already prepended to PATH below
|
|
@@ -271,6 +1339,18 @@ export function doNpmInstall(filePath, nvmNodePath) {
|
|
|
271
1339
|
|
|
272
1340
|
if (resultNpmInstall.status !== 0 || resultNpmInstall.error) {
|
|
273
1341
|
// There was some problem with NpmInstall
|
|
1342
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1343
|
+
ecosystem: "npm",
|
|
1344
|
+
tool: "npm",
|
|
1345
|
+
command: `npm ${installCommand} ${installArgs}`,
|
|
1346
|
+
path: filePath,
|
|
1347
|
+
exitCode:
|
|
1348
|
+
typeof resultNpmInstall.status === "number"
|
|
1349
|
+
? resultNpmInstall.status
|
|
1350
|
+
: undefined,
|
|
1351
|
+
detail: "The npm install did not succeed.",
|
|
1352
|
+
outputExcerpt: commandOutputText(resultNpmInstall),
|
|
1353
|
+
});
|
|
274
1354
|
if (DEBUG_MODE) {
|
|
275
1355
|
if (resultNpmInstall.stdout) {
|
|
276
1356
|
console.log(resultNpmInstall.stdout);
|
|
@@ -356,6 +1436,23 @@ export function prepareSwiftEnv(filePath, options) {
|
|
|
356
1436
|
console.log(
|
|
357
1437
|
"The Swift package command did not yield the expected result. Build this project manually before invoking cdxgen.",
|
|
358
1438
|
);
|
|
1439
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.COMMAND_FAILED, {
|
|
1440
|
+
ecosystem: "swift",
|
|
1441
|
+
tool: "swift",
|
|
1442
|
+
command: "swift package -v resolve",
|
|
1443
|
+
path: basePath,
|
|
1444
|
+
remediationId: "swift.package-command",
|
|
1445
|
+
detail:
|
|
1446
|
+
"The swift package resolve command did not produce a usable result.",
|
|
1447
|
+
});
|
|
1448
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.FALLBACK_ENGAGED, {
|
|
1449
|
+
ecosystem: "swift",
|
|
1450
|
+
tool: "swift",
|
|
1451
|
+
remediationId: "swift.package-command",
|
|
1452
|
+
impact: LEDGER_EVENT_IMPACTS.TRANSITIVE_DEPS,
|
|
1453
|
+
detail:
|
|
1454
|
+
"Swift dependencies will be read from the manifest alone because package resolution did not yield a Package.resolved file.",
|
|
1455
|
+
});
|
|
359
1456
|
}
|
|
360
1457
|
if (!safeExistsSync(resolvedFile)) {
|
|
361
1458
|
console.log(
|
|
@@ -425,6 +1522,15 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
425
1522
|
})
|
|
426
1523
|
.trim()
|
|
427
1524
|
.replace("ruby-", "");
|
|
1525
|
+
if (rubyVersionNeeded) {
|
|
1526
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
1527
|
+
ecosystem: "ruby",
|
|
1528
|
+
tool: "ruby",
|
|
1529
|
+
wanted: rubyVersionNeeded,
|
|
1530
|
+
source: ".ruby-version",
|
|
1531
|
+
path: join(filePath, ".ruby-version"),
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
428
1534
|
} else if (safeExistsSync(join(filePath, "Gemfile.lock"))) {
|
|
429
1535
|
// Is there a lock file that can be used to identify the needed Ruby version?
|
|
430
1536
|
const gemlockData = readFileSync(join(filePath, "Gemfile.lock"), {
|
|
@@ -448,6 +1554,23 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
448
1554
|
}
|
|
449
1555
|
}
|
|
450
1556
|
}
|
|
1557
|
+
if (rubyVersionNeeded) {
|
|
1558
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
1559
|
+
ecosystem: "ruby",
|
|
1560
|
+
tool: "ruby",
|
|
1561
|
+
wanted: rubyVersionNeeded,
|
|
1562
|
+
source: "Gemfile.lock",
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
const bundlerVersion = parseGemfileLockBundlerVersion(gemlockData);
|
|
1566
|
+
if (bundlerVersion) {
|
|
1567
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
1568
|
+
ecosystem: "ruby",
|
|
1569
|
+
tool: "bundler",
|
|
1570
|
+
wanted: bundlerVersion,
|
|
1571
|
+
source: "Gemfile.lock",
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
451
1574
|
} else {
|
|
452
1575
|
// Is the user invoking with a ruby with custom version type. Eg: -t ruby2.5.4
|
|
453
1576
|
let projectTypes = options.projectType;
|
|
@@ -468,6 +1591,14 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
468
1591
|
break;
|
|
469
1592
|
}
|
|
470
1593
|
}
|
|
1594
|
+
if (rubyVersionNeeded) {
|
|
1595
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
1596
|
+
ecosystem: "ruby",
|
|
1597
|
+
tool: "ruby",
|
|
1598
|
+
wanted: rubyVersionNeeded,
|
|
1599
|
+
source: "project-type",
|
|
1600
|
+
});
|
|
1601
|
+
}
|
|
471
1602
|
}
|
|
472
1603
|
// Do we already have this version
|
|
473
1604
|
const existingRuby = collectRubyInfo(filePath);
|
|
@@ -475,6 +1606,14 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
475
1606
|
rubyVersionNeeded &&
|
|
476
1607
|
existingRuby?.version?.startsWith(`ruby ${rubyVersionNeeded} `)
|
|
477
1608
|
) {
|
|
1609
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
1610
|
+
ecosystem: "ruby",
|
|
1611
|
+
tool: "ruby",
|
|
1612
|
+
wanted: rubyVersionNeeded,
|
|
1613
|
+
found: existingRuby.version,
|
|
1614
|
+
source: "env",
|
|
1615
|
+
detail: `The active interpreter is the required Ruby ${rubyVersionNeeded}.`,
|
|
1616
|
+
});
|
|
478
1617
|
if (DEBUG_MODE) {
|
|
479
1618
|
console.log(`Required Ruby version ${rubyVersionNeeded} is present.`);
|
|
480
1619
|
}
|
|
@@ -493,6 +1632,26 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
493
1632
|
return;
|
|
494
1633
|
}
|
|
495
1634
|
}
|
|
1635
|
+
} else if (rubyVersionNeeded && !existingRuby?.version) {
|
|
1636
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISSING, {
|
|
1637
|
+
ecosystem: "ruby",
|
|
1638
|
+
tool: "ruby",
|
|
1639
|
+
wanted: rubyVersionNeeded,
|
|
1640
|
+
source: "env",
|
|
1641
|
+
detail: `No Ruby interpreter answered the version probe, and Ruby ${rubyVersionNeeded} is required.`,
|
|
1642
|
+
});
|
|
1643
|
+
} else if (
|
|
1644
|
+
rubyVersionNeeded &&
|
|
1645
|
+
!existingRuby.version.startsWith(`ruby ${rubyVersionNeeded} `)
|
|
1646
|
+
) {
|
|
1647
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISMATCH, {
|
|
1648
|
+
ecosystem: "ruby",
|
|
1649
|
+
tool: "ruby",
|
|
1650
|
+
wanted: rubyVersionNeeded,
|
|
1651
|
+
found: existingRuby.version,
|
|
1652
|
+
source: "env",
|
|
1653
|
+
detail: `Ruby ${rubyVersionNeeded} is required and the active interpreter reports ${existingRuby.version}.`,
|
|
1654
|
+
});
|
|
496
1655
|
}
|
|
497
1656
|
if (rubyVersionNeeded && !rbenvPresent) {
|
|
498
1657
|
console.log(
|
|
@@ -618,7 +1777,13 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
618
1777
|
}
|
|
619
1778
|
} else {
|
|
620
1779
|
console.log(`Ruby install has failed for version ${rubyVersionNeeded}.`);
|
|
621
|
-
options.deep
|
|
1780
|
+
if (options.deep) {
|
|
1781
|
+
deferFailOnError(options, {
|
|
1782
|
+
ecosystem: "ruby",
|
|
1783
|
+
tool: "ruby",
|
|
1784
|
+
detail: `the ruby ${rubyVersionNeeded} install failed`,
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
622
1787
|
}
|
|
623
1788
|
} else {
|
|
624
1789
|
// Just attempt bundle install
|