@cdxgen/cdxgen 13.1.0 → 13.3.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 +32 -15
- package/bin/cdxgen.js +243 -122
- package/bin/evinse.js +7 -0
- package/bin/sign.js +88 -14
- package/bin/tracebom.js +64 -4
- package/bin/validate.js +29 -7
- package/bin/verify.js +79 -39
- package/data/crypto-oid.json +88 -12
- package/data/remediations.json +47 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +117 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +235 -5
- package/lib/cli/jsBom.js +108 -26
- package/lib/cli/jvmBom.js +68 -12
- package/lib/cli/managedBom.js +264 -41
- package/lib/cli/nativeBom.js +923 -15
- package/lib/core/activity.js +120 -2
- package/lib/core/env.js +44 -7
- package/lib/core/fs.js +174 -6
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/bunutils.js +269 -101
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +447 -7
- package/lib/ecosystems/cmakeResolver.js +66 -0
- package/lib/ecosystems/cppEvidence.js +104 -33
- package/lib/ecosystems/ecosystems.js +260 -99
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +185 -9
- package/lib/ecosystems/jsEvidence.js +19 -20
- package/lib/ecosystems/kosi.js +1263 -0
- package/lib/ecosystems/npmutils.js +110 -9
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +120 -49
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-go.js +47 -9
- package/lib/ecosystems/parsers-js.js +171 -50
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +919 -161
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +27 -15
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +954 -33
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/rubyutils.js +1426 -166
- package/lib/ecosystems/rusi.js +158 -7
- package/lib/evinser/evinser.js +547 -129
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/helpers/bomSigner.js +652 -221
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/atomUtils.js +568 -31
- package/lib/inventory/auditCategories.js +8 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +110 -14
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +627 -13
- package/lib/inventory/dynamic.js +39 -1
- package/lib/inventory/envcontext.js +9 -6
- package/lib/inventory/evidenceUtils.js +26 -5
- package/lib/inventory/fetchBatch.js +190 -19
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +6 -5
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +62 -89
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/source.js +47 -23
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/inventory/traceRunner.js +210 -21
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +440 -20
- package/lib/managers/oci.js +39 -1
- package/lib/parsers/cmakeLists.js +452 -0
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/stages/postgen/introspection/markers.js +29 -6
- package/lib/stages/postgen/postgen.js +237 -16
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +48 -10
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +1 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +27 -27
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +2 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- 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 +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +21 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +2 -2
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +40 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +86 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/npmutils.d.ts +42 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-go.d.ts +10 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +10 -4
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +90 -3
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +143 -2
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +81 -2
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/helpers/bomSigner.d.ts +106 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +145 -3
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +4 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +87 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +34 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/inventory/traceRunner.d.ts +47 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +14 -0
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +76 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +33 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
package/lib/cli/nativeBom.js
CHANGED
|
@@ -12,11 +12,13 @@ import { recordDegradation } from "../core/buildLedger.js";
|
|
|
12
12
|
import { deferFailOnError } from "../core/deferredExit.js";
|
|
13
13
|
import {
|
|
14
14
|
CARGO_CMD,
|
|
15
|
+
CARGO_TARGET,
|
|
15
16
|
CLJ_CMD,
|
|
16
17
|
hasAnyProjectType,
|
|
17
18
|
LEIN_CMD,
|
|
18
19
|
SWIFT_CMD,
|
|
19
20
|
shouldFetchLicense,
|
|
21
|
+
shouldFetchPackageMetadata,
|
|
20
22
|
} from "../core/env.js";
|
|
21
23
|
import {
|
|
22
24
|
checksumFile,
|
|
@@ -29,15 +31,26 @@ import { dirNameStr, isMac } from "../core/paths.js";
|
|
|
29
31
|
import {
|
|
30
32
|
buildDependentPurl,
|
|
31
33
|
collapseCmakeVersions,
|
|
34
|
+
preferFetchedCmakeDependencies,
|
|
32
35
|
resolveCmakeContext,
|
|
36
|
+
withoutScrapedFetchDependency,
|
|
33
37
|
} from "../ecosystems/cmakeResolver.js";
|
|
34
38
|
import { getCppModules } from "../ecosystems/cppEvidence.js";
|
|
35
39
|
import {
|
|
36
40
|
getCratesMetadata,
|
|
37
41
|
getSwiftPackageMetadata,
|
|
38
42
|
} from "../ecosystems/ecosystems.js";
|
|
43
|
+
import { parseShardsProject } from "../ecosystems/parsers-crystal.js";
|
|
44
|
+
import {
|
|
45
|
+
createElmCacheReader,
|
|
46
|
+
getElmMetadata,
|
|
47
|
+
isElmProjectFile,
|
|
48
|
+
parseElmProject,
|
|
49
|
+
parseLegacyElmProject,
|
|
50
|
+
} from "../ecosystems/parsers-elm.js";
|
|
39
51
|
import { parseGleamProject } from "../ecosystems/parsers-gleam.js";
|
|
40
52
|
import {
|
|
53
|
+
isGoModWhyNotNeeded,
|
|
41
54
|
parseGoListDep,
|
|
42
55
|
parseGoModData,
|
|
43
56
|
parseGoModGraph,
|
|
@@ -46,14 +59,20 @@ import {
|
|
|
46
59
|
parseGopkgData,
|
|
47
60
|
parseGosumData,
|
|
48
61
|
} from "../ecosystems/parsers-go.js";
|
|
62
|
+
import { parseJuliaProject } from "../ecosystems/parsers-julia.js";
|
|
49
63
|
import {
|
|
50
64
|
parseCljDep,
|
|
51
65
|
parseEdnData,
|
|
52
66
|
parseLeinDep,
|
|
53
67
|
parseLeiningenData,
|
|
54
68
|
} from "../ecosystems/parsers-jvm.js";
|
|
69
|
+
import {
|
|
70
|
+
parseLuaRocksLock,
|
|
71
|
+
parseRockspecFile,
|
|
72
|
+
} from "../ecosystems/parsers-lua.js";
|
|
55
73
|
import {
|
|
56
74
|
buildObjectForCocoaPod,
|
|
75
|
+
classifySwiftDependencyScopes,
|
|
57
76
|
executeAlpmList,
|
|
58
77
|
executeApkList,
|
|
59
78
|
executeDpkgList,
|
|
@@ -78,12 +97,24 @@ import {
|
|
|
78
97
|
parseSwiftResolved,
|
|
79
98
|
prefetchCocoaPodspecs,
|
|
80
99
|
} from "../ecosystems/parsers-misc.js";
|
|
100
|
+
import { parseNimProject } from "../ecosystems/parsers-nim.js";
|
|
101
|
+
import { parseOpamLockedFile } from "../ecosystems/parsers-opam.js";
|
|
102
|
+
import { parseRebarLock } from "../ecosystems/parsers-rebar.js";
|
|
81
103
|
import {
|
|
104
|
+
applyCargoDependencyKindMetadata,
|
|
105
|
+
buildCargoLockDependencyGraph,
|
|
106
|
+
buildCargoNativeLibraryComponents,
|
|
107
|
+
cargoComponentKey,
|
|
82
108
|
parseCargoData,
|
|
83
109
|
parseCargoDependencyData,
|
|
84
110
|
parseCargoManifestDependencyData,
|
|
111
|
+
parseCargoManifestDependencyKinds,
|
|
112
|
+
parseCargoMetadataResolve,
|
|
85
113
|
parseCargoTomlData,
|
|
114
|
+
rollupCargoDependencyKinds,
|
|
86
115
|
} from "../ecosystems/parsers-rust.js";
|
|
116
|
+
import { parseSpackLock } from "../ecosystems/parsers-spack.js";
|
|
117
|
+
import { parseStackProject } from "../ecosystems/parsers-stack.js";
|
|
87
118
|
import { resolveZigGraph } from "../ecosystems/zigResolver.js";
|
|
88
119
|
import { mergeDependencies, trimComponents } from "../inventory/depsUtils.js";
|
|
89
120
|
import { convertOSQueryResults } from "../inventory/evidenceUtils.js";
|
|
@@ -169,7 +200,6 @@ export function getCargoCacheDir() {
|
|
|
169
200
|
export async function createGoBom(path, options) {
|
|
170
201
|
let pkgList = [];
|
|
171
202
|
let dependencies = [];
|
|
172
|
-
const allImports = {};
|
|
173
203
|
let parentComponent = createDefaultParentComponent(path, "golang", options);
|
|
174
204
|
// Is this a binary file
|
|
175
205
|
let maybeBinary;
|
|
@@ -209,6 +239,8 @@ export async function createGoBom(path, options) {
|
|
|
209
239
|
}
|
|
210
240
|
}
|
|
211
241
|
const doneList = {};
|
|
242
|
+
const requiredModules = new Set();
|
|
243
|
+
const unneededModules = new Set();
|
|
212
244
|
let circuitBreak = false;
|
|
213
245
|
if (DEBUG_MODE) {
|
|
214
246
|
console.log(
|
|
@@ -222,12 +254,12 @@ export async function createGoBom(path, options) {
|
|
|
222
254
|
}
|
|
223
255
|
const pkgFullName = `${apkg.name}`;
|
|
224
256
|
if (apkg.scope === "required") {
|
|
225
|
-
|
|
257
|
+
requiredModules.add(pkgFullName);
|
|
226
258
|
continue;
|
|
227
259
|
}
|
|
228
260
|
if (
|
|
229
261
|
apkg.scope === "optional" ||
|
|
230
|
-
|
|
262
|
+
requiredModules.has(pkgFullName) ||
|
|
231
263
|
doneList[pkgFullName]
|
|
232
264
|
) {
|
|
233
265
|
continue;
|
|
@@ -256,19 +288,40 @@ export async function createGoBom(path, options) {
|
|
|
256
288
|
const mstdout = mresult.stdout;
|
|
257
289
|
if (mstdout) {
|
|
258
290
|
const cmdOutput = Buffer.from(mstdout).toString();
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
291
|
+
if (isGoModWhyNotNeeded(cmdOutput)) {
|
|
292
|
+
unneededModules.add(pkgFullName);
|
|
293
|
+
} else {
|
|
294
|
+
// The last line of the import chain is a package of the module:
|
|
295
|
+
// github.com/golang/protobuf/proto for github.com/golang/protobuf
|
|
296
|
+
const whyPkg = parseGoModWhy(cmdOutput);
|
|
297
|
+
if (
|
|
298
|
+
whyPkg === pkgFullName ||
|
|
299
|
+
whyPkg?.startsWith(`${pkgFullName}/`)
|
|
300
|
+
) {
|
|
301
|
+
requiredModules.add(pkgFullName);
|
|
302
|
+
}
|
|
265
303
|
}
|
|
266
304
|
doneList[pkgFullName] = true;
|
|
267
305
|
}
|
|
268
306
|
}
|
|
269
307
|
}
|
|
308
|
+
// A module go reports as unneeded is optional, but only when every module
|
|
309
|
+
// was checked: once go mod why fails, the answers are incomplete, and a
|
|
310
|
+
// tool failure is not evidence that a module is unused.
|
|
311
|
+
for (const apkg of pkgList) {
|
|
312
|
+
if (apkg.scope) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (requiredModules.has(apkg.name)) {
|
|
316
|
+
apkg.scope = "required";
|
|
317
|
+
} else if (!circuitBreak && unneededModules.has(apkg.name)) {
|
|
318
|
+
apkg.scope = "optional";
|
|
319
|
+
}
|
|
320
|
+
}
|
|
270
321
|
if (DEBUG_MODE) {
|
|
271
|
-
console.log(
|
|
322
|
+
console.log(
|
|
323
|
+
`Required packages: ${requiredModules.size}, not needed: ${circuitBreak ? "unknown" : unneededModules.size}`,
|
|
324
|
+
);
|
|
272
325
|
}
|
|
273
326
|
return buildBomNSData(options, pkgList, "golang", {
|
|
274
327
|
src: path,
|
|
@@ -533,7 +586,6 @@ export async function createGoBom(path, options) {
|
|
|
533
586
|
}
|
|
534
587
|
if (pkgList.length && !shouldManuallyParse) {
|
|
535
588
|
return buildBomNSData(options, pkgList, "golang", {
|
|
536
|
-
allImports,
|
|
537
589
|
dependencies,
|
|
538
590
|
parentComponent,
|
|
539
591
|
src: path,
|
|
@@ -664,6 +716,223 @@ function cargoAnswersVersion(cwd) {
|
|
|
664
716
|
* @param {Object} options Parse options from the cli
|
|
665
717
|
* @returns {Promise<Object|undefined>} Promise resolving to a BOM object or undefined
|
|
666
718
|
*/
|
|
719
|
+
/**
|
|
720
|
+
* Run `cargo metadata` for a manifest and return its parsed resolve graph.
|
|
721
|
+
*
|
|
722
|
+
* The lock file is the union of every dependency kind and every target triple,
|
|
723
|
+
* so it cannot answer which crates are dev-only or platform-gated. Only
|
|
724
|
+
* `cargo metadata` carries that, per dependency edge, in `resolve.nodes[].deps[].dep_kinds`.
|
|
725
|
+
*
|
|
726
|
+
* @param {string} cargoTomlFile Manifest path
|
|
727
|
+
* @param {string} [filterPlatform] Target triple to resolve the graph for
|
|
728
|
+
* @returns {object|undefined} Parsed `cargo metadata` output
|
|
729
|
+
*/
|
|
730
|
+
function runCargoMetadata(cargoTomlFile, filterPlatform) {
|
|
731
|
+
const basePath = dirname(cargoTomlFile);
|
|
732
|
+
const platformArgs = filterPlatform
|
|
733
|
+
? ["--filter-platform", filterPlatform]
|
|
734
|
+
: [];
|
|
735
|
+
// `--locked` keeps the user's lock file untouched; a workspace that is out of
|
|
736
|
+
// date with its lock file needs the retry without it.
|
|
737
|
+
for (const lockArgs of [["--locked"], []]) {
|
|
738
|
+
const cargoArgs = [
|
|
739
|
+
"metadata",
|
|
740
|
+
"--format-version",
|
|
741
|
+
"1",
|
|
742
|
+
"--manifest-path",
|
|
743
|
+
cargoTomlFile,
|
|
744
|
+
...platformArgs,
|
|
745
|
+
...lockArgs,
|
|
746
|
+
];
|
|
747
|
+
if (DEBUG_MODE) {
|
|
748
|
+
console.log("Executing", CARGO_CMD, cargoArgs.join(" "), "in", basePath);
|
|
749
|
+
}
|
|
750
|
+
const result = safeSpawnSync(CARGO_CMD, cargoArgs, {
|
|
751
|
+
cwd: basePath,
|
|
752
|
+
shell: isWin,
|
|
753
|
+
});
|
|
754
|
+
if (result.status === 0 && !result.error && result.stdout) {
|
|
755
|
+
try {
|
|
756
|
+
return JSON.parse(result.stdout);
|
|
757
|
+
} catch (err) {
|
|
758
|
+
if (DEBUG_MODE) {
|
|
759
|
+
console.log(`Unable to parse the cargo metadata output: ${err}`);
|
|
760
|
+
}
|
|
761
|
+
return undefined;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (DEBUG_MODE) {
|
|
765
|
+
console.log(result.error, result.stderr);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return undefined;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Annotate cargo components with their dependency kind, platform gate and
|
|
773
|
+
* scope.
|
|
774
|
+
*
|
|
775
|
+
* With `--deep`, the edge kinds come from `cargo metadata` and are exact at
|
|
776
|
+
* every depth. Otherwise they are recovered from the manifests, which is exact
|
|
777
|
+
* for the dependencies a workspace member declares and inherited below them.
|
|
778
|
+
*
|
|
779
|
+
* @param {object[]} pkgList Components to annotate
|
|
780
|
+
* @param {object[]} dependencyTree Lockfile dependency relationships
|
|
781
|
+
* @param {string[]} cargoFiles Manifest paths
|
|
782
|
+
* @param {Map<string, string>} manifestRootKeys Manifest path to root component key
|
|
783
|
+
* @param {object} options CLI options
|
|
784
|
+
*/
|
|
785
|
+
function annotateCargoDependencyKinds(
|
|
786
|
+
pkgList,
|
|
787
|
+
dependencyTree,
|
|
788
|
+
cargoFiles,
|
|
789
|
+
manifestRootKeys,
|
|
790
|
+
options,
|
|
791
|
+
) {
|
|
792
|
+
if (!pkgList?.length) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const allEdges = new Map();
|
|
796
|
+
if (options?.deep) {
|
|
797
|
+
const rollups = new Map();
|
|
798
|
+
const packageInfo = new Map();
|
|
799
|
+
const visitedWorkspaceRoots = new Set();
|
|
800
|
+
// The keys that survive resolution for the requested target triple. The
|
|
801
|
+
// unfiltered graph still supplies the kinds, because a crate's kind does
|
|
802
|
+
// not change with the triple.
|
|
803
|
+
const targetIncludedKeys = CARGO_TARGET ? new Set() : undefined;
|
|
804
|
+
let targetFilterApplied = !CARGO_TARGET;
|
|
805
|
+
let anyMetadata = false;
|
|
806
|
+
let everyMetadata = true;
|
|
807
|
+
for (const cargoTomlFile of cargoFiles) {
|
|
808
|
+
const metadata = runCargoMetadata(cargoTomlFile);
|
|
809
|
+
if (!metadata) {
|
|
810
|
+
everyMetadata = false;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
// Every member of a workspace answers with the same resolve graph.
|
|
814
|
+
const workspaceRoot = metadata.workspace_root || cargoTomlFile;
|
|
815
|
+
if (visitedWorkspaceRoots.has(workspaceRoot)) {
|
|
816
|
+
anyMetadata = true;
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
visitedWorkspaceRoots.add(workspaceRoot);
|
|
820
|
+
anyMetadata = true;
|
|
821
|
+
if (CARGO_TARGET) {
|
|
822
|
+
const targetMetadata = runCargoMetadata(cargoTomlFile, CARGO_TARGET);
|
|
823
|
+
if (targetMetadata) {
|
|
824
|
+
for (const key of parseCargoMetadataResolve(targetMetadata)
|
|
825
|
+
.resolvedKeys) {
|
|
826
|
+
targetIncludedKeys.add(key);
|
|
827
|
+
}
|
|
828
|
+
targetFilterApplied = true;
|
|
829
|
+
} else {
|
|
830
|
+
console.warn(
|
|
831
|
+
`cargo metadata could not resolve the dependency graph for the target ${CARGO_TARGET}. The components are not filtered by target.`,
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
const graph = parseCargoMetadataResolve(metadata);
|
|
836
|
+
for (const [key, value] of graph.packageInfo) {
|
|
837
|
+
packageInfo.set(key, value);
|
|
838
|
+
}
|
|
839
|
+
for (const [key, value] of graph.edges) {
|
|
840
|
+
allEdges.set(key, [...(allEdges.get(key) || []), ...value]);
|
|
841
|
+
}
|
|
842
|
+
for (const [key, value] of rollupCargoDependencyKinds(graph)) {
|
|
843
|
+
const existing = rollups.get(key);
|
|
844
|
+
if (!existing) {
|
|
845
|
+
rollups.set(key, value);
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
// A crate that is a runtime dependency of any workspace in the scan is
|
|
849
|
+
// a runtime dependency of the BOM.
|
|
850
|
+
for (const kind of value.kinds) {
|
|
851
|
+
existing.kinds.add(kind);
|
|
852
|
+
}
|
|
853
|
+
for (const target of value.targets) {
|
|
854
|
+
existing.targets.add(target);
|
|
855
|
+
}
|
|
856
|
+
const precedence = ["root", "runtime", "build", "dev"];
|
|
857
|
+
if (
|
|
858
|
+
precedence.indexOf(value.kind) < precedence.indexOf(existing.kind)
|
|
859
|
+
) {
|
|
860
|
+
existing.kind = value.kind;
|
|
861
|
+
existing.targetGated = value.targetGated;
|
|
862
|
+
} else if (value.kind === existing.kind && !value.targetGated) {
|
|
863
|
+
existing.targetGated = false;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
if (anyMetadata && rollups.size) {
|
|
868
|
+
applyCargoDependencyKindMetadata(pkgList, rollups, packageInfo, {
|
|
869
|
+
resolveIsComplete: everyMetadata,
|
|
870
|
+
filterPlatform: targetFilterApplied ? CARGO_TARGET : undefined,
|
|
871
|
+
targetIncludedKeys: targetFilterApplied
|
|
872
|
+
? targetIncludedKeys
|
|
873
|
+
: undefined,
|
|
874
|
+
});
|
|
875
|
+
// A `-sys` crate puts a C library into the artifact that the cargo
|
|
876
|
+
// dependency graph itself cannot describe.
|
|
877
|
+
const nativeLibraries = buildCargoNativeLibraryComponents(
|
|
878
|
+
packageInfo,
|
|
879
|
+
allEdges,
|
|
880
|
+
rollups,
|
|
881
|
+
);
|
|
882
|
+
if (nativeLibraries.components.length) {
|
|
883
|
+
pkgList.push(...nativeLibraries.components);
|
|
884
|
+
if (dependencyTree) {
|
|
885
|
+
dependencyTree.push(...nativeLibraries.dependencies);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
recordDegradation("rust.cargo-metadata-unavailable", {
|
|
891
|
+
ecosystem: "rust",
|
|
892
|
+
tool: "cargo",
|
|
893
|
+
impact: "components",
|
|
894
|
+
command: `${basename(CARGO_CMD)} metadata`,
|
|
895
|
+
detail:
|
|
896
|
+
"cargo metadata could not be executed, so dependency kinds and platform gates were recovered from the manifests alone.",
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
if (!dependencyTree?.length || !manifestRootKeys.size) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
const declaredEdgesByRoot = new Map();
|
|
903
|
+
for (const [cargoTomlFile, rootKey] of manifestRootKeys) {
|
|
904
|
+
if (!rootKey) {
|
|
905
|
+
continue;
|
|
906
|
+
}
|
|
907
|
+
const declaredEdges = parseCargoManifestDependencyKinds(cargoTomlFile, {
|
|
908
|
+
includeWorkspaceMembers: !options.multiProject,
|
|
909
|
+
});
|
|
910
|
+
const existing = declaredEdgesByRoot.get(rootKey);
|
|
911
|
+
if (!existing) {
|
|
912
|
+
declaredEdgesByRoot.set(rootKey, declaredEdges);
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
for (const [name, value] of declaredEdges) {
|
|
916
|
+
const knownEdge = existing.get(name);
|
|
917
|
+
if (!knownEdge) {
|
|
918
|
+
existing.set(name, value);
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
for (const kind of value.kinds) {
|
|
922
|
+
knownEdge.kinds.add(kind);
|
|
923
|
+
}
|
|
924
|
+
for (const target of value.targets) {
|
|
925
|
+
knownEdge.targets.add(target);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
const graph = buildCargoLockDependencyGraph(
|
|
930
|
+
dependencyTree,
|
|
931
|
+
declaredEdgesByRoot,
|
|
932
|
+
);
|
|
933
|
+
applyCargoDependencyKindMetadata(pkgList, rollupCargoDependencyKinds(graph));
|
|
934
|
+
}
|
|
935
|
+
|
|
667
936
|
export async function createRustBom(path, options) {
|
|
668
937
|
let pkgList = [];
|
|
669
938
|
let parentComponent = {};
|
|
@@ -769,6 +1038,7 @@ export async function createRustBom(path, options) {
|
|
|
769
1038
|
"No Cargo.lock was found beside the manifest, so components carry the declared dependencies without locked versions or the resolved crate graph.",
|
|
770
1039
|
});
|
|
771
1040
|
}
|
|
1041
|
+
const manifestRootKeys = new Map();
|
|
772
1042
|
for (const f of cargoFiles) {
|
|
773
1043
|
if (DEBUG_MODE) {
|
|
774
1044
|
console.log(`Parsing ${f}`);
|
|
@@ -777,6 +1047,7 @@ export async function createRustBom(path, options) {
|
|
|
777
1047
|
includeWorkspaceMembers: !options.multiProject,
|
|
778
1048
|
});
|
|
779
1049
|
if (dlist?.length) {
|
|
1050
|
+
manifestRootKeys.set(f, cargoComponentKey(dlist[0]));
|
|
780
1051
|
if (!cargoLockMode) {
|
|
781
1052
|
pkgList = pkgList.concat(dlist);
|
|
782
1053
|
} else {
|
|
@@ -832,6 +1103,15 @@ export async function createRustBom(path, options) {
|
|
|
832
1103
|
);
|
|
833
1104
|
}
|
|
834
1105
|
}
|
|
1106
|
+
if (cargoLockMode) {
|
|
1107
|
+
annotateCargoDependencyKinds(
|
|
1108
|
+
pkgList,
|
|
1109
|
+
dependencyTree,
|
|
1110
|
+
cargoFiles,
|
|
1111
|
+
manifestRootKeys,
|
|
1112
|
+
options,
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
835
1115
|
return buildBomNSData(options, pkgList, "cargo", {
|
|
836
1116
|
src: path,
|
|
837
1117
|
filename: cargoLockFiles.join(", "),
|
|
@@ -1160,22 +1440,31 @@ export function createCppBom(path, options) {
|
|
|
1160
1440
|
}
|
|
1161
1441
|
if (cmakeLikeFiles.length) {
|
|
1162
1442
|
const beforeCmake = pkgList.length;
|
|
1443
|
+
// The files at the scan root name the project, so they are read before
|
|
1444
|
+
// the files of any sub-project (the sort is stable otherwise). The scan
|
|
1445
|
+
// root may be given as a relative path while the files come back absolute.
|
|
1446
|
+
const scanRoot = resolve(path);
|
|
1447
|
+
const atScanRoot = (f) => resolve(dirname(f)) === scanRoot;
|
|
1448
|
+
cmakeLikeFiles.sort(
|
|
1449
|
+
(a, b) => (atScanRoot(a) ? 0 : 1) - (atScanRoot(b) ? 0 : 1),
|
|
1450
|
+
);
|
|
1163
1451
|
for (const f of cmakeLikeFiles) {
|
|
1164
1452
|
if (DEBUG_MODE) {
|
|
1165
1453
|
console.log(`Parsing ${f}`);
|
|
1166
1454
|
}
|
|
1167
|
-
const basePath = dirname(f);
|
|
1168
1455
|
const retMap = parseCmakeLikeFile(f, "generic");
|
|
1169
1456
|
if (retMap.pkgList?.length) {
|
|
1170
1457
|
pkgList = pkgList.concat(retMap.pkgList);
|
|
1171
1458
|
}
|
|
1172
1459
|
if (
|
|
1173
|
-
|
|
1460
|
+
atScanRoot(f) &&
|
|
1174
1461
|
retMap.parentComponent &&
|
|
1175
1462
|
Object.keys(retMap.parentComponent).length
|
|
1176
1463
|
) {
|
|
1177
1464
|
if (!parentComponent) {
|
|
1178
1465
|
parentComponent = retMap.parentComponent;
|
|
1466
|
+
// a sub-project reusing the root project's name is the root itself
|
|
1467
|
+
addedParentComponentsMap[retMap.parentComponent.name] = true;
|
|
1179
1468
|
} else {
|
|
1180
1469
|
parentComponent.components = parentComponent.components || [];
|
|
1181
1470
|
if (!addedParentComponentsMap[retMap.parentComponent.name]) {
|
|
@@ -1190,6 +1479,7 @@ export function createCppBom(path, options) {
|
|
|
1190
1479
|
) {
|
|
1191
1480
|
retMap.parentComponent.type = "library";
|
|
1192
1481
|
pkgList.push(retMap.parentComponent);
|
|
1482
|
+
addedParentComponentsMap[retMap.parentComponent.name] = true;
|
|
1193
1483
|
}
|
|
1194
1484
|
// Retain the dependency tree from cmake
|
|
1195
1485
|
if (retMap.dependenciesList) {
|
|
@@ -1209,7 +1499,11 @@ export function createCppBom(path, options) {
|
|
|
1209
1499
|
// resolved versions and are left as they are.
|
|
1210
1500
|
pkgList = pkgList
|
|
1211
1501
|
.slice(0, beforeCmake)
|
|
1212
|
-
.concat(
|
|
1502
|
+
.concat(
|
|
1503
|
+
collapseCmakeVersions(
|
|
1504
|
+
preferFetchedCmakeDependencies(pkgList.slice(beforeCmake)),
|
|
1505
|
+
),
|
|
1506
|
+
);
|
|
1213
1507
|
}
|
|
1214
1508
|
// CMake describes how a project is built from source. An image or an OS
|
|
1215
1509
|
// installation carries build output, not a build tree, so resolving CMake
|
|
@@ -1299,6 +1593,7 @@ export function createCppBom(path, options) {
|
|
|
1299
1593
|
{ name: "cdx:cmake:resolvedVia", value: "gitclone-script" },
|
|
1300
1594
|
],
|
|
1301
1595
|
};
|
|
1596
|
+
pkgList = withoutScrapedFetchDependency(pkgList, dep.name);
|
|
1302
1597
|
if (dep.sourceDir) {
|
|
1303
1598
|
depComponent.properties.push({
|
|
1304
1599
|
name: "cdx:cmake:sourceDir",
|
|
@@ -1596,6 +1891,44 @@ export function createClojureBom(path, options) {
|
|
|
1596
1891
|
return {};
|
|
1597
1892
|
}
|
|
1598
1893
|
|
|
1894
|
+
/**
|
|
1895
|
+
* Locate the Stack lock files under a path.
|
|
1896
|
+
*
|
|
1897
|
+
* Stack names the lock after the configuration file it completes, so the
|
|
1898
|
+
* default `stack.yaml` produces `stack.yaml.lock` and a project with several
|
|
1899
|
+
* configurations produces one lock per configuration. `stack.lock` is also
|
|
1900
|
+
* accepted because tooling and documentation examples have used that name.
|
|
1901
|
+
*
|
|
1902
|
+
* @param {string} path Project path
|
|
1903
|
+
* @param {Object} options Parse options from the cli
|
|
1904
|
+
* @returns {string[]} Lock file paths, deduplicated
|
|
1905
|
+
*/
|
|
1906
|
+
export function findStackLockFiles(path, options) {
|
|
1907
|
+
const prefix = options.multiProject ? "**/" : "";
|
|
1908
|
+
const candidates = new Set([
|
|
1909
|
+
...getAllFiles(path, `${prefix}*.yaml.lock`, options),
|
|
1910
|
+
...getAllFiles(path, `${prefix}stack.lock`, options),
|
|
1911
|
+
]);
|
|
1912
|
+
return [...candidates].filter((lockFile) => {
|
|
1913
|
+
if (/(^|[\\/])stack\.(yaml\.)?lock$/u.test(lockFile)) {
|
|
1914
|
+
return true;
|
|
1915
|
+
}
|
|
1916
|
+
// `*.yaml.lock` is not a name Stack owns, so a custom configuration is
|
|
1917
|
+
// accepted only when the file it completes names a snapshot.
|
|
1918
|
+
const configFile = lockFile.replace(/\.lock$/u, "");
|
|
1919
|
+
if (!safeExistsSync(configFile)) {
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
try {
|
|
1923
|
+
return /^\s*(snapshot|resolver)\s*:/mu.test(
|
|
1924
|
+
readFileSync(configFile, "utf-8"),
|
|
1925
|
+
);
|
|
1926
|
+
} catch {
|
|
1927
|
+
return false;
|
|
1928
|
+
}
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1599
1932
|
/**
|
|
1600
1933
|
* Function to create bom string for Haskell projects
|
|
1601
1934
|
*
|
|
@@ -1626,12 +1959,44 @@ export function createHaskellBom(path, options) {
|
|
|
1626
1959
|
filename: cabalFiles.join(", "),
|
|
1627
1960
|
});
|
|
1628
1961
|
}
|
|
1962
|
+
// Stack projects keep the resolved extra-deps in a lock file named after
|
|
1963
|
+
// the configuration file, so `stack.yaml` locks to `stack.yaml.lock`.
|
|
1964
|
+
const stackLockFiles = findStackLockFiles(path, options);
|
|
1965
|
+
if (stackLockFiles.length) {
|
|
1966
|
+
const dependencies = [];
|
|
1967
|
+
let parentComponent = {};
|
|
1968
|
+
for (const stackLockFile of stackLockFiles) {
|
|
1969
|
+
if (DEBUG_MODE) {
|
|
1970
|
+
console.log(`Parsing ${stackLockFile}`);
|
|
1971
|
+
}
|
|
1972
|
+
const stackYamlFile = stackLockFile.replace(/\.lock$/u, "");
|
|
1973
|
+
const result = parseStackProject(
|
|
1974
|
+
stackLockFile,
|
|
1975
|
+
safeExistsSync(stackYamlFile) ? stackYamlFile : undefined,
|
|
1976
|
+
);
|
|
1977
|
+
if (result.pkgList?.length) {
|
|
1978
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
1979
|
+
}
|
|
1980
|
+
if (!Object.keys(parentComponent).length) {
|
|
1981
|
+
parentComponent = result.parentComponent;
|
|
1982
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
if (pkgList.length) {
|
|
1986
|
+
return buildBomNSData(options, pkgList, "hackage", {
|
|
1987
|
+
src: path,
|
|
1988
|
+
filename: stackLockFiles.join(", "),
|
|
1989
|
+
dependencies,
|
|
1990
|
+
parentComponent,
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1629
1994
|
recordDegradation("haskell.freeze-missing", {
|
|
1630
1995
|
ecosystem: "haskell",
|
|
1631
1996
|
tool: "cabal",
|
|
1632
1997
|
impact: "components",
|
|
1633
1998
|
detail:
|
|
1634
|
-
"No cabal.project.freeze was found
|
|
1999
|
+
"No cabal.project.freeze or stack.yaml.lock was found; those lock files are the sources the parsers read for the package set.",
|
|
1635
2000
|
});
|
|
1636
2001
|
return {};
|
|
1637
2002
|
}
|
|
@@ -1699,6 +2064,50 @@ export async function createSwiftBom(path, options) {
|
|
|
1699
2064
|
let parentComponent = {};
|
|
1700
2065
|
const completedPath = [];
|
|
1701
2066
|
let packageArgsMessageShown = false;
|
|
2067
|
+
// Packages that only the root package's tests use are scoped optional so
|
|
2068
|
+
// that `--required-only` can narrow the BOM. A package some project (or
|
|
2069
|
+
// some production path) needs stays required.
|
|
2070
|
+
const swiftScopes = {
|
|
2071
|
+
optionalRefs: new Set(),
|
|
2072
|
+
requiredRefs: new Set(),
|
|
2073
|
+
optionalNames: new Set(),
|
|
2074
|
+
requiredNames: new Set(),
|
|
2075
|
+
};
|
|
2076
|
+
const classifiedPaths = new Set();
|
|
2077
|
+
const classifyScopes = (basePath, swiftCommand, treeData) => {
|
|
2078
|
+
if (classifiedPaths.has(basePath)) {
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
classifiedPaths.add(basePath);
|
|
2082
|
+
const dumpArgs =
|
|
2083
|
+
swiftCommand === "xcrun"
|
|
2084
|
+
? ["swift", "package", "dump-package"]
|
|
2085
|
+
: ["package", "dump-package"];
|
|
2086
|
+
const dumpResult = safeSpawnSync(swiftCommand, dumpArgs, {
|
|
2087
|
+
cwd: basePath,
|
|
2088
|
+
});
|
|
2089
|
+
if (!dumpResult?.stdout) {
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
let dumpJson;
|
|
2093
|
+
let treeJson;
|
|
2094
|
+
try {
|
|
2095
|
+
dumpJson = JSON.parse(Buffer.from(dumpResult.stdout).toString());
|
|
2096
|
+
} catch (_e) {
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
try {
|
|
2100
|
+
treeJson = treeData ? JSON.parse(treeData) : undefined;
|
|
2101
|
+
} catch (_e) {
|
|
2102
|
+
treeJson = undefined;
|
|
2103
|
+
}
|
|
2104
|
+
const scopes = classifySwiftDependencyScopes(dumpJson, treeJson);
|
|
2105
|
+
for (const key of Object.keys(swiftScopes)) {
|
|
2106
|
+
for (const value of scopes[key]) {
|
|
2107
|
+
swiftScopes[key].add(value);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
1702
2111
|
if (pkgResolvedFiles.length) {
|
|
1703
2112
|
for (const f of pkgResolvedFiles) {
|
|
1704
2113
|
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
@@ -1822,6 +2231,33 @@ export async function createSwiftBom(path, options) {
|
|
|
1822
2231
|
detail: "the swift package command failed",
|
|
1823
2232
|
});
|
|
1824
2233
|
}
|
|
2234
|
+
// Scope classification also works without the dependency graph
|
|
2235
|
+
classifyScopes(basePath, swiftCommand, treeData);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
// Scope packages that only tests use as optional. A package some project or
|
|
2239
|
+
// production path needs keeps its default (required) scope; evidence-based
|
|
2240
|
+
// filtering after generation can still promote an optional package when
|
|
2241
|
+
// the sources are found to use it.
|
|
2242
|
+
for (const apkg of pkgList) {
|
|
2243
|
+
if (apkg?.scope || !apkg?.name) {
|
|
2244
|
+
continue;
|
|
2245
|
+
}
|
|
2246
|
+
const refs = [apkg["bom-ref"], apkg.purl].filter(Boolean);
|
|
2247
|
+
const names = [String(apkg.name).toLowerCase()];
|
|
2248
|
+
for (const aproperty of apkg.properties || []) {
|
|
2249
|
+
if (aproperty?.name === "cdx:swift:packageName" && aproperty.value) {
|
|
2250
|
+
names.push(String(aproperty.value).toLowerCase());
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
const isOptional =
|
|
2254
|
+
refs.some((r) => swiftScopes.optionalRefs.has(r)) ||
|
|
2255
|
+
names.some((n) => swiftScopes.optionalNames.has(n));
|
|
2256
|
+
const isRequired =
|
|
2257
|
+
refs.some((r) => swiftScopes.requiredRefs.has(r)) ||
|
|
2258
|
+
names.some((n) => swiftScopes.requiredNames.has(n));
|
|
2259
|
+
if (isOptional && !isRequired) {
|
|
2260
|
+
apkg.scope = "optional";
|
|
1825
2261
|
}
|
|
1826
2262
|
}
|
|
1827
2263
|
if (shouldFetchLicense()) {
|
|
@@ -2302,3 +2738,475 @@ export async function createGleamBom(path, options) {
|
|
|
2302
2738
|
|
|
2303
2739
|
return {};
|
|
2304
2740
|
}
|
|
2741
|
+
|
|
2742
|
+
/**
|
|
2743
|
+
* Function to create bom string for Julia projects.
|
|
2744
|
+
*
|
|
2745
|
+
* The resolved `Manifest.toml` is preferred over the declared `Project.toml`
|
|
2746
|
+
* so the BOM carries pinned versions and the package graph.
|
|
2747
|
+
*
|
|
2748
|
+
* @param {string} path to the project
|
|
2749
|
+
* @param {Object} options Parse options from the cli
|
|
2750
|
+
* @returns {Promise<Object>} BOM object
|
|
2751
|
+
*/
|
|
2752
|
+
export async function createJuliaBom(path, options) {
|
|
2753
|
+
let pkgList = [];
|
|
2754
|
+
let dependencies = [];
|
|
2755
|
+
let parentComponent = {};
|
|
2756
|
+
|
|
2757
|
+
const projectTomlFiles = getAllFiles(
|
|
2758
|
+
path,
|
|
2759
|
+
`${options.multiProject ? "**/" : ""}Project.toml`,
|
|
2760
|
+
options,
|
|
2761
|
+
);
|
|
2762
|
+
const manifestOnly = !projectTomlFiles.length;
|
|
2763
|
+
|
|
2764
|
+
for (const projectTomlFile of projectTomlFiles) {
|
|
2765
|
+
if (DEBUG_MODE) {
|
|
2766
|
+
console.log(`Parsing ${projectTomlFile}`);
|
|
2767
|
+
}
|
|
2768
|
+
const baseDir = dirname(projectTomlFile);
|
|
2769
|
+
const manifestTomlFile = join(baseDir, "Manifest.toml");
|
|
2770
|
+
const result = parseJuliaProject(
|
|
2771
|
+
projectTomlFile,
|
|
2772
|
+
safeExistsSync(manifestTomlFile) ? manifestTomlFile : undefined,
|
|
2773
|
+
);
|
|
2774
|
+
collectProjectResult(result);
|
|
2775
|
+
}
|
|
2776
|
+
if (manifestOnly) {
|
|
2777
|
+
for (const manifestTomlFile of getAllFiles(
|
|
2778
|
+
path,
|
|
2779
|
+
`${options.multiProject ? "**/" : ""}Manifest.toml`,
|
|
2780
|
+
options,
|
|
2781
|
+
)) {
|
|
2782
|
+
if (DEBUG_MODE) {
|
|
2783
|
+
console.log(`Parsing ${manifestTomlFile}`);
|
|
2784
|
+
}
|
|
2785
|
+
// Gleam also writes a `manifest.toml`; on case-insensitive filesystems
|
|
2786
|
+
// the lookup above can surface it, so only accept files that parse as
|
|
2787
|
+
// a Julia manifest.
|
|
2788
|
+
const parsed = parseJuliaProject(undefined, manifestTomlFile);
|
|
2789
|
+
if (parsed.pkgList.length) {
|
|
2790
|
+
collectProjectResult(parsed);
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
function collectProjectResult(result) {
|
|
2796
|
+
if (result.pkgList?.length) {
|
|
2797
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2798
|
+
}
|
|
2799
|
+
if (result.dependencies?.length) {
|
|
2800
|
+
dependencies = dependencies.concat(result.dependencies);
|
|
2801
|
+
}
|
|
2802
|
+
if (
|
|
2803
|
+
!Object.keys(parentComponent).length &&
|
|
2804
|
+
Object.keys(result.parentComponent).length
|
|
2805
|
+
) {
|
|
2806
|
+
parentComponent = result.parentComponent;
|
|
2807
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
if (pkgList.length || Object.keys(parentComponent).length) {
|
|
2812
|
+
return buildBomNSData(options, pkgList, "julia", {
|
|
2813
|
+
src: path,
|
|
2814
|
+
filename: projectTomlFiles.join(", "),
|
|
2815
|
+
dependencies,
|
|
2816
|
+
parentComponent,
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
return {};
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
/**
|
|
2823
|
+
* Attach a root dependency edge from the parent component to its direct
|
|
2824
|
+
* dependencies, following the shape used by the Gleam and Nix flows.
|
|
2825
|
+
*
|
|
2826
|
+
* @param {{rootInputs?: string[]}} result Parser result with root inputs
|
|
2827
|
+
* @param {Object} parentComponent Parent component to give a bom-ref
|
|
2828
|
+
* @param {Object[]} dependencies Dependency edge list to append to
|
|
2829
|
+
*/
|
|
2830
|
+
function attachRootEdge(result, parentComponent, dependencies) {
|
|
2831
|
+
if (result.rootInputs?.length && parentComponent.name) {
|
|
2832
|
+
const rootRef = `application:${parentComponent.name}:${parentComponent.version || "latest"}`;
|
|
2833
|
+
parentComponent["bom-ref"] = rootRef;
|
|
2834
|
+
dependencies.push({
|
|
2835
|
+
ref: rootRef,
|
|
2836
|
+
dependsOn: [...new Set(result.rootInputs)],
|
|
2837
|
+
});
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
/**
|
|
2842
|
+
* Function to create bom string for Erlang projects using rebar3.
|
|
2843
|
+
*
|
|
2844
|
+
* @param {string} path to the project
|
|
2845
|
+
* @param {Object} options Parse options from the cli
|
|
2846
|
+
* @returns {Promise<Object>} BOM object
|
|
2847
|
+
*/
|
|
2848
|
+
export async function createErlangBom(path, options) {
|
|
2849
|
+
let pkgList = [];
|
|
2850
|
+
const rebarLockFiles = getAllFiles(
|
|
2851
|
+
path,
|
|
2852
|
+
`${options.multiProject ? "**/" : ""}rebar.lock`,
|
|
2853
|
+
options,
|
|
2854
|
+
);
|
|
2855
|
+
for (const rebarLockFile of rebarLockFiles) {
|
|
2856
|
+
if (DEBUG_MODE) {
|
|
2857
|
+
console.log(`Parsing ${rebarLockFile}`);
|
|
2858
|
+
}
|
|
2859
|
+
const result = parseRebarLock(rebarLockFile);
|
|
2860
|
+
if (result.pkgList?.length) {
|
|
2861
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
if (pkgList.length) {
|
|
2865
|
+
return buildBomNSData(options, pkgList, "erlang", {
|
|
2866
|
+
src: path,
|
|
2867
|
+
filename: rebarLockFiles.join(", "),
|
|
2868
|
+
});
|
|
2869
|
+
}
|
|
2870
|
+
return {};
|
|
2871
|
+
}
|
|
2872
|
+
|
|
2873
|
+
/**
|
|
2874
|
+
* Function to create bom string for OCaml projects using opam.
|
|
2875
|
+
*
|
|
2876
|
+
* Resolved `.opam.locked` files are preferred; a plain `.opam` file is used
|
|
2877
|
+
* as a declared fallback with unpinned versions.
|
|
2878
|
+
*
|
|
2879
|
+
* @param {string} path to the project
|
|
2880
|
+
* @param {Object} options Parse options from the cli
|
|
2881
|
+
* @returns {Promise<Object>} BOM object
|
|
2882
|
+
*/
|
|
2883
|
+
export async function createOCamlBom(path, options) {
|
|
2884
|
+
let pkgList = [];
|
|
2885
|
+
const opamLockedFiles = getAllFiles(
|
|
2886
|
+
path,
|
|
2887
|
+
`${options.multiProject ? "**/" : ""}*.opam.locked`,
|
|
2888
|
+
options,
|
|
2889
|
+
);
|
|
2890
|
+
const opamFiles = opamLockedFiles.length
|
|
2891
|
+
? []
|
|
2892
|
+
: getAllFiles(path, `${options.multiProject ? "**/" : ""}*.opam`, options);
|
|
2893
|
+
for (const opamFile of [...opamLockedFiles, ...opamFiles]) {
|
|
2894
|
+
if (DEBUG_MODE) {
|
|
2895
|
+
console.log(`Parsing ${opamFile}`);
|
|
2896
|
+
}
|
|
2897
|
+
const result = parseOpamLockedFile(opamFile);
|
|
2898
|
+
if (result.pkgList?.length) {
|
|
2899
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
if (pkgList.length) {
|
|
2903
|
+
return buildBomNSData(options, pkgList, "ocaml", {
|
|
2904
|
+
src: path,
|
|
2905
|
+
filename: [...opamLockedFiles, ...opamFiles].join(", "),
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
return {};
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
/**
|
|
2912
|
+
* Function to create bom string for Elm projects.
|
|
2913
|
+
*
|
|
2914
|
+
* @param {string} path to the project
|
|
2915
|
+
* @param {Object} options Parse options from the cli
|
|
2916
|
+
* @returns {Promise<Object>} BOM object
|
|
2917
|
+
*/
|
|
2918
|
+
export async function createElmBom(path, options) {
|
|
2919
|
+
let pkgList = [];
|
|
2920
|
+
let dependencies = [];
|
|
2921
|
+
let parentComponent = {};
|
|
2922
|
+
|
|
2923
|
+
const elmJsonFiles = getAllFiles(
|
|
2924
|
+
path,
|
|
2925
|
+
`${options.multiProject ? "**/" : ""}elm.json`,
|
|
2926
|
+
options,
|
|
2927
|
+
).filter(isElmProjectFile);
|
|
2928
|
+
// A single reader memoises the cache lookups shared by every manifest.
|
|
2929
|
+
const cacheReader = createElmCacheReader();
|
|
2930
|
+
for (const elmJsonFile of elmJsonFiles) {
|
|
2931
|
+
if (DEBUG_MODE) {
|
|
2932
|
+
console.log(`Parsing ${elmJsonFile}`);
|
|
2933
|
+
}
|
|
2934
|
+
const result = parseElmProject(elmJsonFile, cacheReader);
|
|
2935
|
+
if (result.pkgList?.length) {
|
|
2936
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2937
|
+
}
|
|
2938
|
+
if (result.dependencies?.length) {
|
|
2939
|
+
dependencies = dependencies.concat(result.dependencies);
|
|
2940
|
+
}
|
|
2941
|
+
if (!Object.keys(parentComponent).length) {
|
|
2942
|
+
parentComponent = result.parentComponent;
|
|
2943
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
const legacyFiles = getAllFiles(
|
|
2947
|
+
path,
|
|
2948
|
+
`${options.multiProject ? "**/" : ""}elm-package.json`,
|
|
2949
|
+
options,
|
|
2950
|
+
);
|
|
2951
|
+
for (const legacyFile of legacyFiles) {
|
|
2952
|
+
if (DEBUG_MODE) {
|
|
2953
|
+
console.log(`Parsing ${legacyFile}`);
|
|
2954
|
+
}
|
|
2955
|
+
const baseDir = dirname(legacyFile);
|
|
2956
|
+
const exactDepsFile = join(baseDir, "elm-stuff", "exact-dependencies.json");
|
|
2957
|
+
const result = parseLegacyElmProject(
|
|
2958
|
+
legacyFile,
|
|
2959
|
+
safeExistsSync(exactDepsFile) ? exactDepsFile : undefined,
|
|
2960
|
+
);
|
|
2961
|
+
if (result.pkgList?.length) {
|
|
2962
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
2963
|
+
}
|
|
2964
|
+
if (result.dependencies?.length) {
|
|
2965
|
+
dependencies = dependencies.concat(result.dependencies);
|
|
2966
|
+
}
|
|
2967
|
+
if (!Object.keys(parentComponent).length) {
|
|
2968
|
+
parentComponent = result.parentComponent;
|
|
2969
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
if (pkgList.length && shouldFetchPackageMetadata()) {
|
|
2973
|
+
pkgList = await getElmMetadata(pkgList, dependencies);
|
|
2974
|
+
}
|
|
2975
|
+
if (pkgList.length || Object.keys(parentComponent).length) {
|
|
2976
|
+
return buildBomNSData(options, pkgList, "elm", {
|
|
2977
|
+
src: path,
|
|
2978
|
+
filename: [...elmJsonFiles, ...legacyFiles].join(", "),
|
|
2979
|
+
dependencies,
|
|
2980
|
+
parentComponent,
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
return {};
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
/**
|
|
2987
|
+
* Function to create bom string for Crystal projects using shards.
|
|
2988
|
+
*
|
|
2989
|
+
* @param {string} path to the project
|
|
2990
|
+
* @param {Object} options Parse options from the cli
|
|
2991
|
+
* @returns {Promise<Object>} BOM object
|
|
2992
|
+
*/
|
|
2993
|
+
export async function createCrystalBom(path, options) {
|
|
2994
|
+
let pkgList = [];
|
|
2995
|
+
const dependencies = [];
|
|
2996
|
+
let parentComponent = {};
|
|
2997
|
+
|
|
2998
|
+
const shardYmlFiles = getAllFiles(
|
|
2999
|
+
path,
|
|
3000
|
+
`${options.multiProject ? "**/" : ""}shard.yml`,
|
|
3001
|
+
options,
|
|
3002
|
+
);
|
|
3003
|
+
for (const shardYmlFile of shardYmlFiles) {
|
|
3004
|
+
if (DEBUG_MODE) {
|
|
3005
|
+
console.log(`Parsing ${shardYmlFile}`);
|
|
3006
|
+
}
|
|
3007
|
+
const baseDir = dirname(shardYmlFile);
|
|
3008
|
+
const shardLockFile = join(baseDir, "shard.lock");
|
|
3009
|
+
const result = parseShardsProject(
|
|
3010
|
+
shardYmlFile,
|
|
3011
|
+
safeExistsSync(shardLockFile) ? shardLockFile : undefined,
|
|
3012
|
+
);
|
|
3013
|
+
if (result.pkgList?.length) {
|
|
3014
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
3015
|
+
}
|
|
3016
|
+
if (!Object.keys(parentComponent).length) {
|
|
3017
|
+
parentComponent = result.parentComponent;
|
|
3018
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
if (pkgList.length || Object.keys(parentComponent).length) {
|
|
3022
|
+
return buildBomNSData(options, pkgList, "crystal", {
|
|
3023
|
+
src: path,
|
|
3024
|
+
filename: shardYmlFiles.join(", "),
|
|
3025
|
+
dependencies,
|
|
3026
|
+
parentComponent,
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3029
|
+
return {};
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
/**
|
|
3033
|
+
* Function to create bom string for Nim projects using nimble.
|
|
3034
|
+
*
|
|
3035
|
+
* @param {string} path to the project
|
|
3036
|
+
* @param {Object} options Parse options from the cli
|
|
3037
|
+
* @returns {Promise<Object>} BOM object
|
|
3038
|
+
*/
|
|
3039
|
+
export async function createNimBom(path, options) {
|
|
3040
|
+
let pkgList = [];
|
|
3041
|
+
let dependencies = [];
|
|
3042
|
+
let parentComponent = {};
|
|
3043
|
+
|
|
3044
|
+
const nimbleFiles = getAllFiles(
|
|
3045
|
+
path,
|
|
3046
|
+
`${options.multiProject ? "**/" : ""}*.nimble`,
|
|
3047
|
+
options,
|
|
3048
|
+
);
|
|
3049
|
+
for (const nimbleFile of nimbleFiles) {
|
|
3050
|
+
if (DEBUG_MODE) {
|
|
3051
|
+
console.log(`Parsing ${nimbleFile}`);
|
|
3052
|
+
}
|
|
3053
|
+
const baseDir = dirname(nimbleFile);
|
|
3054
|
+
const nimbleLockFile = join(baseDir, "nimble.lock");
|
|
3055
|
+
const result = parseNimProject(
|
|
3056
|
+
nimbleFile,
|
|
3057
|
+
safeExistsSync(nimbleLockFile) ? nimbleLockFile : undefined,
|
|
3058
|
+
);
|
|
3059
|
+
if (result.pkgList?.length) {
|
|
3060
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
3061
|
+
}
|
|
3062
|
+
if (result.dependencies?.length) {
|
|
3063
|
+
dependencies = dependencies.concat(result.dependencies);
|
|
3064
|
+
}
|
|
3065
|
+
if (!Object.keys(parentComponent).length) {
|
|
3066
|
+
parentComponent = result.parentComponent;
|
|
3067
|
+
attachRootEdge(result, parentComponent, dependencies);
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
if (pkgList.length || Object.keys(parentComponent).length) {
|
|
3071
|
+
return buildBomNSData(options, pkgList, "nim", {
|
|
3072
|
+
src: path,
|
|
3073
|
+
filename: nimbleFiles.join(", "),
|
|
3074
|
+
dependencies,
|
|
3075
|
+
parentComponent,
|
|
3076
|
+
});
|
|
3077
|
+
}
|
|
3078
|
+
return {};
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
/**
|
|
3082
|
+
* Function to create bom string for Lua projects using LuaRocks.
|
|
3083
|
+
*
|
|
3084
|
+
* When a `luarocks.lock` is present next to a rockspec it supplies the
|
|
3085
|
+
* resolved versions; otherwise the rockspec's declared dependencies are
|
|
3086
|
+
* inventoried without versions.
|
|
3087
|
+
*
|
|
3088
|
+
* @param {string} path to the project
|
|
3089
|
+
* @param {Object} options Parse options from the cli
|
|
3090
|
+
* @returns {Promise<Object>} BOM object
|
|
3091
|
+
*/
|
|
3092
|
+
export async function createLuaBom(path, options) {
|
|
3093
|
+
let pkgList = [];
|
|
3094
|
+
let parentComponent = {};
|
|
3095
|
+
const seenLockFiles = new Set();
|
|
3096
|
+
const rockspecFiles = getAllFiles(
|
|
3097
|
+
path,
|
|
3098
|
+
`${options.multiProject ? "**/" : ""}*.rockspec`,
|
|
3099
|
+
options,
|
|
3100
|
+
);
|
|
3101
|
+
for (const rockspecFile of rockspecFiles) {
|
|
3102
|
+
if (DEBUG_MODE) {
|
|
3103
|
+
console.log(`Parsing ${rockspecFile}`);
|
|
3104
|
+
}
|
|
3105
|
+
const baseDir = dirname(rockspecFile);
|
|
3106
|
+
const lockFile = join(baseDir, "luarocks.lock");
|
|
3107
|
+
// The rockspec names the project and its declared set either way; the
|
|
3108
|
+
// lock, when present, supplies the resolved versions.
|
|
3109
|
+
const declared = parseRockspecFile(rockspecFile);
|
|
3110
|
+
if (!Object.keys(parentComponent).length) {
|
|
3111
|
+
parentComponent = declared.parentComponent;
|
|
3112
|
+
}
|
|
3113
|
+
let resolved;
|
|
3114
|
+
if (safeExistsSync(lockFile)) {
|
|
3115
|
+
seenLockFiles.add(lockFile);
|
|
3116
|
+
resolved = parseLuaRocksLock(lockFile, rockspecFile);
|
|
3117
|
+
}
|
|
3118
|
+
const chosen = resolved?.pkgList?.length ? resolved : declared;
|
|
3119
|
+
if (chosen.pkgList?.length) {
|
|
3120
|
+
pkgList = pkgList.concat(chosen.pkgList);
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
// A lock can also stand alone, in a project whose rockspec is generated at
|
|
3124
|
+
// release time rather than committed.
|
|
3125
|
+
for (const lockFile of getAllFiles(
|
|
3126
|
+
path,
|
|
3127
|
+
`${options.multiProject ? "**/" : ""}luarocks.lock`,
|
|
3128
|
+
options,
|
|
3129
|
+
)) {
|
|
3130
|
+
if (seenLockFiles.has(lockFile)) {
|
|
3131
|
+
continue;
|
|
3132
|
+
}
|
|
3133
|
+
if (DEBUG_MODE) {
|
|
3134
|
+
console.log(`Parsing ${lockFile}`);
|
|
3135
|
+
}
|
|
3136
|
+
const result = parseLuaRocksLock(lockFile);
|
|
3137
|
+
if (result.pkgList?.length) {
|
|
3138
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
if (pkgList.length) {
|
|
3142
|
+
return buildBomNSData(options, pkgList, "lua", {
|
|
3143
|
+
src: path,
|
|
3144
|
+
filename: rockspecFiles.join(", "),
|
|
3145
|
+
parentComponent,
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
return {};
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
/**
|
|
3152
|
+
* Function to create bom string for Spack environments.
|
|
3153
|
+
*
|
|
3154
|
+
* @param {string} path to the project
|
|
3155
|
+
* @param {Object} options Parse options from the cli
|
|
3156
|
+
* @returns {Promise<Object>} BOM object
|
|
3157
|
+
*/
|
|
3158
|
+
export async function createSpackBom(path, options) {
|
|
3159
|
+
let pkgList = [];
|
|
3160
|
+
let dependencies = [];
|
|
3161
|
+
let rootInputs = [];
|
|
3162
|
+
const spackLockFiles = getAllFiles(
|
|
3163
|
+
path,
|
|
3164
|
+
`${options.multiProject ? "**/" : ""}spack.lock`,
|
|
3165
|
+
options,
|
|
3166
|
+
);
|
|
3167
|
+
for (const spackLockFile of spackLockFiles) {
|
|
3168
|
+
if (DEBUG_MODE) {
|
|
3169
|
+
console.log(`Parsing ${spackLockFile}`);
|
|
3170
|
+
}
|
|
3171
|
+
const result = parseSpackLock(spackLockFile);
|
|
3172
|
+
if (result.pkgList?.length) {
|
|
3173
|
+
pkgList = pkgList.concat(result.pkgList);
|
|
3174
|
+
}
|
|
3175
|
+
if (result.dependencies?.length) {
|
|
3176
|
+
dependencies = dependencies.concat(result.dependencies);
|
|
3177
|
+
}
|
|
3178
|
+
if (result.rootInputs?.length) {
|
|
3179
|
+
rootInputs = rootInputs.concat(result.rootInputs);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
if (!pkgList.length) {
|
|
3183
|
+
return {};
|
|
3184
|
+
}
|
|
3185
|
+
// The lock names no project component, so the environment directory is the
|
|
3186
|
+
// name the environment is already known by. Its roots are the specs the
|
|
3187
|
+
// lock records as requested, which is why they are linked explicitly rather
|
|
3188
|
+
// than inferred from which nodes have no incoming edge: a build-only tool
|
|
3189
|
+
// such as gmake also has none, and it is not a root.
|
|
3190
|
+
const envName = basename(resolve(path));
|
|
3191
|
+
const parentComponent = {
|
|
3192
|
+
type: "application",
|
|
3193
|
+
name: envName,
|
|
3194
|
+
"bom-ref": `application:${envName}:latest`,
|
|
3195
|
+
description: `Spack environment: ${envName}`,
|
|
3196
|
+
properties: [
|
|
3197
|
+
{ name: "internal:SrcFile", value: spackLockFiles.join(", ") },
|
|
3198
|
+
],
|
|
3199
|
+
};
|
|
3200
|
+
if (rootInputs.length) {
|
|
3201
|
+
dependencies.push({
|
|
3202
|
+
ref: parentComponent["bom-ref"],
|
|
3203
|
+
dependsOn: [...new Set(rootInputs)].sort(),
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3206
|
+
return buildBomNSData(options, pkgList, "spack", {
|
|
3207
|
+
src: path,
|
|
3208
|
+
filename: spackLockFiles.join(", "),
|
|
3209
|
+
dependencies,
|
|
3210
|
+
parentComponent,
|
|
3211
|
+
});
|
|
3212
|
+
}
|