@cdxgen/cdxgen 13.5.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 +6 -6
- package/bin/cdxgen.js +4 -0
- package/bin/evinse.js +3 -0
- package/bin/validate.js +6 -0
- package/data/license-aliases.json +24 -2
- package/data/remediations.json +124 -0
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -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/cli/bomAssembly.js +92 -3
- package/lib/cli/index.js +108 -1
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +237 -46
- package/lib/cli/managedBom.js +142 -16
- package/lib/cli/nativeBom.js +42 -5
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +8 -0
- package/lib/core/fs.js +12 -0
- package/lib/core/httpClient.js +98 -1
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -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/gradleutils.js +828 -224
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-misc.js +209 -20
- package/lib/ecosystems/parsers-python.js +89 -2
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +538 -31
- package/lib/ecosystems/scalacliutils.js +2 -0
- package/lib/ecosystems/scalasem.js +796 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +346 -43
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/atomUtils.js +77 -0
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/deps.js +35 -0
- package/lib/inventory/dosai.js +139 -5
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/envcontext.js +307 -13
- package/lib/inventory/evidenceUtils.js +11 -0
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +31 -3
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -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/osPackageResolver.js +182 -0
- package/lib/inventory/phpBuildInfo.js +10 -59
- package/lib/inventory/phpHostProbe.js +32 -488
- 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 +49 -2
- package/lib/managers/binary.js +337 -3
- package/lib/managers/docker.js +79 -0
- package/lib/parsers/largeJson.js +28 -11
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +12 -1
- package/lib/stages/postgen/ruleEngine.js +13 -5
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +114 -57
- package/lib/validator/bomValidator.js +6 -1
- package/lib/validator/generated/validate-1.6.mjs +1 -1
- package/lib/validator/generated/validate-1.7.mjs +1 -1
- package/lib/validator/generated/validate-2.0.mjs +1 -1
- package/lib/validator/index.js +1 -1
- package/package.json +3 -2
- package/types/lib/cli/bomAssembly.d.ts +13 -1
- package/types/lib/cli/bomAssembly.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.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 +4 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +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/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/gradleutils.d.ts +47 -13
- 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-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +19 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +10 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +20 -2
- 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/scalacliutils.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 +63 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +19 -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/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +17 -1
- 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/envcontext.d.ts +52 -6
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/gtfobins.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/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/osPackageResolver.d.ts +38 -0
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -1
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -1
- 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 +10 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +12 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/largeJson.d.ts +4 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +12 -8
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "../inventory/buildToolRateLimit.js";
|
|
32
32
|
import { collectJarNS } from "../inventory/deps.js";
|
|
33
33
|
import { minimumGradleVersionForJava } from "../inventory/jvmToolEnv.js";
|
|
34
|
+
import { gradleMirrorInitScriptArgs } from "../inventory/mavenCentralMirror.js";
|
|
34
35
|
import { applyPurl, tryBuildPurl } from "../inventory/purl.js";
|
|
35
36
|
import { parsePkgJson } from "./parsers-js.js";
|
|
36
37
|
|
|
@@ -218,6 +219,50 @@ function gradleEvaluationOverrides(gradleArguments) {
|
|
|
218
219
|
return overrides;
|
|
219
220
|
}
|
|
220
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
|
+
|
|
221
266
|
/**
|
|
222
267
|
* Method to combine the general gradle arguments, the sub-commands and the sub-commands' arguments in the correct way
|
|
223
268
|
*
|
|
@@ -242,6 +287,8 @@ export function buildGradleCommandArguments(
|
|
|
242
287
|
]
|
|
243
288
|
.concat(getGradleDaemonParameter())
|
|
244
289
|
.concat(gradleEvaluationOverrides(gradleArguments))
|
|
290
|
+
.concat(gradleLogLevelOverride(gradleArguments))
|
|
291
|
+
.concat(gradleMirrorInitScriptArgs())
|
|
245
292
|
.concat(gradleArguments);
|
|
246
293
|
const maxCliArgsLength = isWin
|
|
247
294
|
? 7500 - gradleCommandLength - mainGradleArguments.join(" ").length - 2
|
|
@@ -257,7 +304,13 @@ export function buildGradleCommandArguments(
|
|
|
257
304
|
for (const gradleSubCommand of gradleSubCommands) {
|
|
258
305
|
const subCommandLength =
|
|
259
306
|
[gradleSubCommand, ...gradleSubCommandArguments].join(" ").length + 1;
|
|
260
|
-
|
|
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
|
+
) {
|
|
261
314
|
splitArgs.push(allGradleArguments);
|
|
262
315
|
allGradleArguments = [].concat(mainGradleArguments);
|
|
263
316
|
remainingLength = maxCliArgsLength;
|
|
@@ -297,16 +350,52 @@ function parseGradleTaskHeader(line) {
|
|
|
297
350
|
if (!line.startsWith("> Task :")) {
|
|
298
351
|
return undefined;
|
|
299
352
|
}
|
|
300
|
-
//
|
|
301
|
-
// follow
|
|
302
|
-
|
|
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();
|
|
303
358
|
const lastColon = taskPath.lastIndexOf(":");
|
|
304
359
|
return {
|
|
305
360
|
projectPath: taskPath.slice(0, lastColon),
|
|
306
|
-
taskName: taskPath.slice(lastColon + 1),
|
|
361
|
+
taskName: taskPath.slice(lastColon + 1).split(/\s+/)[0],
|
|
307
362
|
};
|
|
308
363
|
}
|
|
309
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
|
+
|
|
310
399
|
/**
|
|
311
400
|
* Method to split the output produced by Gradle using parallel processing by project
|
|
312
401
|
*
|
|
@@ -398,27 +487,19 @@ export function parseGradleProjects(rawOutput) {
|
|
|
398
487
|
}
|
|
399
488
|
}
|
|
400
489
|
}
|
|
401
|
-
} else
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const tmpName = projName.split(" ")[0];
|
|
407
|
-
if (tmpName.length > 1) {
|
|
408
|
-
projects.add(tmpName);
|
|
409
|
-
}
|
|
490
|
+
} else {
|
|
491
|
+
for (const marker of ["--- project ", "-> project "]) {
|
|
492
|
+
const markerIndex = l.indexOf(marker);
|
|
493
|
+
if (markerIndex === -1) {
|
|
494
|
+
continue;
|
|
410
495
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (projName.startsWith(":")) {
|
|
417
|
-
const tmpName = projName.split(" ")[0];
|
|
418
|
-
if (tmpName.length > 1) {
|
|
419
|
-
projects.add(tmpName);
|
|
420
|
-
}
|
|
496
|
+
const projName = gradleProjectPath(
|
|
497
|
+
l.slice(markerIndex + marker.length),
|
|
498
|
+
);
|
|
499
|
+
if (projName?.startsWith(":") && projName.length > 1) {
|
|
500
|
+
projects.add(projName);
|
|
421
501
|
}
|
|
502
|
+
break;
|
|
422
503
|
}
|
|
423
504
|
}
|
|
424
505
|
});
|
|
@@ -432,11 +513,17 @@ export function parseGradleProjects(rawOutput) {
|
|
|
432
513
|
/**
|
|
433
514
|
* Parse gradle properties output
|
|
434
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
|
+
*
|
|
435
520
|
* @param {string} rawOutput Raw string output
|
|
436
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
|
|
437
523
|
*/
|
|
438
524
|
export function parseGradleProperties(rawOutput, gradleModuleName = null) {
|
|
439
525
|
let rootProject = "root";
|
|
526
|
+
let gradlePlugin = false;
|
|
440
527
|
const projects = new Set();
|
|
441
528
|
const metadata = { group: "", version: "latest", properties: [] };
|
|
442
529
|
if (gradleModuleName) {
|
|
@@ -472,6 +559,8 @@ export function parseGradleProperties(rawOutput, gradleModuleName = null) {
|
|
|
472
559
|
metadata[tmpB[0]] = tmpB[1].trim().replace("unspecified", "latest");
|
|
473
560
|
} else if (["buildFile", "projectDir", "rootDir"].includes(tmpB[0])) {
|
|
474
561
|
metadata.properties.push({ name: tmpB[0], value: tmpB[1].trim() });
|
|
562
|
+
} else if (tmpB[0] === "gradlePlugin") {
|
|
563
|
+
gradlePlugin = true;
|
|
475
564
|
} else if (tmpB[0] === "subprojects") {
|
|
476
565
|
const spStrs = tmpB[1].replace(/[[\]']/g, "").split(", ");
|
|
477
566
|
const tmpprojects = spStrs
|
|
@@ -486,6 +575,7 @@ export function parseGradleProperties(rawOutput, gradleModuleName = null) {
|
|
|
486
575
|
rootProject,
|
|
487
576
|
projects: Array.from(projects),
|
|
488
577
|
metadata,
|
|
578
|
+
gradlePlugin,
|
|
489
579
|
};
|
|
490
580
|
}
|
|
491
581
|
|
|
@@ -511,17 +601,11 @@ export function executeParallelGradleProperties(
|
|
|
511
601
|
}
|
|
512
602
|
const gradleCmd = getGradleCommand(dir, null, options);
|
|
513
603
|
const gradleArgs = buildGradleCommandArguments(
|
|
514
|
-
extraArgs.concat(
|
|
515
|
-
readEnvironmentVariable("GRADLE_ARGS")
|
|
516
|
-
? readEnvironmentVariable("GRADLE_ARGS").split(" ")
|
|
517
|
-
: [],
|
|
518
|
-
),
|
|
604
|
+
extraArgs.concat(gradleArgsFromEnv("GRADLE_ARGS")),
|
|
519
605
|
allProjectsStr.map((project) =>
|
|
520
606
|
project ? `${project}:properties` : "properties",
|
|
521
607
|
),
|
|
522
|
-
|
|
523
|
-
? readEnvironmentVariable("GRADLE_ARGS_PROPERTIES").split(" ")
|
|
524
|
-
: [],
|
|
608
|
+
gradleArgsFromEnv("GRADLE_ARGS_PROPERTIES"),
|
|
525
609
|
gradleCmd.length,
|
|
526
610
|
);
|
|
527
611
|
const allOutputs = [];
|
|
@@ -880,19 +964,526 @@ export function recordGradleInvocationFailure(result, context = {}) {
|
|
|
880
964
|
});
|
|
881
965
|
}
|
|
882
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
|
+
|
|
883
1450
|
/**
|
|
884
1451
|
* Method to resolve dependencies from a gradle output
|
|
885
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
|
+
*
|
|
886
1474
|
* @param {string} rawOutput Text output from gradle dependencies task
|
|
887
|
-
* @param {string} rootProjectName
|
|
888
|
-
* @param {map} gradleModules
|
|
889
|
-
* @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
|
|
890
1480
|
*/
|
|
891
1481
|
export async function parseGradleDep(
|
|
892
1482
|
rawOutput,
|
|
893
1483
|
rootProjectName = "root",
|
|
894
1484
|
gradleModules = new Map(),
|
|
895
1485
|
gradleRootPath = "",
|
|
1486
|
+
reportedProjects = new Set(),
|
|
896
1487
|
) {
|
|
897
1488
|
if (typeof rawOutput === "string") {
|
|
898
1489
|
// Bug: 249. Get any sub-projects refered here
|
|
@@ -907,225 +1498,238 @@ export async function parseGradleDep(
|
|
|
907
1498
|
) {
|
|
908
1499
|
rootProjectName = retMap.rootProject;
|
|
909
1500
|
}
|
|
910
|
-
let match = "";
|
|
911
1501
|
// To render dependency tree we need a root project
|
|
912
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
|
+
}
|
|
913
1509
|
const deps = [];
|
|
914
1510
|
const dependenciesList = [];
|
|
915
|
-
const
|
|
916
|
-
const
|
|
917
|
-
let last_level = 0;
|
|
918
|
-
let last_bomref = rootProject["bom-ref"];
|
|
919
|
-
const first_bomref = last_bomref;
|
|
920
|
-
let last_project_bomref = first_bomref;
|
|
1511
|
+
const depsByRef = new Map();
|
|
1512
|
+
const firstBomRef = rootProject["bom-ref"];
|
|
921
1513
|
const level_trees = {};
|
|
922
|
-
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
|
+
}
|
|
923
1523
|
let scope;
|
|
924
1524
|
let profileName;
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
for (const module of modulesToScan) {
|
|
943
|
-
const propMap = parseGradleProperties(
|
|
944
|
-
splitPropTaskOut.get(module),
|
|
945
|
-
module,
|
|
946
|
-
);
|
|
947
|
-
const rootSubProject = propMap.rootProject;
|
|
948
|
-
if (rootSubProject) {
|
|
949
|
-
const rootSubProjectObj = await buildObjectForGradleModule(
|
|
950
|
-
rootSubProject === "root" ? module : rootSubProject,
|
|
951
|
-
propMap.metadata,
|
|
952
|
-
);
|
|
953
|
-
gradleModules.set(module, rootSubProjectObj);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
const subDependsOn = [];
|
|
958
|
-
for (const sd of retMap.projects) {
|
|
959
|
-
if (gradleModules.has(sd)) {
|
|
960
|
-
subDependsOn.push(gradleModules.get(sd)["bom-ref"]);
|
|
961
|
-
}
|
|
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}`);
|
|
962
1542
|
}
|
|
963
|
-
level_trees[last_bomref] = subDependsOn;
|
|
964
1543
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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;
|
|
970
1553
|
continue;
|
|
971
1554
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
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);
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
const { level } = parsed;
|
|
1566
|
+
const isProject = parsed.projectPath !== undefined;
|
|
1567
|
+
if (parsed.constraint) {
|
|
1568
|
+
ancestors.length = Math.min(ancestors.length, level);
|
|
980
1569
|
continue;
|
|
981
1570
|
}
|
|
1571
|
+
// Only a dependency that failed to resolve can lack a version: Gradle
|
|
1572
|
+
// prints every resolved one with the version it selected.
|
|
982
1573
|
if (
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
1574
|
+
!isProject &&
|
|
1575
|
+
parsed.version === undefined &&
|
|
1576
|
+
(!parsed.failed ||
|
|
1577
|
+
resolvedModules.has(`${parsed.group}:${parsed.name}`))
|
|
986
1578
|
) {
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
last_bomref = last_project_bomref;
|
|
990
|
-
stack = [first_bomref];
|
|
1579
|
+
ancestors.length = Math.min(ancestors.length, level);
|
|
1580
|
+
continue;
|
|
991
1581
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
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;
|
|
1000
1621
|
}
|
|
1001
1622
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
if (version !== undefined || group === "project") {
|
|
1018
|
-
// Project line has no version
|
|
1019
|
-
// For multi sub-module projects such as :module:dummy:starter the regex is producing incorrect values
|
|
1020
|
-
if (rline.includes("project ")) {
|
|
1021
|
-
const tmpA = rline.split("project ");
|
|
1022
|
-
if (tmpA && tmpA.length > 1) {
|
|
1023
|
-
group = rootProject.group;
|
|
1024
|
-
name = tmpA[1].split(" ")[0];
|
|
1025
|
-
version = undefined;
|
|
1026
|
-
}
|
|
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;
|
|
1027
1638
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
if (
|
|
1031
|
-
|
|
1032
|
-
bomRef = gradleModules.get(name)["bom-ref"];
|
|
1033
|
-
} else {
|
|
1034
|
-
try {
|
|
1035
|
-
purl = build({
|
|
1036
|
-
type: "maven",
|
|
1037
|
-
namespace:
|
|
1038
|
-
group !== "project" ? group : rootProject.group || null,
|
|
1039
|
-
name: name.replace(/^:/, ""),
|
|
1040
|
-
version:
|
|
1041
|
-
version !== undefined ? version : rootProject.version || null,
|
|
1042
|
-
qualifiers: { type: "jar" },
|
|
1043
|
-
});
|
|
1044
|
-
bomRef = decodeURIComponent(purl);
|
|
1045
|
-
} catch {
|
|
1046
|
-
// A purl that cannot be built keeps a name-only bom-ref so the
|
|
1047
|
-
// component still lands in the graph.
|
|
1048
|
-
purl = undefined;
|
|
1049
|
-
bomRef = name.replace(/^:/, "");
|
|
1050
|
-
}
|
|
1639
|
+
adep["purl"] = purl;
|
|
1640
|
+
adep["bom-ref"] = bomRef;
|
|
1641
|
+
if (scope) {
|
|
1642
|
+
adep["scope"] = scope;
|
|
1051
1643
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
adep = gradleModules.get(name);
|
|
1059
|
-
} else {
|
|
1060
|
-
adep = {
|
|
1061
|
-
group: group !== "project" ? group : rootProject.group,
|
|
1062
|
-
name: name,
|
|
1063
|
-
version: version !== undefined ? version : rootProject.version,
|
|
1064
|
-
qualifiers: { type: "jar" },
|
|
1065
|
-
};
|
|
1066
|
-
adep["purl"] = purl;
|
|
1067
|
-
adep["bom-ref"] = bomRef;
|
|
1068
|
-
if (scope) {
|
|
1069
|
-
adep["scope"] = scope;
|
|
1070
|
-
}
|
|
1071
|
-
adep.properties = [];
|
|
1072
|
-
if (profileName) {
|
|
1073
|
-
adep.properties.push({
|
|
1074
|
-
name: "internal:GradleProfileName",
|
|
1075
|
-
value: profileName,
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
if (gradleRootPath && gradleRootPath !== ".") {
|
|
1079
|
-
adep.properties.push({
|
|
1080
|
-
name: "cdx:gradle:GradleRootPath",
|
|
1081
|
-
value: gradleRootPath,
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
if (adep?.properties?.length === 0) {
|
|
1086
|
-
delete adep.properties;
|
|
1087
|
-
}
|
|
1088
|
-
deps.push(adep);
|
|
1644
|
+
adep.properties = [];
|
|
1645
|
+
if (profileName) {
|
|
1646
|
+
adep.properties.push({
|
|
1647
|
+
name: "internal:GradleProfileName",
|
|
1648
|
+
value: profileName,
|
|
1649
|
+
});
|
|
1089
1650
|
}
|
|
1090
|
-
if (
|
|
1091
|
-
|
|
1651
|
+
if (gradleRootPath && gradleRootPath !== ".") {
|
|
1652
|
+
adep.properties.push({
|
|
1653
|
+
name: "cdx:gradle:GradleRootPath",
|
|
1654
|
+
value: gradleRootPath,
|
|
1655
|
+
});
|
|
1092
1656
|
}
|
|
1093
|
-
if (
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
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
|
+
});
|
|
1106
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;
|
|
1107
1683
|
} else {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
cnodes.push(bomRef);
|
|
1116
|
-
}
|
|
1117
|
-
level_trees[last_stack] = cnodes;
|
|
1118
|
-
stack.push(bomRef);
|
|
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;
|
|
1119
1691
|
}
|
|
1120
|
-
last_level = level;
|
|
1121
|
-
last_bomref = bomRef;
|
|
1122
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;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
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);
|
|
1123
1724
|
}
|
|
1124
1725
|
}
|
|
1726
|
+
const renamedRefs = markGradlePlatforms(depsByRef, level_trees);
|
|
1125
1727
|
for (const lk of Object.keys(level_trees)) {
|
|
1126
1728
|
dependenciesList.push({
|
|
1127
|
-
ref: lk,
|
|
1128
|
-
dependsOn: [
|
|
1729
|
+
ref: renamedRefs.get(lk) || lk,
|
|
1730
|
+
dependsOn: [
|
|
1731
|
+
...new Set(level_trees[lk].map((ref) => renamedRefs.get(ref) || ref)),
|
|
1732
|
+
].sort(),
|
|
1129
1733
|
});
|
|
1130
1734
|
}
|
|
1131
1735
|
return {
|