@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
3
|
import {
|
|
4
4
|
sep as _sep,
|
|
5
5
|
basename,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
hasDangerousUnicode,
|
|
19
19
|
readEnvironmentVariable,
|
|
20
20
|
} from "../core/activity.js";
|
|
21
|
+
import { readRegularTextFile } from "../core/boundedFile.js";
|
|
21
22
|
import { recordDegradation } from "../core/buildLedger.js";
|
|
22
23
|
import { deferFailOnError } from "../core/deferredExit.js";
|
|
23
24
|
import { shouldFetchLicense } from "../core/env.js";
|
|
@@ -52,7 +53,12 @@ import {
|
|
|
52
53
|
tryBuildPurl,
|
|
53
54
|
} from "../inventory/purl.js";
|
|
54
55
|
import { getLicenses, spdxLicenses } from "../inventory/spdx.js";
|
|
55
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
cmakeFetchDependencies,
|
|
58
|
+
cmakeSetVariables,
|
|
59
|
+
expandCmakeVariables,
|
|
60
|
+
parseCmakeCommands,
|
|
61
|
+
} from "../parsers/cmakeLists.js";
|
|
56
62
|
import { isValidIdnEmail } from "../parsers/email.js";
|
|
57
63
|
import { submodulePurlCoordinates } from "../parsers/gitmodules.js";
|
|
58
64
|
import { getDartMetadata } from "./ecosystems.js";
|
|
@@ -2784,6 +2790,130 @@ export function cmakeFetchDependencyComponent(dep, srcFile) {
|
|
|
2784
2790
|
return component;
|
|
2785
2791
|
}
|
|
2786
2792
|
|
|
2793
|
+
const MAX_MESON_WRAPS = 256;
|
|
2794
|
+
const MAX_MESON_WRAP_BYTES = 64 * 1024;
|
|
2795
|
+
|
|
2796
|
+
function upstreamVersionOf(text) {
|
|
2797
|
+
if (typeof text !== "string") {
|
|
2798
|
+
return undefined;
|
|
2799
|
+
}
|
|
2800
|
+
const value = text.trim().replace(/^v(?=\d)/, "");
|
|
2801
|
+
return /^\d+(\.\d+)*([-+._~][0-9A-Za-z.]+)?$/.test(value) ? value : undefined;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
// `brotli-1.1.0`, `brotli-1.1.0.tar.gz`: the version after the last `-` that
|
|
2805
|
+
// starts a number.
|
|
2806
|
+
function versionFromArchiveName(name) {
|
|
2807
|
+
if (typeof name !== "string") {
|
|
2808
|
+
return undefined;
|
|
2809
|
+
}
|
|
2810
|
+
const stem = name.replace(/\.(tar\.gz|tar\.xz|tar\.bz2|tgz|zip|tar)$/i, "");
|
|
2811
|
+
const dash = stem.search(/-\d[^-]*$/);
|
|
2812
|
+
return dash === -1 ? undefined : upstreamVersionOf(stem.slice(dash + 1));
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
/**
|
|
2816
|
+
* Read the wrap files of a meson project's subprojects directory: which
|
|
2817
|
+
* dependency names each wrap provides, and the version it pins.
|
|
2818
|
+
* https://mesonbuild.com/Wrap-dependency-system-manual.html
|
|
2819
|
+
*
|
|
2820
|
+
* A WrapDB wrap records `wrapdb_version = <upstream>-<wrap revision>`; any wrap
|
|
2821
|
+
* names its unpacked `directory` and its archive, which carry the upstream
|
|
2822
|
+
* version; a git wrap pins a `revision` that is often a version tag.
|
|
2823
|
+
*
|
|
2824
|
+
* @param {string} subprojectsDir path of the subprojects directory
|
|
2825
|
+
* @returns {Map<string, {wrap: string, version: string, source: string, revision?: string}>}
|
|
2826
|
+
* by provided dependency name, lower-case
|
|
2827
|
+
*/
|
|
2828
|
+
export function readMesonWraps(subprojectsDir) {
|
|
2829
|
+
const provided = new Map();
|
|
2830
|
+
let wrapFiles = [];
|
|
2831
|
+
try {
|
|
2832
|
+
wrapFiles = readdirSync(subprojectsDir)
|
|
2833
|
+
.filter((name) => name.endsWith(".wrap"))
|
|
2834
|
+
.sort()
|
|
2835
|
+
.slice(0, MAX_MESON_WRAPS);
|
|
2836
|
+
} catch {
|
|
2837
|
+
return provided;
|
|
2838
|
+
}
|
|
2839
|
+
for (const wrapFile of wrapFiles) {
|
|
2840
|
+
// A regular file only: a FIFO named *.wrap would block a plain read.
|
|
2841
|
+
const text = readRegularTextFile(
|
|
2842
|
+
join(subprojectsDir, wrapFile),
|
|
2843
|
+
MAX_MESON_WRAP_BYTES,
|
|
2844
|
+
);
|
|
2845
|
+
if (text === undefined) {
|
|
2846
|
+
continue;
|
|
2847
|
+
}
|
|
2848
|
+
const sections = {};
|
|
2849
|
+
let section = "";
|
|
2850
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
2851
|
+
const line = rawLine.trim();
|
|
2852
|
+
if (!line || line.startsWith("#") || line.startsWith(";")) {
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2855
|
+
if (line.startsWith("[") && line.endsWith("]")) {
|
|
2856
|
+
section = line.slice(1, -1).trim();
|
|
2857
|
+
sections[section] = sections[section] || {};
|
|
2858
|
+
continue;
|
|
2859
|
+
}
|
|
2860
|
+
const equals = line.indexOf("=");
|
|
2861
|
+
if (equals <= 0 || !section) {
|
|
2862
|
+
continue;
|
|
2863
|
+
}
|
|
2864
|
+
sections[section][line.slice(0, equals).trim()] = line
|
|
2865
|
+
.slice(equals + 1)
|
|
2866
|
+
.trim();
|
|
2867
|
+
}
|
|
2868
|
+
const body = sections["wrap-file"] || sections["wrap-git"] || {};
|
|
2869
|
+
let version;
|
|
2870
|
+
let source;
|
|
2871
|
+
let revision;
|
|
2872
|
+
if (body.wrapdb_version) {
|
|
2873
|
+
const dash = body.wrapdb_version.lastIndexOf("-");
|
|
2874
|
+
version = upstreamVersionOf(
|
|
2875
|
+
dash > 0 ? body.wrapdb_version.slice(0, dash) : body.wrapdb_version,
|
|
2876
|
+
);
|
|
2877
|
+
revision = dash > 0 ? body.wrapdb_version.slice(dash + 1) : undefined;
|
|
2878
|
+
source = "wrapdb_version";
|
|
2879
|
+
}
|
|
2880
|
+
if (!version && sections["wrap-file"]) {
|
|
2881
|
+
version =
|
|
2882
|
+
versionFromArchiveName(body.directory) ||
|
|
2883
|
+
versionFromArchiveName(body.source_filename);
|
|
2884
|
+
source = version ? "directory" : undefined;
|
|
2885
|
+
}
|
|
2886
|
+
if (!version && sections["wrap-git"]) {
|
|
2887
|
+
version = upstreamVersionOf(body.revision);
|
|
2888
|
+
source = version ? "revision" : undefined;
|
|
2889
|
+
}
|
|
2890
|
+
if (!version) {
|
|
2891
|
+
continue;
|
|
2892
|
+
}
|
|
2893
|
+
const wrap = wrapFile.slice(0, -".wrap".length);
|
|
2894
|
+
const entry = { wrap, version, source, ...(revision ? { revision } : {}) };
|
|
2895
|
+
const names = new Set([wrap.toLowerCase()]);
|
|
2896
|
+
const provide = sections.provide || {};
|
|
2897
|
+
for (const [key, value] of Object.entries(provide)) {
|
|
2898
|
+
if (key === "dependency_names" || key === "program_names") {
|
|
2899
|
+
for (const name of value.split(",")) {
|
|
2900
|
+
if (name.trim()) {
|
|
2901
|
+
names.add(name.trim().toLowerCase());
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2904
|
+
} else {
|
|
2905
|
+
names.add(key.toLowerCase());
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
for (const name of names) {
|
|
2909
|
+
if (!provided.has(name)) {
|
|
2910
|
+
provided.set(name, entry);
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
return provided;
|
|
2915
|
+
}
|
|
2916
|
+
|
|
2787
2917
|
/**
|
|
2788
2918
|
* Parse a CMake-like build file (CMakeLists.txt, meson.build, etc.) and extract the
|
|
2789
2919
|
* parent component and list of dependency packages.
|
|
@@ -2801,6 +2931,11 @@ export function cmakeFetchDependencyComponent(dep, srcFile) {
|
|
|
2801
2931
|
*/
|
|
2802
2932
|
export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
2803
2933
|
const rawData = readFileSync(cmakeListFile, { encoding: "utf-8" });
|
|
2934
|
+
// The wraps a meson project ships pin the versions of the subprojects its
|
|
2935
|
+
// dependency() calls fall back to.
|
|
2936
|
+
const mesonWraps = basename(cmakeListFile).startsWith("meson")
|
|
2937
|
+
? readMesonWraps(join(dirname(cmakeListFile), "subprojects"))
|
|
2938
|
+
: new Map();
|
|
2804
2939
|
let cmakeListData = rawData;
|
|
2805
2940
|
const pkgList = [];
|
|
2806
2941
|
const pkgAddedMap = {};
|
|
@@ -2808,6 +2943,25 @@ export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
|
2808
2943
|
const versionsMap = {};
|
|
2809
2944
|
let parentComponent = {};
|
|
2810
2945
|
const templateValues = {};
|
|
2946
|
+
// The parent component is built the same way whether its name and version
|
|
2947
|
+
// came from the parsed project() command or from the line loop below.
|
|
2948
|
+
const toParentComponent = (parentName, parentVersion) => {
|
|
2949
|
+
const component = {
|
|
2950
|
+
group: options.projectGroup || "",
|
|
2951
|
+
name: parentName,
|
|
2952
|
+
version: parentVersion || options.projectVersion || "",
|
|
2953
|
+
type: "application",
|
|
2954
|
+
};
|
|
2955
|
+
component["purl"] = build({
|
|
2956
|
+
type: pkgType,
|
|
2957
|
+
namespace: component.group || null,
|
|
2958
|
+
name: component.name,
|
|
2959
|
+
version: component.version || null,
|
|
2960
|
+
subpath: null,
|
|
2961
|
+
});
|
|
2962
|
+
component["bom-ref"] = decodeURIComponent(component["purl"]);
|
|
2963
|
+
return component;
|
|
2964
|
+
};
|
|
2811
2965
|
// A dependency fetched at configure time is the same package a find_package()
|
|
2812
2966
|
// of the same name asks for: the fetched component, with its source and pin,
|
|
2813
2967
|
// stands for both.
|
|
@@ -2823,6 +2977,38 @@ export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
|
2823
2977
|
fetchedNames.add(dep.name.toLowerCase());
|
|
2824
2978
|
pkgList.push(component);
|
|
2825
2979
|
}
|
|
2980
|
+
// A project() call may span several lines, which the line loop below
|
|
2981
|
+
// cannot rejoin, so the parent name and version are read from the parsed
|
|
2982
|
+
// commands instead. The first argument is the project name and the
|
|
2983
|
+
// VERSION keyword is followed by the version.
|
|
2984
|
+
const commands = parseCmakeCommands(rawData);
|
|
2985
|
+
const variables = cmakeSetVariables(commands);
|
|
2986
|
+
const projectCommand = commands.find(
|
|
2987
|
+
(command) => command.name === "project",
|
|
2988
|
+
);
|
|
2989
|
+
if (projectCommand?.args.length) {
|
|
2990
|
+
const args = projectCommand.args.map((arg) =>
|
|
2991
|
+
expandCmakeVariables(arg, variables),
|
|
2992
|
+
);
|
|
2993
|
+
const parentName = args[0].replace(":", "").trim();
|
|
2994
|
+
let parentVersion;
|
|
2995
|
+
const versionIndex = args.findIndex(
|
|
2996
|
+
(arg) => arg.toLowerCase() === "version",
|
|
2997
|
+
);
|
|
2998
|
+
if (versionIndex > -1 && args.length > versionIndex + 1) {
|
|
2999
|
+
parentVersion = args[versionIndex + 1];
|
|
3000
|
+
}
|
|
3001
|
+
// A `${VAR}` that no `set()` in this file defines stays literal. It is
|
|
3002
|
+
// not a version, and percent-encoding it into a purl publishes the
|
|
3003
|
+
// variable name as the version, so the field is left empty instead.
|
|
3004
|
+
// CMakeCache.txt carries the evaluated value when a build tree exists.
|
|
3005
|
+
if (parentVersion?.includes("$")) {
|
|
3006
|
+
parentVersion = undefined;
|
|
3007
|
+
}
|
|
3008
|
+
if (parentName.length && !parentName.includes("$")) {
|
|
3009
|
+
parentComponent = toParentComponent(parentName, parentVersion);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
2826
3012
|
}
|
|
2827
3013
|
cmakeListData = cmakeListData
|
|
2828
3014
|
.replace(/^ {2}/g, "")
|
|
@@ -2883,23 +3069,7 @@ export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
|
2883
3069
|
parentVersion = undefined;
|
|
2884
3070
|
}
|
|
2885
3071
|
if (parentName?.length && !parentName.includes("$")) {
|
|
2886
|
-
parentComponent =
|
|
2887
|
-
group: options.projectGroup || "",
|
|
2888
|
-
name: parentName,
|
|
2889
|
-
version: parentVersion || options.projectVersion || "",
|
|
2890
|
-
type: "application",
|
|
2891
|
-
};
|
|
2892
|
-
const safePath = path ? path.replace(/^\/+/, "") : null;
|
|
2893
|
-
parentComponent["purl"] = build({
|
|
2894
|
-
type: pkgType,
|
|
2895
|
-
namespace: parentComponent.group || null,
|
|
2896
|
-
name: parentComponent.name,
|
|
2897
|
-
version: parentComponent.version || null,
|
|
2898
|
-
subpath: safePath,
|
|
2899
|
-
});
|
|
2900
|
-
parentComponent["bom-ref"] = decodeURIComponent(
|
|
2901
|
-
parentComponent["purl"],
|
|
2902
|
-
);
|
|
3072
|
+
parentComponent = toParentComponent(parentName, parentVersion);
|
|
2903
3073
|
}
|
|
2904
3074
|
} else if (CMAKE_FIND_COMMANDS.has(command)) {
|
|
2905
3075
|
let tmpB = commandArgs.split(")")[0].split(" ");
|
|
@@ -3041,7 +3211,26 @@ export function parseCmakeLikeFile(cmakeListFile, pkgType, options = {}) {
|
|
|
3041
3211
|
// wrap match has since replaced, so the scraped name is the fallback.
|
|
3042
3212
|
const versionSpecifier =
|
|
3043
3213
|
versionSpecifiersMap[n] || versionSpecifiersMap[scrapedName];
|
|
3044
|
-
|
|
3214
|
+
let version = versionsMap[n] || versionsMap[scrapedName] || "";
|
|
3215
|
+
const localWrap =
|
|
3216
|
+
mesonWraps.get(scrapedName.toLowerCase()) ||
|
|
3217
|
+
mesonWraps.get(n.toLowerCase());
|
|
3218
|
+
if (localWrap && !version) {
|
|
3219
|
+
version = localWrap.version;
|
|
3220
|
+
props.push({ name: "cdx:meson:wrap", value: localWrap.wrap });
|
|
3221
|
+
props.push({
|
|
3222
|
+
name: "cdx:meson:wrap:versionSource",
|
|
3223
|
+
value: localWrap.source,
|
|
3224
|
+
});
|
|
3225
|
+
if (localWrap.revision) {
|
|
3226
|
+
props.push({
|
|
3227
|
+
name: "cdx:meson:wrap:revision",
|
|
3228
|
+
value: localWrap.revision,
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
// A pinned wrap is a stronger identity than a name match alone.
|
|
3232
|
+
confidence = Math.max(confidence, 0.7);
|
|
3233
|
+
}
|
|
3045
3234
|
if (versionSpecifier) {
|
|
3046
3235
|
props.push({
|
|
3047
3236
|
name: "cdx:build:versionSpecifiers",
|
|
@@ -888,6 +888,61 @@ export function enrichFromInstalledMetadata(pkgList, installedMetadata) {
|
|
|
888
888
|
return enriched;
|
|
889
889
|
}
|
|
890
890
|
|
|
891
|
+
// The interpreter cdxgen assumes for a lock file's environment. A marker that
|
|
892
|
+
// tests the implementation against it is decidable without a target machine.
|
|
893
|
+
const IMPLEMENTATION_CLAUSE_RE =
|
|
894
|
+
/^(platform_python_implementation|implementation_name)\s*(==|!=)\s*(['"])([A-Za-z]+)\3$/;
|
|
895
|
+
|
|
896
|
+
function implementationClauseIsFalse(clause) {
|
|
897
|
+
const match = IMPLEMENTATION_CLAUSE_RE.exec(clause.trim());
|
|
898
|
+
if (!match) {
|
|
899
|
+
return false;
|
|
900
|
+
}
|
|
901
|
+
const expected = match[1] === "implementation_name" ? "cpython" : "CPython";
|
|
902
|
+
const equal = match[4] === expected;
|
|
903
|
+
return match[2] === "==" ? !equal : equal;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* Whether an environment marker is false for every CPython interpreter. Only
|
|
908
|
+
* `platform_python_implementation` and `implementation_name` clauses are
|
|
909
|
+
* decided; anything else, or a marker with parentheses, is treated as possibly
|
|
910
|
+
* true, so the answer errs towards keeping a package.
|
|
911
|
+
*
|
|
912
|
+
* @param {string} marker PEP 508 environment marker
|
|
913
|
+
* @returns {boolean} true when no CPython environment satisfies the marker
|
|
914
|
+
*/
|
|
915
|
+
export function markerExcludesCPython(marker) {
|
|
916
|
+
if (typeof marker !== "string" || !marker.trim() || /[()]/.test(marker)) {
|
|
917
|
+
return false;
|
|
918
|
+
}
|
|
919
|
+
return marker
|
|
920
|
+
.split(/\s+or\s+/)
|
|
921
|
+
.every((disjunct) =>
|
|
922
|
+
disjunct.split(/\s+and\s+/).some(implementationClauseIsFalse),
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function recordMarkerEdge(markerEdges, depKey, apkgDep) {
|
|
927
|
+
if (!depKey) {
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
const edge = markerEdges.get(depKey) || {
|
|
931
|
+
total: 0,
|
|
932
|
+
cpythonExcluded: 0,
|
|
933
|
+
markers: new Set(),
|
|
934
|
+
};
|
|
935
|
+
edge.total += 1;
|
|
936
|
+
const marker = typeof apkgDep === "object" ? apkgDep?.marker : undefined;
|
|
937
|
+
if (typeof marker === "string" && marker.trim()) {
|
|
938
|
+
edge.markers.add(marker.trim());
|
|
939
|
+
if (markerExcludesCPython(marker)) {
|
|
940
|
+
edge.cpythonExcluded += 1;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
markerEdges.set(depKey, edge);
|
|
944
|
+
}
|
|
945
|
+
|
|
891
946
|
/**
|
|
892
947
|
* Method to parse python lock files such as poetry.lock, pdm.lock, uv.lock, and pylock.toml.
|
|
893
948
|
*
|
|
@@ -911,6 +966,8 @@ export async function parsePyLockData(
|
|
|
911
966
|
const depsMap = {};
|
|
912
967
|
const existingPkgMap = {};
|
|
913
968
|
const pkgBomRefMap = {};
|
|
969
|
+
// Incoming edges per dependency, with the environment markers they carry.
|
|
970
|
+
const markerEdges = new Map();
|
|
914
971
|
let directDepsKeys = {};
|
|
915
972
|
let groupDepsKeys = {};
|
|
916
973
|
let dependencySourceMap = {};
|
|
@@ -1334,9 +1391,16 @@ export async function parsePyLockData(
|
|
|
1334
1391
|
devDependencies.length ||
|
|
1335
1392
|
optionalDependencies.length
|
|
1336
1393
|
) {
|
|
1337
|
-
|
|
1394
|
+
// A package whose only dependencies are optional (urllib3 has no
|
|
1395
|
+
// required ones, only the `brotli`, `socks` and `zstd` extras) has no
|
|
1396
|
+
// `dependencies` key in uv.lock, but its extras are still edges.
|
|
1397
|
+
if (
|
|
1398
|
+
Array.isArray(apkg.dependencies) ||
|
|
1399
|
+
(!apkg.dependencies &&
|
|
1400
|
+
(devDependencies.length || optionalDependencies.length))
|
|
1401
|
+
) {
|
|
1338
1402
|
// pdm.lock files
|
|
1339
|
-
let allDeps = apkg.dependencies;
|
|
1403
|
+
let allDeps = apkg.dependencies || [];
|
|
1340
1404
|
allDeps = allDeps.concat(devDependencies);
|
|
1341
1405
|
allDeps = allDeps.concat(optionalDependencies);
|
|
1342
1406
|
for (const apkgDep of allDeps) {
|
|
@@ -1348,6 +1412,7 @@ export async function parsePyLockData(
|
|
|
1348
1412
|
const depPkgRef =
|
|
1349
1413
|
existingPkgMap[nameLower] || existingPkgMap[nameStr];
|
|
1350
1414
|
depsMap[pkg["bom-ref"]].add(depPkgRef || nameStr);
|
|
1415
|
+
recordMarkerEdge(markerEdges, depPkgRef || nameLower, apkgDep);
|
|
1351
1416
|
// Propagate the workspace properties to the child components
|
|
1352
1417
|
if (depPkgRef && pkgBomRefMap[depPkgRef]) {
|
|
1353
1418
|
const dependentPkg = pkgBomRefMap[depPkgRef];
|
|
@@ -1421,6 +1486,28 @@ export async function parsePyLockData(
|
|
|
1421
1486
|
}
|
|
1422
1487
|
}
|
|
1423
1488
|
}
|
|
1489
|
+
// A package reached only through edges whose environment marker excludes
|
|
1490
|
+
// CPython (brotlicffi is `platform_python_implementation != 'CPython'`) is
|
|
1491
|
+
// not installed by a CPython environment built from this lock, so it is
|
|
1492
|
+
// optional rather than part of the CPython closure. A direct dependency of
|
|
1493
|
+
// the project is left alone: the manifest asked for it.
|
|
1494
|
+
for (const [depRef, edge] of markerEdges) {
|
|
1495
|
+
const dependentPkg = pkgBomRefMap[depRef];
|
|
1496
|
+
if (
|
|
1497
|
+
!dependentPkg ||
|
|
1498
|
+
!edge.total ||
|
|
1499
|
+
edge.cpythonExcluded !== edge.total ||
|
|
1500
|
+
directDepsKeys[normalizePythonDependencyKey(dependentPkg.name)]
|
|
1501
|
+
) {
|
|
1502
|
+
continue;
|
|
1503
|
+
}
|
|
1504
|
+
dependentPkg.scope = "optional";
|
|
1505
|
+
dependentPkg.properties = dependentPkg.properties || [];
|
|
1506
|
+
dependentPkg.properties.push({
|
|
1507
|
+
name: "cdx:pip:markers",
|
|
1508
|
+
value: Array.from(edge.markers).sort().join(" or "),
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1424
1511
|
// Seed the parent component's first-level edges from the manifest. Lock
|
|
1425
1512
|
// files describe the resolved closure but do not say which of the locked
|
|
1426
1513
|
// packages the root project directly depends on; parsePyProjectTomlFile
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import process from "node:process";
|
|
12
12
|
|
|
13
13
|
import { build } from "@cdxgen/cdx-purl";
|
|
14
|
+
import { coerce, gte } from "semver";
|
|
14
15
|
|
|
15
16
|
import {
|
|
16
17
|
DEBUG_MODE,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
PYPI_MODULE_PACKAGE_MAPPING,
|
|
26
27
|
PYTHON_STD_MODULES,
|
|
27
28
|
} from "../core/state.js";
|
|
29
|
+
import { parseToml } from "../core/toml.js";
|
|
28
30
|
import { findAppModules } from "../inventory/atomUtils.js";
|
|
29
31
|
import { flattenDeps } from "../inventory/deps.js";
|
|
30
32
|
import { pypiBomRef } from "../inventory/purl.js";
|
|
@@ -612,6 +614,128 @@ export function createUVLock(basePath, options) {
|
|
|
612
614
|
}
|
|
613
615
|
}
|
|
614
616
|
|
|
617
|
+
const CONSTRAINT_OPERATORS = [">=", "<=", "==", "!=", "~=", ">", "<", "^", "="];
|
|
618
|
+
const LOWER_BOUND_OPERATORS = new Set([">=", ">", "==", "~=", "^", "="]);
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Check whether a single version constraint puts the lower bound at version 2
|
|
622
|
+
* or higher.
|
|
623
|
+
*
|
|
624
|
+
* @param {string} constraint Constraint such as ">=2.0.0" or "<3.0.0"
|
|
625
|
+
* @returns {boolean} true when the constraint requires version 2 or newer
|
|
626
|
+
*/
|
|
627
|
+
function requiresVersion2(constraint) {
|
|
628
|
+
let operator = "";
|
|
629
|
+
let version = constraint.trim();
|
|
630
|
+
for (const candidate of CONSTRAINT_OPERATORS) {
|
|
631
|
+
if (version.startsWith(candidate)) {
|
|
632
|
+
operator = candidate;
|
|
633
|
+
version = version.slice(candidate.length).trim();
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (!operator || !LOWER_BOUND_OPERATORS.has(operator)) {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
const coerced = coerce(version);
|
|
641
|
+
return coerced !== null && gte(coerced, "2.0.0");
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Check whether a build requirement names poetry-core with a lower bound of
|
|
646
|
+
* version 2 or higher.
|
|
647
|
+
*
|
|
648
|
+
* @param {string} requirement Build requirement such as "poetry-core>=2.0.0"
|
|
649
|
+
* @returns {boolean} true when poetry-core 2 or newer is required
|
|
650
|
+
*/
|
|
651
|
+
function requiresPoetryCore2(requirement) {
|
|
652
|
+
if (typeof requirement !== "string") {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
const entry = requirement.trim().toLowerCase();
|
|
656
|
+
if (!entry.startsWith("poetry-core")) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
let spec = entry.slice("poetry-core".length);
|
|
660
|
+
if (spec.startsWith("[")) {
|
|
661
|
+
const extrasEnd = spec.indexOf("]");
|
|
662
|
+
if (extrasEnd === -1) {
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
spec = spec.slice(extrasEnd + 1);
|
|
666
|
+
}
|
|
667
|
+
const next = spec.charAt(0);
|
|
668
|
+
if (next && next !== " " && next !== "\t" && !"><=!~^".includes(next)) {
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
return spec
|
|
672
|
+
.trim()
|
|
673
|
+
.split(",")
|
|
674
|
+
.some((constraint) => requiresVersion2(constraint));
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Check whether a pyproject.toml requires poetry-core 2 or newer to build.
|
|
679
|
+
*
|
|
680
|
+
* @param {string} pyprojectFile Path to pyproject.toml
|
|
681
|
+
* @returns {boolean} true when the project needs Poetry 2 or newer
|
|
682
|
+
*/
|
|
683
|
+
export function isPoetry2Project(pyprojectFile) {
|
|
684
|
+
if (!pyprojectFile || !safeExistsSync(pyprojectFile)) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
let config;
|
|
688
|
+
try {
|
|
689
|
+
config = parseToml(readFileSync(pyprojectFile, "utf-8"));
|
|
690
|
+
} catch (_err) {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
const requires = config?.["build-system"]?.requires;
|
|
694
|
+
if (!Array.isArray(requires)) {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
return requires.some((requirement) => requiresPoetryCore2(requirement));
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Find the pyproject.toml belonging to a poetry trigger file.
|
|
702
|
+
*
|
|
703
|
+
* @param {string} triggerFile pyproject.toml or poetry.lock path
|
|
704
|
+
* @returns {string|null} Path to pyproject.toml, or null when there is none
|
|
705
|
+
*/
|
|
706
|
+
function findPoetryPyprojectFile(triggerFile) {
|
|
707
|
+
if (!triggerFile) {
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
if (triggerFile.endsWith("pyproject.toml")) {
|
|
711
|
+
return triggerFile;
|
|
712
|
+
}
|
|
713
|
+
if (triggerFile.endsWith("poetry.lock")) {
|
|
714
|
+
const sibling = join(dirname(triggerFile), "pyproject.toml");
|
|
715
|
+
return safeExistsSync(sibling) ? sibling : null;
|
|
716
|
+
}
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Build the poetry install arguments for a poetry project.
|
|
722
|
+
*
|
|
723
|
+
* Poetry 2 installs only the main dependency group and no extras by default,
|
|
724
|
+
* so projects requiring poetry-core 2 or newer also get --all-groups and
|
|
725
|
+
* --all-extras. Callers prepend the python invocation such as -m poetry.
|
|
726
|
+
*
|
|
727
|
+
* @param {string} pyprojectFile pyproject.toml or poetry.lock path of the project
|
|
728
|
+
* @returns {string[]} poetry install arguments
|
|
729
|
+
*/
|
|
730
|
+
export function getPoetryInstallArgs(pyprojectFile) {
|
|
731
|
+
const args = ["install", "-n", "--no-root"];
|
|
732
|
+
const resolvedFile = findPoetryPyprojectFile(pyprojectFile);
|
|
733
|
+
if (resolvedFile && isPoetry2Project(resolvedFile)) {
|
|
734
|
+
args.push("--all-groups", "--all-extras");
|
|
735
|
+
}
|
|
736
|
+
return args;
|
|
737
|
+
}
|
|
738
|
+
|
|
615
739
|
/**
|
|
616
740
|
* Execute pip freeze by creating a virtual env in a temp directory and construct the dependency tree
|
|
617
741
|
*
|
|
@@ -742,7 +866,9 @@ export async function getPipFrozenTree(
|
|
|
742
866
|
shell: isWin,
|
|
743
867
|
});
|
|
744
868
|
thoughtLog("Performing poetry install");
|
|
745
|
-
let poetryInstallArgs = ["-m", "poetry"
|
|
869
|
+
let poetryInstallArgs = ["-m", "poetry"].concat(
|
|
870
|
+
getPoetryInstallArgs(reqOrSetupFile),
|
|
871
|
+
);
|
|
746
872
|
if (isSecureMode) {
|
|
747
873
|
poetryInstallArgs.unshift("-S");
|
|
748
874
|
}
|
|
@@ -756,7 +882,7 @@ export async function getPipFrozenTree(
|
|
|
756
882
|
thoughtLog(
|
|
757
883
|
"Hmm, poetry doesn't seem to be available as a module. Perhaps it was installed directly 🤔?",
|
|
758
884
|
);
|
|
759
|
-
poetryInstallArgs =
|
|
885
|
+
poetryInstallArgs = getPoetryInstallArgs(reqOrSetupFile);
|
|
760
886
|
// Attempt to perform poetry install
|
|
761
887
|
result = safeSpawnSync("poetry", poetryInstallArgs, {
|
|
762
888
|
cwd: basePath,
|