@cdxgen/cdxgen 13.4.0 → 13.6.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 -9
- package/bin/cdxgen-secure.js +15 -0
- package/bin/cdxgen.js +29 -5
- package/bin/evinse.js +8 -0
- package/bin/hbom.js +15 -7
- package/bin/repl.js +29 -0
- package/bin/validate.js +6 -0
- package/data/cbomosdb-queries.json +13 -1
- package/data/component-tags.json +8 -0
- package/data/license-aliases.json +24 -2
- package/data/queries-darwin.json +25 -1
- package/data/queries-win.json +19 -1
- package/data/queries.json +41 -0
- package/data/remediations.json +407 -1
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/cbom-security.yaml +235 -4
- package/data/rules/go-binaries.yaml +46 -0
- package/data/rules/golem-go.yaml +80 -0
- package/data/rules/hbom-compliance.yaml +40 -22
- package/data/rules/hbom-performance.yaml +7 -1
- package/data/rules/host-topology.yaml +1 -1
- package/data/rules/obom-runtime.yaml +31 -2
- package/data/rules/php-runtime.yaml +143 -0
- package/data/rules/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/audit/index.js +14 -0
- package/lib/cli/bomAssembly.js +120 -6
- package/lib/cli/cliOptions.js +110 -3
- package/lib/cli/index.js +182 -6
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +363 -66
- package/lib/cli/managedBom.js +853 -87
- package/lib/cli/nativeBom.js +267 -33
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +38 -8
- package/lib/core/fs.js +40 -19
- package/lib/core/httpClient.js +172 -46
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -0
- package/lib/core/projectConfig.js +1 -0
- package/lib/core/projectTooling.js +97 -0
- package/lib/core/propertySanitizer.js +72 -0
- package/lib/core/toolRegistry.js +170 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/golem.js +550 -6
- package/lib/ecosystems/gradleutils.js +858 -244
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-dotnet.js +117 -18
- package/lib/ecosystems/parsers-go.js +1090 -367
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-jvm.js +259 -17
- package/lib/ecosystems/parsers-misc.js +438 -64
- package/lib/ecosystems/parsers-python.js +100 -12
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +544 -34
- package/lib/ecosystems/rusi.js +10 -0
- package/lib/ecosystems/scalacliutils.js +4 -1
- package/lib/ecosystems/scalasem.js +798 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +472 -61
- package/lib/evinser/swiftsem.js +68 -5
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/agentSkillSpec.js +17 -0
- package/lib/inventory/apkInstalledDb.js +243 -0
- package/lib/inventory/atomUtils.js +110 -0
- package/lib/inventory/auditCategories.js +3 -3
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cacheDir.js +52 -9
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/certificates.js +2371 -0
- package/lib/inventory/communityAiConfigParser.js +4 -9
- package/lib/inventory/deps.js +41 -4
- package/lib/inventory/display.js +200 -3
- package/lib/inventory/dosai.js +639 -63
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/dynamic.js +45 -3
- package/lib/inventory/envcontext.js +475 -18
- package/lib/inventory/evidenceUtils.js +447 -23
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +32 -3
- package/lib/inventory/goBuildInfo.js +140 -0
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hbom.js +67 -7
- package/lib/inventory/hbomAnalysis.js +16 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/hostTopology.js +21 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/osPackagePurl.js +244 -0
- package/lib/inventory/osPackageResolver.js +271 -87
- package/lib/inventory/osinfo.js +79 -31
- package/lib/inventory/osqueryTransform.js +114 -5
- package/lib/inventory/phpBuildInfo.js +438 -0
- package/lib/inventory/phpHostProbe.js +185 -0
- package/lib/inventory/rootfsCerts.js +426 -0
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +256 -5
- package/lib/inventory/spdx.js +144 -0
- package/lib/inventory/traceRunner.js +5 -1
- package/lib/inventory/tracedCrypto.js +129 -0
- package/lib/managers/binary.js +866 -186
- package/lib/managers/docker.js +245 -12
- package/lib/parsers/email.js +277 -0
- package/lib/parsers/largeJson.js +62 -13
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +198 -2
- package/lib/stages/postgen/annotator.js +17 -1
- package/lib/stages/postgen/introspection/reflect.js +4 -1
- package/lib/stages/postgen/postgen.js +5 -5
- package/lib/stages/postgen/ruleEngine.js +71 -6
- package/lib/stages/postgen/specVersionCompat.js +30 -0
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +231 -62
- package/lib/validator/bomValidator.js +32 -6
- package/lib/validator/generated/runtime.mjs +13 -0
- package/lib/validator/generated/validate-1.6.mjs +27 -8
- package/lib/validator/generated/validate-1.7.mjs +32 -13
- package/lib/validator/generated/validate-2.0.mjs +31 -12
- package/lib/validator/index.js +1 -1
- package/package.json +19 -18
- package/types/bin/cdxgen-secure.d.ts +3 -0
- package/types/bin/cdxgen-secure.d.ts.map +1 -0
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +22 -3
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +43 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +1 -17
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +20 -0
- 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/boundedFile.d.ts +70 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +22 -2
- 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 +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +2 -0
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/projectTooling.d.ts +41 -0
- package/types/lib/core/projectTooling.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +26 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts +81 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +57 -21
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +85 -13
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +37 -58
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +14 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +19 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +88 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +10 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
- package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
- package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
- package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +33 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +12 -4
- package/types/lib/inventory/cacheDir.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/certificates.d.ts +205 -0
- package/types/lib/inventory/certificates.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts +2 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts +18 -0
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +162 -7
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +6 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts +92 -8
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +29 -10
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/goBuildInfo.d.ts +10 -0
- package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/gtfobins.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
- package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/osPackagePurl.d.ts +99 -0
- package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +44 -2
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/osinfo.d.ts +31 -6
- package/types/lib/inventory/osinfo.d.ts.map +1 -1
- package/types/lib/inventory/osqueryTransform.d.ts +51 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/phpHostProbe.d.ts +35 -0
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rootfsCerts.d.ts +27 -0
- package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +38 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +15 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +4 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +22 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +10 -0
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +24 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/email.d.ts +19 -0
- package/types/lib/parsers/email.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +17 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts +8 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
- package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +26 -9
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
|
|
24
24
|
import { thoughtLog } from "../core/logger.js";
|
|
25
25
|
import { isWin } from "../core/paths.js";
|
|
26
|
+
import { isProjectToolingAllowed } from "../core/projectTooling.js";
|
|
26
27
|
import { parseToml } from "../core/toml.js";
|
|
27
28
|
import {
|
|
28
29
|
isBuildToolRateLimited,
|
|
@@ -30,6 +31,7 @@ import {
|
|
|
30
31
|
} from "../inventory/buildToolRateLimit.js";
|
|
31
32
|
import { collectJarNS } from "../inventory/deps.js";
|
|
32
33
|
import { minimumGradleVersionForJava } from "../inventory/jvmToolEnv.js";
|
|
34
|
+
import { gradleMirrorInitScriptArgs } from "../inventory/mavenCentralMirror.js";
|
|
33
35
|
import { applyPurl, tryBuildPurl } from "../inventory/purl.js";
|
|
34
36
|
import { parsePkgJson } from "./parsers-js.js";
|
|
35
37
|
|
|
@@ -44,13 +46,16 @@ function isJvmToolPinned() {
|
|
|
44
46
|
);
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
// The gate lives in lib/core so that every layer can reach it; it stays
|
|
50
|
+
// exported here for existing callers.
|
|
51
|
+
export { isProjectToolingAllowed };
|
|
52
|
+
|
|
47
53
|
/**
|
|
48
54
|
* Method to return the gradle command to use.
|
|
49
55
|
*
|
|
50
56
|
* Project-local wrapper scripts (`gradlew`) are attacker-controlled files in
|
|
51
|
-
* the scanned project, so they are only used when
|
|
52
|
-
*
|
|
53
|
-
* used.
|
|
57
|
+
* the scanned project, so they are only used when the project's own tooling
|
|
58
|
+
* may run (see isProjectToolingAllowed); otherwise the system Gradle is used.
|
|
54
59
|
*
|
|
55
60
|
* @param {string} srcPath Path to look for gradlew wrapper
|
|
56
61
|
* @param {string|null} rootPath Root directory to look for gradlew wrapper
|
|
@@ -59,10 +64,7 @@ function isJvmToolPinned() {
|
|
|
59
64
|
export function getGradleCommand(srcPath, rootPath, options = {}) {
|
|
60
65
|
let gradleCmd = "gradle";
|
|
61
66
|
const pinnedToolSelected = isJvmToolPinned();
|
|
62
|
-
|
|
63
|
-
// keeps using wrappers even when dependency installation is disabled.
|
|
64
|
-
const allowProjectWrappers =
|
|
65
|
-
options.installDeps !== false || options.introspect === true;
|
|
67
|
+
const allowProjectWrappers = isProjectToolingAllowed(options);
|
|
66
68
|
|
|
67
69
|
let findGradleFile = "gradlew";
|
|
68
70
|
if (platform() === "win32") {
|
|
@@ -217,6 +219,50 @@ function gradleEvaluationOverrides(gradleArguments) {
|
|
|
217
219
|
return overrides;
|
|
218
220
|
}
|
|
219
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Keep Gradle's task headers in the output. cdxgen splits the `properties`
|
|
224
|
+
* and `dependencies` reports by the `> Task :path` headers, which Gradle
|
|
225
|
+
* prints at the lifecycle log level, so a project that sets
|
|
226
|
+
* `org.gradle.logging.level=quiet` or `warn` in `gradle.properties` would
|
|
227
|
+
* leave nothing to split. A command-line `-D` property beats
|
|
228
|
+
* `gradle.properties`, and a log-level option such as `--info` beats the
|
|
229
|
+
* property. A log level the user chose in `GRADLE_ARGS` is left alone.
|
|
230
|
+
*
|
|
231
|
+
* @param {string[]} gradleArguments User-supplied general gradle arguments
|
|
232
|
+
* @returns {string[]} The `-D` flag to inject, or nothing
|
|
233
|
+
*/
|
|
234
|
+
function gradleLogLevelOverride(gradleArguments) {
|
|
235
|
+
const logLevelOptions = [
|
|
236
|
+
"-q",
|
|
237
|
+
"--quiet",
|
|
238
|
+
"-w",
|
|
239
|
+
"--warn",
|
|
240
|
+
"-i",
|
|
241
|
+
"--info",
|
|
242
|
+
"-d",
|
|
243
|
+
"--debug",
|
|
244
|
+
];
|
|
245
|
+
const userChose = gradleArguments.some(
|
|
246
|
+
(arg) =>
|
|
247
|
+
logLevelOptions.includes(arg) ||
|
|
248
|
+
arg.startsWith("-Dorg.gradle.logging.level="),
|
|
249
|
+
);
|
|
250
|
+
return userChose ? [] : ["-Dorg.gradle.logging.level=lifecycle"];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Read a whitespace-separated list of Gradle arguments from an environment
|
|
255
|
+
* variable such as `GRADLE_ARGS`. Repeated, leading or trailing whitespace
|
|
256
|
+
* adds no empty argument, which Gradle would read as a task path.
|
|
257
|
+
*
|
|
258
|
+
* @param {string} name Name of the environment variable
|
|
259
|
+
* @returns {string[]} The arguments, empty when the variable is unset
|
|
260
|
+
*/
|
|
261
|
+
export function gradleArgsFromEnv(name) {
|
|
262
|
+
const value = readEnvironmentVariable(name);
|
|
263
|
+
return value ? value.split(/\s+/).filter(Boolean) : [];
|
|
264
|
+
}
|
|
265
|
+
|
|
220
266
|
/**
|
|
221
267
|
* Method to combine the general gradle arguments, the sub-commands and the sub-commands' arguments in the correct way
|
|
222
268
|
*
|
|
@@ -241,6 +287,8 @@ export function buildGradleCommandArguments(
|
|
|
241
287
|
]
|
|
242
288
|
.concat(getGradleDaemonParameter())
|
|
243
289
|
.concat(gradleEvaluationOverrides(gradleArguments))
|
|
290
|
+
.concat(gradleLogLevelOverride(gradleArguments))
|
|
291
|
+
.concat(gradleMirrorInitScriptArgs())
|
|
244
292
|
.concat(gradleArguments);
|
|
245
293
|
const maxCliArgsLength = isWin
|
|
246
294
|
? 7500 - gradleCommandLength - mainGradleArguments.join(" ").length - 2
|
|
@@ -256,7 +304,13 @@ export function buildGradleCommandArguments(
|
|
|
256
304
|
for (const gradleSubCommand of gradleSubCommands) {
|
|
257
305
|
const subCommandLength =
|
|
258
306
|
[gradleSubCommand, ...gradleSubCommandArguments].join(" ").length + 1;
|
|
259
|
-
|
|
307
|
+
// An invocation always carries at least one task: without one Gradle
|
|
308
|
+
// runs the project's default tasks.
|
|
309
|
+
if (
|
|
310
|
+
maxCliArgsLength !== -1 &&
|
|
311
|
+
remainingLength - subCommandLength < 0 &&
|
|
312
|
+
allGradleArguments.length > mainGradleArguments.length
|
|
313
|
+
) {
|
|
260
314
|
splitArgs.push(allGradleArguments);
|
|
261
315
|
allGradleArguments = [].concat(mainGradleArguments);
|
|
262
316
|
remainingLength = maxCliArgsLength;
|
|
@@ -296,16 +350,52 @@ function parseGradleTaskHeader(line) {
|
|
|
296
350
|
if (!line.startsWith("> Task :")) {
|
|
297
351
|
return undefined;
|
|
298
352
|
}
|
|
299
|
-
//
|
|
300
|
-
// follow
|
|
301
|
-
|
|
353
|
+
// A project path may contain spaces (`:my lib`), and a status such as
|
|
354
|
+
// UP-TO-DATE or FAILED may follow the task name, so the task name is the
|
|
355
|
+
// first token after the last colon. A status never contains a colon, and a
|
|
356
|
+
// CRLF line keeps its carriage return.
|
|
357
|
+
const taskPath = line.slice("> Task ".length).trim();
|
|
302
358
|
const lastColon = taskPath.lastIndexOf(":");
|
|
303
359
|
return {
|
|
304
360
|
projectPath: taskPath.slice(0, lastColon),
|
|
305
|
-
taskName: taskPath.slice(lastColon + 1),
|
|
361
|
+
taskName: taskPath.slice(lastColon + 1).split(/\s+/)[0],
|
|
306
362
|
};
|
|
307
363
|
}
|
|
308
364
|
|
|
365
|
+
/**
|
|
366
|
+
* List the dependency tasks to run for the root project and every module read
|
|
367
|
+
* from `gradle properties`.
|
|
368
|
+
*
|
|
369
|
+
* The root project is recognised by identity, never by purl. A module named
|
|
370
|
+
* after the root project that inherits its group and version has the root's
|
|
371
|
+
* purl (issue 4695), and it still needs its own `:module:dependencies` task.
|
|
372
|
+
* `root` in `modulesToSkip` drops the root task; any other entry is a module
|
|
373
|
+
* path.
|
|
374
|
+
*
|
|
375
|
+
* @param {Map<string, Object>} gradleModules Modules keyed by project path, and the root project by its name
|
|
376
|
+
* @param {Object} rootComponent The component built for the root project
|
|
377
|
+
* @param {string} gradleDepTask Name of the dependency task, usually `dependencies`
|
|
378
|
+
* @param {string[]} modulesToSkip Module paths, or `root`, to leave out
|
|
379
|
+
* @returns {string[]} Task paths in the order Gradle should run them
|
|
380
|
+
*/
|
|
381
|
+
export function gradleDependencyTasks(
|
|
382
|
+
gradleModules,
|
|
383
|
+
rootComponent,
|
|
384
|
+
gradleDepTask,
|
|
385
|
+
modulesToSkip = [],
|
|
386
|
+
) {
|
|
387
|
+
const tasks = [];
|
|
388
|
+
if (!modulesToSkip.includes("root")) {
|
|
389
|
+
tasks.push(gradleDepTask);
|
|
390
|
+
}
|
|
391
|
+
for (const [key, module] of gradleModules) {
|
|
392
|
+
if (module !== rootComponent && !modulesToSkip.includes(key)) {
|
|
393
|
+
tasks.push(`${key}:${gradleDepTask}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return tasks;
|
|
397
|
+
}
|
|
398
|
+
|
|
309
399
|
/**
|
|
310
400
|
* Method to split the output produced by Gradle using parallel processing by project
|
|
311
401
|
*
|
|
@@ -397,27 +487,19 @@ export function parseGradleProjects(rawOutput) {
|
|
|
397
487
|
}
|
|
398
488
|
}
|
|
399
489
|
}
|
|
400
|
-
} else
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const tmpName = projName.split(" ")[0];
|
|
406
|
-
if (tmpName.length > 1) {
|
|
407
|
-
projects.add(tmpName);
|
|
408
|
-
}
|
|
490
|
+
} else {
|
|
491
|
+
for (const marker of ["--- project ", "-> project "]) {
|
|
492
|
+
const markerIndex = l.indexOf(marker);
|
|
493
|
+
if (markerIndex === -1) {
|
|
494
|
+
continue;
|
|
409
495
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
if (projName.startsWith(":")) {
|
|
416
|
-
const tmpName = projName.split(" ")[0];
|
|
417
|
-
if (tmpName.length > 1) {
|
|
418
|
-
projects.add(tmpName);
|
|
419
|
-
}
|
|
496
|
+
const projName = gradleProjectPath(
|
|
497
|
+
l.slice(markerIndex + marker.length),
|
|
498
|
+
);
|
|
499
|
+
if (projName?.startsWith(":") && projName.length > 1) {
|
|
500
|
+
projects.add(projName);
|
|
420
501
|
}
|
|
502
|
+
break;
|
|
421
503
|
}
|
|
422
504
|
}
|
|
423
505
|
});
|
|
@@ -431,11 +513,17 @@ export function parseGradleProjects(rawOutput) {
|
|
|
431
513
|
/**
|
|
432
514
|
* Parse gradle properties output
|
|
433
515
|
*
|
|
516
|
+
* `gradlePlugin` is true when the project has the `gradlePlugin` extension,
|
|
517
|
+
* which `java-gradle-plugin`, `kotlin-dsl` and `groovy-gradle-plugin` add: the
|
|
518
|
+
* project builds Gradle plugins, as a `build-logic` build does.
|
|
519
|
+
*
|
|
434
520
|
* @param {string} rawOutput Raw string output
|
|
435
521
|
* @param {string} gradleModuleName The name (or 'path') of the module as seen from the root of the project
|
|
522
|
+
* @returns {{rootProject: string, projects: string[], metadata: Object, gradlePlugin: boolean}} The parsed properties
|
|
436
523
|
*/
|
|
437
524
|
export function parseGradleProperties(rawOutput, gradleModuleName = null) {
|
|
438
525
|
let rootProject = "root";
|
|
526
|
+
let gradlePlugin = false;
|
|
439
527
|
const projects = new Set();
|
|
440
528
|
const metadata = { group: "", version: "latest", properties: [] };
|
|
441
529
|
if (gradleModuleName) {
|
|
@@ -471,6 +559,8 @@ export function parseGradleProperties(rawOutput, gradleModuleName = null) {
|
|
|
471
559
|
metadata[tmpB[0]] = tmpB[1].trim().replace("unspecified", "latest");
|
|
472
560
|
} else if (["buildFile", "projectDir", "rootDir"].includes(tmpB[0])) {
|
|
473
561
|
metadata.properties.push({ name: tmpB[0], value: tmpB[1].trim() });
|
|
562
|
+
} else if (tmpB[0] === "gradlePlugin") {
|
|
563
|
+
gradlePlugin = true;
|
|
474
564
|
} else if (tmpB[0] === "subprojects") {
|
|
475
565
|
const spStrs = tmpB[1].replace(/[[\]']/g, "").split(", ");
|
|
476
566
|
const tmpprojects = spStrs
|
|
@@ -485,6 +575,7 @@ export function parseGradleProperties(rawOutput, gradleModuleName = null) {
|
|
|
485
575
|
rootProject,
|
|
486
576
|
projects: Array.from(projects),
|
|
487
577
|
metadata,
|
|
578
|
+
gradlePlugin,
|
|
488
579
|
};
|
|
489
580
|
}
|
|
490
581
|
|
|
@@ -510,17 +601,11 @@ export function executeParallelGradleProperties(
|
|
|
510
601
|
}
|
|
511
602
|
const gradleCmd = getGradleCommand(dir, null, options);
|
|
512
603
|
const gradleArgs = buildGradleCommandArguments(
|
|
513
|
-
extraArgs.concat(
|
|
514
|
-
readEnvironmentVariable("GRADLE_ARGS")
|
|
515
|
-
? readEnvironmentVariable("GRADLE_ARGS").split(" ")
|
|
516
|
-
: [],
|
|
517
|
-
),
|
|
604
|
+
extraArgs.concat(gradleArgsFromEnv("GRADLE_ARGS")),
|
|
518
605
|
allProjectsStr.map((project) =>
|
|
519
606
|
project ? `${project}:properties` : "properties",
|
|
520
607
|
),
|
|
521
|
-
|
|
522
|
-
? readEnvironmentVariable("GRADLE_ARGS_PROPERTIES").split(" ")
|
|
523
|
-
: [],
|
|
608
|
+
gradleArgsFromEnv("GRADLE_ARGS_PROPERTIES"),
|
|
524
609
|
gradleCmd.length,
|
|
525
610
|
);
|
|
526
611
|
const allOutputs = [];
|
|
@@ -879,19 +964,526 @@ export function recordGradleInvocationFailure(result, context = {}) {
|
|
|
879
964
|
});
|
|
880
965
|
}
|
|
881
966
|
|
|
967
|
+
/**
|
|
968
|
+
* Configurations whose dependencies never reach the packaged application:
|
|
969
|
+
* code-quality tools, coverage agents and Spring Boot's development-only
|
|
970
|
+
* dependencies.
|
|
971
|
+
*/
|
|
972
|
+
const GRADLE_TOOL_CONFIGURATIONS = new Set([
|
|
973
|
+
"checkstyle",
|
|
974
|
+
"codenarc",
|
|
975
|
+
"detekt",
|
|
976
|
+
"developmentOnly",
|
|
977
|
+
"errorprone",
|
|
978
|
+
"jacocoAgent",
|
|
979
|
+
"jacocoAnt",
|
|
980
|
+
"ktlint",
|
|
981
|
+
"lintChecks",
|
|
982
|
+
"lintClassPath",
|
|
983
|
+
"pmd",
|
|
984
|
+
"spotbugs",
|
|
985
|
+
"spotbugsPlugins",
|
|
986
|
+
]);
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* Parts of a configuration name that mark it as feeding the compiler or a
|
|
990
|
+
* build tool, such as `annotationProcessor`, Android's
|
|
991
|
+
* `debugAnnotationProcessorClasspath` or `kotlinCompilerPluginClasspathMain`.
|
|
992
|
+
*/
|
|
993
|
+
const GRADLE_TOOL_CONFIGURATION_PARTS = [
|
|
994
|
+
"annotationprocessor",
|
|
995
|
+
"buildtoolsapiclasspath",
|
|
996
|
+
"commonizer",
|
|
997
|
+
"compilerclasspath",
|
|
998
|
+
"compilerpluginclasspath",
|
|
999
|
+
];
|
|
1000
|
+
|
|
1001
|
+
/**
|
|
1002
|
+
* Properties that describe a dependency Gradle could not resolve.
|
|
1003
|
+
*/
|
|
1004
|
+
const GRADLE_FAILURE_PROPERTIES = [
|
|
1005
|
+
"cdx:gradle:resolutionFailed",
|
|
1006
|
+
"cdx:gradle:requestedVersion",
|
|
1007
|
+
];
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Markers Gradle appends to a dependency tree line, with the flag each sets.
|
|
1011
|
+
*/
|
|
1012
|
+
const GRADLE_TREE_LINE_MARKERS = [
|
|
1013
|
+
["(*)", "omitted"],
|
|
1014
|
+
["(c)", "constraint"],
|
|
1015
|
+
["(n)", "unresolvable"],
|
|
1016
|
+
["FAILED", "failed"],
|
|
1017
|
+
];
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* Read the name of the configuration a `gradle dependencies` header line
|
|
1021
|
+
* introduces, such as `runtimeClasspath - Runtime classpath of source set
|
|
1022
|
+
* 'main'.`, `api (n)`, a bare `classpath`, a Groovy name such as
|
|
1023
|
+
* `integration-test` or an internal one such as
|
|
1024
|
+
* `_internal-unified-test-platform-android-device-provider-ddmlib`.
|
|
1025
|
+
*
|
|
1026
|
+
* @param {string} line One line of the report
|
|
1027
|
+
* @returns {string|undefined} The configuration name, or undefined when the
|
|
1028
|
+
* line is not a configuration header
|
|
1029
|
+
*/
|
|
1030
|
+
function parseGradleConfigurationHeader(line) {
|
|
1031
|
+
let name = line.split(" - ")[0];
|
|
1032
|
+
if (name.endsWith(" (n)")) {
|
|
1033
|
+
name = name.slice(0, -" (n)".length);
|
|
1034
|
+
}
|
|
1035
|
+
return /^[A-Za-z_][A-Za-z0-9_.-]*$/.test(name) ? name : undefined;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Configurations, other than runtime classpaths, whose dependencies ship
|
|
1040
|
+
* with the application: `compile` and `default` in Gradle 6 and older, which
|
|
1041
|
+
* the runtime classpath extended, and the Shadow plugin's `shadow`, whose
|
|
1042
|
+
* dependencies the published artifact declares as runtime dependencies.
|
|
1043
|
+
*/
|
|
1044
|
+
const GRADLE_SHIPPED_CONFIGURATIONS = new Set(["compile", "default", "shadow"]);
|
|
1045
|
+
|
|
1046
|
+
/**
|
|
1047
|
+
* Map a Gradle configuration to the CycloneDX scope of the dependencies it
|
|
1048
|
+
* resolves, so that only what ships with the application reads as required.
|
|
1049
|
+
*
|
|
1050
|
+
* Configurations whose name starts with `_`, such as the Android Gradle
|
|
1051
|
+
* plugin's `_internal-unified-test-platform-*` ones, are the plugin's own
|
|
1052
|
+
* tooling and `excluded`. Test configurations are `optional`. Runtime
|
|
1053
|
+
* classpaths, including Android
|
|
1054
|
+
* variants such as `debugRuntimeClasspath` and Kotlin targets such as
|
|
1055
|
+
* `jvmRuntimeClasspath`, are `required`, and so are Kotlin/Native klibraries,
|
|
1056
|
+
* which are linked into the binary, and the configurations in
|
|
1057
|
+
* `GRADLE_SHIPPED_CONFIGURATIONS`. Everything else is `excluded`: annotation
|
|
1058
|
+
* processors, compiler, code-quality and other build-tool classpaths, and
|
|
1059
|
+
* compile classpaths, whose dependencies a runtime classpath also lists unless
|
|
1060
|
+
* they are compile-only, as Maven's `provided` scope is. A dependency seen in
|
|
1061
|
+
* several configurations keeps the strongest scope among them. Test and
|
|
1062
|
+
* runtime are matched as whole words of the camel-case name, so
|
|
1063
|
+
* `latestRuntimeClasspath` is a runtime classpath, not a test one.
|
|
1064
|
+
*
|
|
1065
|
+
* @param {string} configurationName Name of the configuration
|
|
1066
|
+
* @returns {string} The CycloneDX scope
|
|
1067
|
+
*/
|
|
1068
|
+
function gradleConfigurationScope(configurationName) {
|
|
1069
|
+
const lowerName = configurationName.toLowerCase();
|
|
1070
|
+
const words = configurationName
|
|
1071
|
+
.split(/[-_.]|(?=[A-Z])/)
|
|
1072
|
+
.map((word) => word.toLowerCase());
|
|
1073
|
+
if (
|
|
1074
|
+
GRADLE_TOOL_CONFIGURATIONS.has(configurationName) ||
|
|
1075
|
+
configurationName.startsWith("_") ||
|
|
1076
|
+
["kapt", "ksp"].includes(words[0]) ||
|
|
1077
|
+
GRADLE_TOOL_CONFIGURATION_PARTS.some((part) => lowerName.includes(part))
|
|
1078
|
+
) {
|
|
1079
|
+
return "excluded";
|
|
1080
|
+
}
|
|
1081
|
+
if (words.includes("test") || words.includes("tests")) {
|
|
1082
|
+
return "optional";
|
|
1083
|
+
}
|
|
1084
|
+
if (
|
|
1085
|
+
words.includes("runtime") ||
|
|
1086
|
+
words.includes("klibraries") ||
|
|
1087
|
+
GRADLE_SHIPPED_CONFIGURATIONS.has(configurationName)
|
|
1088
|
+
) {
|
|
1089
|
+
return "required";
|
|
1090
|
+
}
|
|
1091
|
+
return "excluded";
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Rank a scope so that a dependency seen in several configurations keeps the
|
|
1096
|
+
* strongest one: `required` or no scope (which CycloneDX reads as required),
|
|
1097
|
+
* then `optional`, then `excluded`.
|
|
1098
|
+
*
|
|
1099
|
+
* @param {string|undefined} scope CycloneDX scope
|
|
1100
|
+
* @returns {number} The rank, higher being stronger
|
|
1101
|
+
*/
|
|
1102
|
+
function gradleScopeRank(scope) {
|
|
1103
|
+
if (scope === "required" || scope === undefined) {
|
|
1104
|
+
return 2;
|
|
1105
|
+
}
|
|
1106
|
+
return scope === "optional" ? 1 : 0;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Read a project path from the text that follows `project ` in a dependency
|
|
1111
|
+
* tree line. Gradle 9 quotes the path (`project ':lib'`), Gradle 8 does not
|
|
1112
|
+
* (`project :lib`).
|
|
1113
|
+
*
|
|
1114
|
+
* @param {string} text The text after `project `
|
|
1115
|
+
* @returns {string|undefined} The project path, such as `:lib`
|
|
1116
|
+
*/
|
|
1117
|
+
function gradleProjectPath(text) {
|
|
1118
|
+
const trimmed = text.trim();
|
|
1119
|
+
if (trimmed.startsWith("'")) {
|
|
1120
|
+
const end = trimmed.indexOf("'", 1);
|
|
1121
|
+
return end > 1 ? trimmed.slice(1, end) : undefined;
|
|
1122
|
+
}
|
|
1123
|
+
return trimmed.split(" ")[0] || undefined;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Find the module a project path in a dependency tree names. Subprojects are
|
|
1128
|
+
* keyed by their path (`:lib`) and the root project by its name, so the root
|
|
1129
|
+
* path `:` resolves to the report's own project when that is the root, or else
|
|
1130
|
+
* to the one module keyed by a name.
|
|
1131
|
+
*
|
|
1132
|
+
* @param {Map} gradleModules Modules keyed by project path or root name
|
|
1133
|
+
* @param {string} projectPath Project path from the tree, such as `:lib` or `:`
|
|
1134
|
+
* @param {string} reportProjectName Key of the project the report belongs to
|
|
1135
|
+
* @returns {Object|undefined} The module component
|
|
1136
|
+
*/
|
|
1137
|
+
function gradleModuleForPath(gradleModules, projectPath, reportProjectName) {
|
|
1138
|
+
if (projectPath !== ":") {
|
|
1139
|
+
return gradleModules.get(projectPath);
|
|
1140
|
+
}
|
|
1141
|
+
if (!reportProjectName.startsWith(":")) {
|
|
1142
|
+
return gradleModules.get(reportProjectName);
|
|
1143
|
+
}
|
|
1144
|
+
const rootKeys = [...gradleModules.keys()].filter((k) => !k.startsWith(":"));
|
|
1145
|
+
return rootKeys.length === 1 ? gradleModules.get(rootKeys[0]) : undefined;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Pick a concrete version out of the version Gradle printed as requested. A
|
|
1150
|
+
* rich version such as `{strictly 1.0}`, `{require 2.2; reject 2.0}` or
|
|
1151
|
+
* `{prefer 2.0.9}` gives the version of its first strictly, require or prefer
|
|
1152
|
+
* clause. A range or dynamic version gives none.
|
|
1153
|
+
*
|
|
1154
|
+
* @param {string|undefined} versionText The requested version
|
|
1155
|
+
* @returns {string|undefined} The concrete version
|
|
1156
|
+
*/
|
|
1157
|
+
function gradleRequestedVersion(versionText) {
|
|
1158
|
+
if (!versionText) {
|
|
1159
|
+
return undefined;
|
|
1160
|
+
}
|
|
1161
|
+
// `1.+` and `latest.release` are dynamic and a bracketed range is a
|
|
1162
|
+
// range, but `0.92.2+1.20.1` is a concrete version with build metadata.
|
|
1163
|
+
const isConcrete = (v) =>
|
|
1164
|
+
v &&
|
|
1165
|
+
!v.endsWith("+") &&
|
|
1166
|
+
!v.startsWith("latest.") &&
|
|
1167
|
+
![..."[](),"].some((ch) => v.includes(ch));
|
|
1168
|
+
if (!versionText.startsWith("{")) {
|
|
1169
|
+
return isConcrete(versionText) ? versionText : undefined;
|
|
1170
|
+
}
|
|
1171
|
+
const body = versionText.slice(1, versionText.lastIndexOf("}"));
|
|
1172
|
+
for (const clause of body.split(";")) {
|
|
1173
|
+
const [keyword, ...rest] = clause.trim().split(" ");
|
|
1174
|
+
const value = rest.join(" ").trim();
|
|
1175
|
+
if (
|
|
1176
|
+
["strictly", "require", "prefer"].includes(keyword) &&
|
|
1177
|
+
isConcrete(value)
|
|
1178
|
+
) {
|
|
1179
|
+
return value;
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return undefined;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Split one line of a `gradle dependencies` tree into its depth and the
|
|
1187
|
+
* dependency it names. The selected side of `->` wins over the requested
|
|
1188
|
+
* side, whether it is a version, other coordinates or a project.
|
|
1189
|
+
*
|
|
1190
|
+
* @param {string} line One line of the report, without its line ending
|
|
1191
|
+
* @returns {Object|undefined} `{ level, group, name, version,
|
|
1192
|
+
* requestedVersion, projectPath, omitted, constraint, unresolvable, failed }`,
|
|
1193
|
+
* `{ level, unknown: true }` for a tree line whose dependency cannot be
|
|
1194
|
+
* read, or undefined when the line is not a tree line
|
|
1195
|
+
*/
|
|
1196
|
+
function parseGradleTreeLine(line) {
|
|
1197
|
+
const markerIndex = line.indexOf("--- ");
|
|
1198
|
+
if (markerIndex < 1) {
|
|
1199
|
+
return undefined;
|
|
1200
|
+
}
|
|
1201
|
+
for (const ch of line.slice(0, markerIndex)) {
|
|
1202
|
+
if (!"|+\\ ".includes(ch)) {
|
|
1203
|
+
return undefined;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
const parsed = {
|
|
1207
|
+
level: Math.floor(markerIndex / 5) + 1,
|
|
1208
|
+
omitted: false,
|
|
1209
|
+
constraint: false,
|
|
1210
|
+
unresolvable: false,
|
|
1211
|
+
failed: false,
|
|
1212
|
+
};
|
|
1213
|
+
let text = line.slice(markerIndex + "--- ".length).trim();
|
|
1214
|
+
let stripped = true;
|
|
1215
|
+
while (stripped) {
|
|
1216
|
+
stripped = false;
|
|
1217
|
+
for (const [marker, flag] of GRADLE_TREE_LINE_MARKERS) {
|
|
1218
|
+
if (text.endsWith(` ${marker}`)) {
|
|
1219
|
+
parsed[flag] = true;
|
|
1220
|
+
text = text.slice(0, -marker.length - 1).trimEnd();
|
|
1221
|
+
stripped = true;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
const [requested, selected] = text.split(" -> ");
|
|
1226
|
+
if (requested.startsWith("project ")) {
|
|
1227
|
+
parsed.projectPath = gradleProjectPath(requested.slice("project ".length));
|
|
1228
|
+
} else {
|
|
1229
|
+
const [group, name, ...versionParts] = requested.split(":");
|
|
1230
|
+
if (!group || !name || group.includes(" ") || name.includes(" ")) {
|
|
1231
|
+
return { level: parsed.level, unknown: true };
|
|
1232
|
+
}
|
|
1233
|
+
parsed.group = group;
|
|
1234
|
+
parsed.name = name;
|
|
1235
|
+
parsed.requestedVersion = versionParts.join(":") || undefined;
|
|
1236
|
+
parsed.version = gradleRequestedVersion(parsed.requestedVersion);
|
|
1237
|
+
}
|
|
1238
|
+
if (selected?.startsWith("project ")) {
|
|
1239
|
+
parsed.projectPath = gradleProjectPath(selected.slice("project ".length));
|
|
1240
|
+
} else if (selected?.includes(":")) {
|
|
1241
|
+
// The selected module replaces the requested one, version and all.
|
|
1242
|
+
const [group, name, version] = selected.split(":");
|
|
1243
|
+
parsed.group = group;
|
|
1244
|
+
parsed.name = name;
|
|
1245
|
+
parsed.version = version;
|
|
1246
|
+
parsed.requestedVersion = undefined;
|
|
1247
|
+
} else if (selected) {
|
|
1248
|
+
parsed.version = selected;
|
|
1249
|
+
}
|
|
1250
|
+
if (parsed.projectPath === undefined && !parsed.group) {
|
|
1251
|
+
return { level: parsed.level, unknown: true };
|
|
1252
|
+
}
|
|
1253
|
+
return parsed;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* Read the properties of every project the dependency reports name that
|
|
1258
|
+
* `gradleModules` does not have yet, such as a project of an included build,
|
|
1259
|
+
* and add a module for each one whose properties task printed a report. One
|
|
1260
|
+
* Gradle invocation covers all of them.
|
|
1261
|
+
*
|
|
1262
|
+
* @param {Iterable<string>} reports Text of the `gradle dependencies` reports
|
|
1263
|
+
* @param {Map} gradleModules Modules keyed by project path, updated in place
|
|
1264
|
+
* @param {string} gradleRootPath Directory to run Gradle in
|
|
1265
|
+
* @param {Object} options CLI options
|
|
1266
|
+
* @returns {Promise<string[]>} Paths of the projects added
|
|
1267
|
+
*/
|
|
1268
|
+
export async function loadGradleModulesForReports(
|
|
1269
|
+
reports,
|
|
1270
|
+
gradleModules,
|
|
1271
|
+
gradleRootPath,
|
|
1272
|
+
options = {},
|
|
1273
|
+
) {
|
|
1274
|
+
const modulesToSkip = readEnvironmentVariable("GRADLE_SKIP_MODULES")
|
|
1275
|
+
? readEnvironmentVariable("GRADLE_SKIP_MODULES").split(",")
|
|
1276
|
+
: [];
|
|
1277
|
+
const missing = new Set();
|
|
1278
|
+
for (const report of reports) {
|
|
1279
|
+
for (const projectPath of parseGradleProjects(report).projects) {
|
|
1280
|
+
if (
|
|
1281
|
+
!gradleModules.has(projectPath) &&
|
|
1282
|
+
!modulesToSkip.includes(projectPath)
|
|
1283
|
+
) {
|
|
1284
|
+
missing.add(projectPath);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if (!missing.size || !isProjectToolingAllowed(options)) {
|
|
1289
|
+
return [];
|
|
1290
|
+
}
|
|
1291
|
+
const splitPropTaskOut = splitOutputByGradleProjects(
|
|
1292
|
+
executeParallelGradleProperties(gradleRootPath, [...missing], [], options),
|
|
1293
|
+
["properties"],
|
|
1294
|
+
);
|
|
1295
|
+
const added = [];
|
|
1296
|
+
for (const projectPath of missing) {
|
|
1297
|
+
// A project whose properties task printed nothing has no metadata to
|
|
1298
|
+
// build a module from.
|
|
1299
|
+
if (!splitPropTaskOut.has(projectPath)) {
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
const propMap = parseGradleProperties(
|
|
1303
|
+
splitPropTaskOut.get(projectPath),
|
|
1304
|
+
projectPath,
|
|
1305
|
+
);
|
|
1306
|
+
gradleModules.set(
|
|
1307
|
+
projectPath,
|
|
1308
|
+
await buildObjectForGradleModule(
|
|
1309
|
+
propMap.rootProject === "root"
|
|
1310
|
+
? projectPath.split(":").pop()
|
|
1311
|
+
: propMap.rootProject,
|
|
1312
|
+
propMap.metadata,
|
|
1313
|
+
),
|
|
1314
|
+
);
|
|
1315
|
+
added.push(projectPath);
|
|
1316
|
+
}
|
|
1317
|
+
return added;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Give the components of a build-tooling report, such as that of an included
|
|
1322
|
+
* build that builds Gradle plugins, the `excluded` scope that build-tool
|
|
1323
|
+
* configurations get. `reconcileGradleComponents` later keeps a stronger
|
|
1324
|
+
* scope that another project's report gives the same component.
|
|
1325
|
+
*
|
|
1326
|
+
* @param {Object[]} pkgList Components from one project's report, updated in place
|
|
1327
|
+
* @param {Map<string, Object>} gradleModules Modules, which keep their own scope
|
|
1328
|
+
* @returns {Object[]} The same list
|
|
1329
|
+
*/
|
|
1330
|
+
export function excludeGradleBuildTooling(pkgList, gradleModules) {
|
|
1331
|
+
const modules = new Set(gradleModules.values());
|
|
1332
|
+
for (const pkg of pkgList) {
|
|
1333
|
+
if (!modules.has(pkg)) {
|
|
1334
|
+
pkg.scope = "excluded";
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
return pkgList;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/**
|
|
1341
|
+
* Reconcile the copies of a component that several Gradle projects report.
|
|
1342
|
+
* Every copy gets the strongest scope any project gave it, so a library one
|
|
1343
|
+
* module only tests with stays required when another module compiles against
|
|
1344
|
+
* it, and a dependency one project failed to resolve loses its failure
|
|
1345
|
+
* properties when another project resolved it.
|
|
1346
|
+
*
|
|
1347
|
+
* @param {Object[]} pkgList Components from all the projects, updated in place
|
|
1348
|
+
* @returns {Object[]} The same list
|
|
1349
|
+
*/
|
|
1350
|
+
export function reconcileGradleComponents(pkgList) {
|
|
1351
|
+
const strongest = new Map();
|
|
1352
|
+
const resolvedSomewhere = new Set();
|
|
1353
|
+
const isFailed = (pkg) =>
|
|
1354
|
+
pkg.properties?.some((p) => p.name === "cdx:gradle:resolutionFailed");
|
|
1355
|
+
for (const pkg of pkgList) {
|
|
1356
|
+
const key = pkg?.["bom-ref"];
|
|
1357
|
+
if (!key) {
|
|
1358
|
+
continue;
|
|
1359
|
+
}
|
|
1360
|
+
if (
|
|
1361
|
+
!strongest.has(key) ||
|
|
1362
|
+
gradleScopeRank(pkg.scope) > gradleScopeRank(strongest.get(key))
|
|
1363
|
+
) {
|
|
1364
|
+
strongest.set(key, pkg.scope);
|
|
1365
|
+
}
|
|
1366
|
+
if (!isFailed(pkg)) {
|
|
1367
|
+
resolvedSomewhere.add(key);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
for (const pkg of pkgList) {
|
|
1371
|
+
const key = pkg?.["bom-ref"];
|
|
1372
|
+
if (!key) {
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
if (strongest.get(key) === undefined) {
|
|
1376
|
+
delete pkg.scope;
|
|
1377
|
+
} else {
|
|
1378
|
+
pkg.scope = strongest.get(key);
|
|
1379
|
+
}
|
|
1380
|
+
if (resolvedSomewhere.has(key) && isFailed(pkg)) {
|
|
1381
|
+
pkg.properties = pkg.properties.filter(
|
|
1382
|
+
(p) => !GRADLE_FAILURE_PROPERTIES.includes(p.name),
|
|
1383
|
+
);
|
|
1384
|
+
if (!pkg.properties.length) {
|
|
1385
|
+
delete pkg.properties;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
return pkgList;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/**
|
|
1393
|
+
* Whether a module name is one that platforms and BOMs use, such as
|
|
1394
|
+
* `jackson-bom`, `spring-boot-dependencies`, `micronaut-platform` or the AWS
|
|
1395
|
+
* SDK's `bom`.
|
|
1396
|
+
*
|
|
1397
|
+
* @param {string} name Module name
|
|
1398
|
+
* @returns {boolean} True for a platform-style name
|
|
1399
|
+
*/
|
|
1400
|
+
function isGradlePlatformName(name) {
|
|
1401
|
+
return (
|
|
1402
|
+
name === "bom" ||
|
|
1403
|
+
["-bom", "-dependencies", "-platform"].some((suffix) =>
|
|
1404
|
+
name.endsWith(suffix),
|
|
1405
|
+
)
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/**
|
|
1410
|
+
* Give the platforms and BOMs in one report a `pom` type and the `excluded`
|
|
1411
|
+
* scope. Gradle prints a platform like a library, with only constraints
|
|
1412
|
+
* (`(c)`) under it, but a platform is a pom that never ships. A module counts
|
|
1413
|
+
* as a platform when its name is one platforms use and the report gives it no
|
|
1414
|
+
* dependency of its own, so a jar such as `jna-platform`, which depends on
|
|
1415
|
+
* `jna`, stays a jar.
|
|
1416
|
+
*
|
|
1417
|
+
* @param {Map<string, {adep: Object, isModule: boolean}>} depsByRef Components of the report by bom-ref, updated in place
|
|
1418
|
+
* @param {Object<string, string[]>} levelTrees Edges of the report by bom-ref
|
|
1419
|
+
* @returns {Map<string, string>} The new bom-ref of each renamed component, keyed by its old one
|
|
1420
|
+
*/
|
|
1421
|
+
function markGradlePlatforms(depsByRef, levelTrees) {
|
|
1422
|
+
const renamed = new Map();
|
|
1423
|
+
for (const [bomRef, { adep, isModule }] of depsByRef) {
|
|
1424
|
+
if (
|
|
1425
|
+
isModule ||
|
|
1426
|
+
adep.qualifiers?.type !== "jar" ||
|
|
1427
|
+
!isGradlePlatformName(adep.name || "") ||
|
|
1428
|
+
levelTrees[bomRef]?.length
|
|
1429
|
+
) {
|
|
1430
|
+
continue;
|
|
1431
|
+
}
|
|
1432
|
+
const purl = tryBuildPurl({
|
|
1433
|
+
type: "maven",
|
|
1434
|
+
namespace: adep.group || null,
|
|
1435
|
+
name: adep.name,
|
|
1436
|
+
version: adep.version || null,
|
|
1437
|
+
qualifiers: { type: "pom" },
|
|
1438
|
+
});
|
|
1439
|
+
if (!purl) {
|
|
1440
|
+
continue;
|
|
1441
|
+
}
|
|
1442
|
+
adep.qualifiers = { type: "pom" };
|
|
1443
|
+
adep.scope = "excluded";
|
|
1444
|
+
applyPurl(adep, purl);
|
|
1445
|
+
renamed.set(bomRef, adep["bom-ref"]);
|
|
1446
|
+
}
|
|
1447
|
+
return renamed;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
882
1450
|
/**
|
|
883
1451
|
* Method to resolve dependencies from a gradle output
|
|
884
1452
|
*
|
|
1453
|
+
* A constraint line (`(c)`) adds neither a component nor an edge. Gradle
|
|
1454
|
+
* prints a nested constraint under the platform or module that declares it,
|
|
1455
|
+
* and only when the dependency it affects is resolved elsewhere in the tree,
|
|
1456
|
+
* where that dependency gets its component and edges. A top-level
|
|
1457
|
+
* `{strictly …}` constraint from consistent resolution can name a module the
|
|
1458
|
+
* configuration never resolves. A dependency that failed to resolve carries
|
|
1459
|
+
* `cdx:gradle:resolutionFailed`, keeps the version it was requested at when
|
|
1460
|
+
* that is a concrete version, and otherwise records the request in
|
|
1461
|
+
* `cdx:gradle:requestedVersion`. A dependency seen in several configurations
|
|
1462
|
+
* keeps the strongest scope among them. Platforms and BOMs get a `pom` type
|
|
1463
|
+
* and the `excluded` scope.
|
|
1464
|
+
*
|
|
1465
|
+
* The parser runs no Gradle task. A project the tree names but `gradleModules`
|
|
1466
|
+
* lacks gets a component built from the root project's group and version; use
|
|
1467
|
+
* `loadGradleModulesForReports` first to read such projects' properties.
|
|
1468
|
+
*
|
|
1469
|
+
* The lines Gradle prints under another project, such as `project ':lib'` in
|
|
1470
|
+
* the report of `:app`, show that project's dependencies as `:app` resolved
|
|
1471
|
+
* them, after conflict resolution. When `reportedProjects` names that project,
|
|
1472
|
+
* its own report supplies its edges, so these lines add no edge from it.
|
|
1473
|
+
*
|
|
885
1474
|
* @param {string} rawOutput Text output from gradle dependencies task
|
|
886
|
-
* @param {string} rootProjectName
|
|
887
|
-
* @param {map} gradleModules
|
|
888
|
-
* @param {string} gradleRootPath
|
|
1475
|
+
* @param {string} rootProjectName Key in `gradleModules` of the project the report belongs to
|
|
1476
|
+
* @param {map} gradleModules Gradle modules keyed by project path, and the root project by its name
|
|
1477
|
+
* @param {string} gradleRootPath Directory Gradle ran in, recorded as `cdx:gradle:GradleRootPath`
|
|
1478
|
+
* @param {Set<string>} [reportedProjects] Keys in `gradleModules` of the projects whose own reports are parsed
|
|
1479
|
+
* @returns {{pkgList: Object[], dependenciesList: Object[]}|{}} The components and dependency graph
|
|
889
1480
|
*/
|
|
890
1481
|
export async function parseGradleDep(
|
|
891
1482
|
rawOutput,
|
|
892
1483
|
rootProjectName = "root",
|
|
893
1484
|
gradleModules = new Map(),
|
|
894
1485
|
gradleRootPath = "",
|
|
1486
|
+
reportedProjects = new Set(),
|
|
895
1487
|
) {
|
|
896
1488
|
if (typeof rawOutput === "string") {
|
|
897
1489
|
// Bug: 249. Get any sub-projects refered here
|
|
@@ -906,225 +1498,238 @@ export async function parseGradleDep(
|
|
|
906
1498
|
) {
|
|
907
1499
|
rootProjectName = retMap.rootProject;
|
|
908
1500
|
}
|
|
909
|
-
let match = "";
|
|
910
1501
|
// To render dependency tree we need a root project
|
|
911
1502
|
const rootProject = gradleModules.get(rootProjectName);
|
|
1503
|
+
if (!rootProject) {
|
|
1504
|
+
thoughtLog(
|
|
1505
|
+
`The Gradle report for ${rootProjectName} belongs to no module read from 'gradle properties', so it has no parent to hang its dependency tree from.`,
|
|
1506
|
+
);
|
|
1507
|
+
return {};
|
|
1508
|
+
}
|
|
912
1509
|
const deps = [];
|
|
913
1510
|
const dependenciesList = [];
|
|
914
|
-
const
|
|
915
|
-
const
|
|
916
|
-
let last_level = 0;
|
|
917
|
-
let last_bomref = rootProject["bom-ref"];
|
|
918
|
-
const first_bomref = last_bomref;
|
|
919
|
-
let last_project_bomref = first_bomref;
|
|
1511
|
+
const depsByRef = new Map();
|
|
1512
|
+
const firstBomRef = rootProject["bom-ref"];
|
|
920
1513
|
const level_trees = {};
|
|
921
|
-
level_trees[
|
|
1514
|
+
level_trees[firstBomRef] = [];
|
|
1515
|
+
// Modules whose own report supplies their edges.
|
|
1516
|
+
const reportedModuleRefs = new Set();
|
|
1517
|
+
for (const key of reportedProjects) {
|
|
1518
|
+
const reportedRef = gradleModules.get(key)?.["bom-ref"];
|
|
1519
|
+
if (reportedRef && reportedRef !== firstBomRef) {
|
|
1520
|
+
reportedModuleRefs.add(reportedRef);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
922
1523
|
let scope;
|
|
923
1524
|
let profileName;
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
for (const module of modulesToScan) {
|
|
942
|
-
const propMap = parseGradleProperties(
|
|
943
|
-
splitPropTaskOut.get(module),
|
|
944
|
-
module,
|
|
945
|
-
);
|
|
946
|
-
const rootSubProject = propMap.rootProject;
|
|
947
|
-
if (rootSubProject) {
|
|
948
|
-
const rootSubProjectObj = await buildObjectForGradleModule(
|
|
949
|
-
rootSubProject === "root" ? module : rootSubProject,
|
|
950
|
-
propMap.metadata,
|
|
951
|
-
);
|
|
952
|
-
gradleModules.set(module, rootSubProjectObj);
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
const subDependsOn = [];
|
|
957
|
-
for (const sd of retMap.projects) {
|
|
958
|
-
if (gradleModules.has(sd)) {
|
|
959
|
-
subDependsOn.push(gradleModules.get(sd)["bom-ref"]);
|
|
960
|
-
}
|
|
1525
|
+
// ancestors[level] is the bom-ref of the line most recently seen at that
|
|
1526
|
+
// depth; ancestors[0] is the project the report belongs to.
|
|
1527
|
+
const ancestors = [firstBomRef];
|
|
1528
|
+
const lines = rawOutput.split("\n").map((l) => l.replaceAll("\r", ""));
|
|
1529
|
+
// Modules some configuration resolved to a version, so that a failed,
|
|
1530
|
+
// versionless request for one of them adds nothing.
|
|
1531
|
+
const resolvedModules = new Set();
|
|
1532
|
+
for (const line of lines) {
|
|
1533
|
+
const parsed = parseGradleTreeLine(line);
|
|
1534
|
+
if (
|
|
1535
|
+
parsed?.group &&
|
|
1536
|
+
parsed.version !== undefined &&
|
|
1537
|
+
!parsed.failed &&
|
|
1538
|
+
!parsed.constraint &&
|
|
1539
|
+
!parsed.unresolvable
|
|
1540
|
+
) {
|
|
1541
|
+
resolvedModules.add(`${parsed.group}:${parsed.name}`);
|
|
961
1542
|
}
|
|
962
|
-
level_trees[last_bomref] = subDependsOn;
|
|
963
1543
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1544
|
+
for (const rline of lines) {
|
|
1545
|
+
const configurationName = parseGradleConfigurationHeader(rline);
|
|
1546
|
+
if (configurationName) {
|
|
1547
|
+
ancestors.length = 1;
|
|
1548
|
+
profileName = configurationName;
|
|
1549
|
+
scope = gradleConfigurationScope(configurationName);
|
|
1550
|
+
}
|
|
1551
|
+
if (rline.trim() === "") {
|
|
1552
|
+
ancestors.length = 1;
|
|
969
1553
|
continue;
|
|
970
1554
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1555
|
+
const parsed = parseGradleTreeLine(rline);
|
|
1556
|
+
if (!parsed) {
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
// A line that names nothing usable still takes its place in the tree,
|
|
1560
|
+
// so the lines under it get no parent rather than a wrong one.
|
|
1561
|
+
if (parsed.unknown || parsed.unresolvable) {
|
|
1562
|
+
ancestors.length = Math.min(ancestors.length, parsed.level);
|
|
979
1563
|
continue;
|
|
980
1564
|
}
|
|
1565
|
+
const { level } = parsed;
|
|
1566
|
+
const isProject = parsed.projectPath !== undefined;
|
|
1567
|
+
if (parsed.constraint) {
|
|
1568
|
+
ancestors.length = Math.min(ancestors.length, level);
|
|
1569
|
+
continue;
|
|
1570
|
+
}
|
|
1571
|
+
// Only a dependency that failed to resolve can lack a version: Gradle
|
|
1572
|
+
// prints every resolved one with the version it selected.
|
|
981
1573
|
if (
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1574
|
+
!isProject &&
|
|
1575
|
+
parsed.version === undefined &&
|
|
1576
|
+
(!parsed.failed ||
|
|
1577
|
+
resolvedModules.has(`${parsed.group}:${parsed.name}`))
|
|
985
1578
|
) {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
last_bomref = last_project_bomref;
|
|
989
|
-
stack = [first_bomref];
|
|
1579
|
+
ancestors.length = Math.min(ancestors.length, level);
|
|
1580
|
+
continue;
|
|
990
1581
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
1582
|
+
// A project the modules lack is named as Gradle publishes it by
|
|
1583
|
+
// default: the last segment of its path, with the root project's group
|
|
1584
|
+
// and version.
|
|
1585
|
+
const name = isProject
|
|
1586
|
+
? parsed.projectPath.split(":").pop()
|
|
1587
|
+
: parsed.name;
|
|
1588
|
+
const group = isProject ? rootProject.group : parsed.group;
|
|
1589
|
+
const version = isProject ? rootProject.version : parsed.version;
|
|
1590
|
+
const gradleModule = isProject
|
|
1591
|
+
? gradleModuleForPath(
|
|
1592
|
+
gradleModules,
|
|
1593
|
+
parsed.projectPath,
|
|
1594
|
+
rootProjectName,
|
|
1595
|
+
)
|
|
1596
|
+
: undefined;
|
|
1597
|
+
if (isProject && !gradleModule && name === "") {
|
|
1598
|
+
ancestors.length = Math.min(ancestors.length, level);
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
let purl;
|
|
1602
|
+
let bomRef;
|
|
1603
|
+
if (gradleModule) {
|
|
1604
|
+
purl = gradleModule["purl"];
|
|
1605
|
+
bomRef = gradleModule["bom-ref"];
|
|
1606
|
+
} else {
|
|
1607
|
+
try {
|
|
1608
|
+
purl = build({
|
|
1609
|
+
type: "maven",
|
|
1610
|
+
namespace: group || null,
|
|
1611
|
+
name,
|
|
1612
|
+
version: version || null,
|
|
1613
|
+
qualifiers: { type: "jar" },
|
|
1614
|
+
});
|
|
1615
|
+
bomRef = decodeURIComponent(purl);
|
|
1616
|
+
} catch {
|
|
1617
|
+
// A purl that cannot be built keeps a name-only bom-ref so the
|
|
1618
|
+
// component still lands in the graph.
|
|
1619
|
+
purl = undefined;
|
|
1620
|
+
bomRef = name;
|
|
999
1621
|
}
|
|
1000
1622
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
if (version !== undefined || group === "project") {
|
|
1017
|
-
// Project line has no version
|
|
1018
|
-
// For multi sub-module projects such as :module:dummy:starter the regex is producing incorrect values
|
|
1019
|
-
if (rline.includes("project ")) {
|
|
1020
|
-
const tmpA = rline.split("project ");
|
|
1021
|
-
if (tmpA && tmpA.length > 1) {
|
|
1022
|
-
group = rootProject.group;
|
|
1023
|
-
name = tmpA[1].split(" ")[0];
|
|
1024
|
-
version = undefined;
|
|
1025
|
-
}
|
|
1623
|
+
const existing = depsByRef.get(bomRef);
|
|
1624
|
+
// The project the report belongs to is the parent, not a component.
|
|
1625
|
+
if (!existing && bomRef !== firstBomRef) {
|
|
1626
|
+
let adep;
|
|
1627
|
+
if (gradleModule) {
|
|
1628
|
+
adep = gradleModule;
|
|
1629
|
+
} else {
|
|
1630
|
+
adep = {
|
|
1631
|
+
group,
|
|
1632
|
+
name: name,
|
|
1633
|
+
version,
|
|
1634
|
+
qualifiers: { type: "jar" },
|
|
1635
|
+
};
|
|
1636
|
+
if (version === undefined) {
|
|
1637
|
+
delete adep.version;
|
|
1026
1638
|
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
if (
|
|
1030
|
-
|
|
1031
|
-
bomRef = gradleModules.get(name)["bom-ref"];
|
|
1032
|
-
} else {
|
|
1033
|
-
try {
|
|
1034
|
-
purl = build({
|
|
1035
|
-
type: "maven",
|
|
1036
|
-
namespace:
|
|
1037
|
-
group !== "project" ? group : rootProject.group || null,
|
|
1038
|
-
name: name.replace(/^:/, ""),
|
|
1039
|
-
version:
|
|
1040
|
-
version !== undefined ? version : rootProject.version || null,
|
|
1041
|
-
qualifiers: { type: "jar" },
|
|
1042
|
-
});
|
|
1043
|
-
bomRef = decodeURIComponent(purl);
|
|
1044
|
-
} catch {
|
|
1045
|
-
// A purl that cannot be built keeps a name-only bom-ref so the
|
|
1046
|
-
// component still lands in the graph.
|
|
1047
|
-
purl = undefined;
|
|
1048
|
-
bomRef = name.replace(/^:/, "");
|
|
1049
|
-
}
|
|
1639
|
+
adep["purl"] = purl;
|
|
1640
|
+
adep["bom-ref"] = bomRef;
|
|
1641
|
+
if (scope) {
|
|
1642
|
+
adep["scope"] = scope;
|
|
1050
1643
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
adep = gradleModules.get(name);
|
|
1058
|
-
} else {
|
|
1059
|
-
adep = {
|
|
1060
|
-
group: group !== "project" ? group : rootProject.group,
|
|
1061
|
-
name: name,
|
|
1062
|
-
version: version !== undefined ? version : rootProject.version,
|
|
1063
|
-
qualifiers: { type: "jar" },
|
|
1064
|
-
};
|
|
1065
|
-
adep["purl"] = purl;
|
|
1066
|
-
adep["bom-ref"] = bomRef;
|
|
1067
|
-
if (scope) {
|
|
1068
|
-
adep["scope"] = scope;
|
|
1069
|
-
}
|
|
1070
|
-
adep.properties = [];
|
|
1071
|
-
if (profileName) {
|
|
1072
|
-
adep.properties.push({
|
|
1073
|
-
name: "internal:GradleProfileName",
|
|
1074
|
-
value: profileName,
|
|
1075
|
-
});
|
|
1076
|
-
}
|
|
1077
|
-
if (gradleRootPath && gradleRootPath !== ".") {
|
|
1078
|
-
adep.properties.push({
|
|
1079
|
-
name: "cdx:gradle:GradleRootPath",
|
|
1080
|
-
value: gradleRootPath,
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
if (adep?.properties?.length === 0) {
|
|
1085
|
-
delete adep.properties;
|
|
1086
|
-
}
|
|
1087
|
-
deps.push(adep);
|
|
1644
|
+
adep.properties = [];
|
|
1645
|
+
if (profileName) {
|
|
1646
|
+
adep.properties.push({
|
|
1647
|
+
name: "internal:GradleProfileName",
|
|
1648
|
+
value: profileName,
|
|
1649
|
+
});
|
|
1088
1650
|
}
|
|
1089
|
-
if (
|
|
1090
|
-
|
|
1651
|
+
if (gradleRootPath && gradleRootPath !== ".") {
|
|
1652
|
+
adep.properties.push({
|
|
1653
|
+
name: "cdx:gradle:GradleRootPath",
|
|
1654
|
+
value: gradleRootPath,
|
|
1655
|
+
});
|
|
1091
1656
|
}
|
|
1092
|
-
if (
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1657
|
+
if (parsed.failed) {
|
|
1658
|
+
adep.properties.push({
|
|
1659
|
+
name: "cdx:gradle:resolutionFailed",
|
|
1660
|
+
value: "true",
|
|
1661
|
+
});
|
|
1662
|
+
if (
|
|
1663
|
+
parsed.requestedVersion &&
|
|
1664
|
+
parsed.requestedVersion !== version
|
|
1665
|
+
) {
|
|
1666
|
+
adep.properties.push({
|
|
1667
|
+
name: "cdx:gradle:requestedVersion",
|
|
1668
|
+
value: parsed.requestedVersion.slice(0, 256),
|
|
1669
|
+
});
|
|
1105
1670
|
}
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
if (adep?.properties?.length === 0) {
|
|
1674
|
+
delete adep.properties;
|
|
1675
|
+
}
|
|
1676
|
+
depsByRef.set(bomRef, { adep, isModule: !!gradleModule });
|
|
1677
|
+
deps.push(adep);
|
|
1678
|
+
} else if (existing && !existing.isModule) {
|
|
1679
|
+
const { adep } = existing;
|
|
1680
|
+
if (gradleScopeRank(scope) > gradleScopeRank(adep.scope)) {
|
|
1681
|
+
if (scope) {
|
|
1682
|
+
adep.scope = scope;
|
|
1106
1683
|
} else {
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1684
|
+
delete adep.scope;
|
|
1685
|
+
}
|
|
1686
|
+
const profileProp = adep.properties?.find(
|
|
1687
|
+
(p) => p.name === "internal:GradleProfileName",
|
|
1688
|
+
);
|
|
1689
|
+
if (profileProp && profileName) {
|
|
1690
|
+
profileProp.value = profileName;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
// A dependency one configuration failed to resolve and another
|
|
1694
|
+
// resolved is not a failed dependency.
|
|
1695
|
+
if (!parsed.failed && adep.properties) {
|
|
1696
|
+
adep.properties = adep.properties.filter(
|
|
1697
|
+
(p) => !GRADLE_FAILURE_PROPERTIES.includes(p.name),
|
|
1698
|
+
);
|
|
1699
|
+
if (!adep.properties.length) {
|
|
1700
|
+
delete adep.properties;
|
|
1118
1701
|
}
|
|
1119
|
-
last_level = level;
|
|
1120
|
-
last_bomref = bomRef;
|
|
1121
1702
|
}
|
|
1122
1703
|
}
|
|
1704
|
+
if (!level_trees[bomRef]) {
|
|
1705
|
+
level_trees[bomRef] = [];
|
|
1706
|
+
}
|
|
1707
|
+
// A line whose parent was skipped has no known parent and gets no edge.
|
|
1708
|
+
const parentRef =
|
|
1709
|
+
ancestors.length >= level ? ancestors[level - 1] : undefined;
|
|
1710
|
+
if (
|
|
1711
|
+
parentRef &&
|
|
1712
|
+
parentRef !== bomRef &&
|
|
1713
|
+
!reportedModuleRefs.has(parentRef)
|
|
1714
|
+
) {
|
|
1715
|
+
const cnodes = level_trees[parentRef] || [];
|
|
1716
|
+
if (!cnodes.includes(bomRef)) {
|
|
1717
|
+
cnodes.push(bomRef);
|
|
1718
|
+
}
|
|
1719
|
+
level_trees[parentRef] = cnodes;
|
|
1720
|
+
}
|
|
1721
|
+
ancestors.length = Math.min(ancestors.length, level);
|
|
1722
|
+
if (ancestors.length === level) {
|
|
1723
|
+
ancestors.push(bomRef);
|
|
1724
|
+
}
|
|
1123
1725
|
}
|
|
1726
|
+
const renamedRefs = markGradlePlatforms(depsByRef, level_trees);
|
|
1124
1727
|
for (const lk of Object.keys(level_trees)) {
|
|
1125
1728
|
dependenciesList.push({
|
|
1126
|
-
ref: lk,
|
|
1127
|
-
dependsOn: [
|
|
1729
|
+
ref: renamedRefs.get(lk) || lk,
|
|
1730
|
+
dependsOn: [
|
|
1731
|
+
...new Set(level_trees[lk].map((ref) => renamedRefs.get(ref) || ref)),
|
|
1732
|
+
].sort(),
|
|
1128
1733
|
});
|
|
1129
1734
|
}
|
|
1130
1735
|
return {
|
|
@@ -1349,15 +1954,18 @@ export async function collectGradleDependencies(
|
|
|
1349
1954
|
* Method to return the mill command to use.
|
|
1350
1955
|
*
|
|
1351
1956
|
* The project-local `mill` script is an attacker-controlled file in the
|
|
1352
|
-
* scanned project, so it is only used when
|
|
1353
|
-
* (
|
|
1957
|
+
* scanned project, so it is only used when the project's own tooling may run
|
|
1958
|
+
* (see isProjectToolingAllowed).
|
|
1354
1959
|
*
|
|
1355
1960
|
* @param {string} srcPath Path to look for mill wrapper
|
|
1356
1961
|
* @param {Object} [options] CLI options (`installDeps` gates wrapper use)
|
|
1357
1962
|
*/
|
|
1358
1963
|
export function getMillCommand(srcPath, options = {}) {
|
|
1359
1964
|
let millCmd = `mill${platform() === "win32" ? ".bat" : ""}`;
|
|
1360
|
-
if (
|
|
1965
|
+
if (
|
|
1966
|
+
isProjectToolingAllowed(options) &&
|
|
1967
|
+
safeExistsSync(join(srcPath, millCmd))
|
|
1968
|
+
) {
|
|
1361
1969
|
// Use local mill wrapper if available
|
|
1362
1970
|
// Enable execute permission
|
|
1363
1971
|
try {
|
|
@@ -1510,16 +2118,25 @@ function isProcessAlive(pid) {
|
|
|
1510
2118
|
* @param {string} rootPath Scanned tree
|
|
1511
2119
|
* @param {Map<string, Set<number>>} [runningBefore] Result of
|
|
1512
2120
|
* {@link findMillDaemons} taken before the scan
|
|
2121
|
+
* @param {Object} [options] CLI options
|
|
1513
2122
|
*/
|
|
1514
|
-
export function stopMillDaemons(
|
|
2123
|
+
export function stopMillDaemons(
|
|
2124
|
+
rootPath,
|
|
2125
|
+
runningBefore = new Map(),
|
|
2126
|
+
options = {},
|
|
2127
|
+
) {
|
|
2128
|
+
// The daemon PID files live in the scanned tree. When the scan was not
|
|
2129
|
+
// allowed to run Mill it started no daemon, and running the build's own
|
|
2130
|
+
// launcher here would execute a script from the scanned project.
|
|
2131
|
+
if (!isProjectToolingAllowed(options)) {
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
1515
2134
|
for (const [buildRoot, pids] of findMillDaemons(rootPath)) {
|
|
1516
2135
|
const before = runningBefore.get(buildRoot) || new Set();
|
|
1517
2136
|
if ([...pids].every((pid) => before.has(pid))) {
|
|
1518
2137
|
continue;
|
|
1519
2138
|
}
|
|
1520
|
-
const launcher =
|
|
1521
|
-
? join(buildRoot, "mill")
|
|
1522
|
-
: "mill";
|
|
2139
|
+
const launcher = getMillCommand(buildRoot, options);
|
|
1523
2140
|
console.log("Stopping the Mill daemon the scan started in", buildRoot);
|
|
1524
2141
|
safeSpawnSync(launcher, ["shutdown"], {
|
|
1525
2142
|
cwd: buildRoot,
|
|
@@ -1532,8 +2149,8 @@ export function stopMillDaemons(rootPath, runningBefore = new Map()) {
|
|
|
1532
2149
|
* Method to return the maven command to use.
|
|
1533
2150
|
*
|
|
1534
2151
|
* Project-local wrapper scripts (`mvnw`) are attacker-controlled files in the
|
|
1535
|
-
* scanned project, so they are only used when
|
|
1536
|
-
* (
|
|
2152
|
+
* scanned project, so they are only used when the project's own tooling may
|
|
2153
|
+
* run (see isProjectToolingAllowed); otherwise the installed Maven is used.
|
|
1537
2154
|
*
|
|
1538
2155
|
* @param {string} srcPath Path to look for maven wrapper
|
|
1539
2156
|
* @param {string} rootPath Root directory to look for maven wrapper
|
|
@@ -1545,10 +2162,7 @@ export function getMavenCommand(srcPath, rootPath, options = {}) {
|
|
|
1545
2162
|
let isWrapperReady = false;
|
|
1546
2163
|
let isWrapperFound = false;
|
|
1547
2164
|
const pinnedToolSelected = isJvmToolPinned();
|
|
1548
|
-
|
|
1549
|
-
// keeps using wrappers even when dependency installation is disabled.
|
|
1550
|
-
const allowProjectWrappers =
|
|
1551
|
-
options.installDeps !== false || options.introspect === true;
|
|
2165
|
+
const allowProjectWrappers = isProjectToolingAllowed(options);
|
|
1552
2166
|
let findMavenFile = "mvnw";
|
|
1553
2167
|
let mavenWrapperCmd = null;
|
|
1554
2168
|
if (platform() === "win32") {
|