Skip to content

Commit

Permalink
Poetry root list from pyproject.toml (#1386)
Browse files Browse the repository at this point in the history
* Poetry root list from pyproject.toml

Signed-off-by: Prabhu Subramanian <[email protected]>

* Fixes 1385

Signed-off-by: Prabhu Subramanian <[email protected]>

* Fixes 1385

Signed-off-by: Prabhu Subramanian <[email protected]>

* Fix test

Signed-off-by: Prabhu Subramanian <[email protected]>

* Handle duplicate components in lock files

Signed-off-by: Prabhu Subramanian <[email protected]>

---------

Signed-off-by: Prabhu Subramanian <[email protected]>
  • Loading branch information
prabhu authored Sep 22, 2024
1 parent 9e36e8e commit 5385422
Show file tree
Hide file tree
Showing 53 changed files with 99 additions and 39 deletions.
20 changes: 11 additions & 9 deletions lib/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2876,16 +2876,18 @@ export async function createPythonBom(path, options) {
);
}
}
const parentDependsOn = [];
// Complete the dependency tree by making parent component depend on the first level
for (const p of retMap.rootList) {
parentDependsOn.push(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
if (retMap.rootList) {
const parentDependsOn = [];
// Complete the dependency tree by making parent component depend on the first level
for (const p of retMap.rootList) {
parentDependsOn.push(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
}
const pdependencies = {
ref: parentComponent["bom-ref"],
dependsOn: parentDependsOn,
};
dependencies.splice(0, 0, pdependencies);
}
const pdependencies = {
ref: parentComponent["bom-ref"],
dependsOn: parentDependsOn,
};
dependencies.splice(0, 0, pdependencies);
}
options.parentComponent = parentComponent;
} // poetryMode
Expand Down
69 changes: 61 additions & 8 deletions lib/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3944,17 +3944,57 @@ export function parsePyProjectToml(tomlFile) {
*
* @param {Object} lockData JSON data from poetry.lock
* @param {string} lockFile Lock file name for evidence
* @param {string} pyProjectFile pyproject.toml file
*/
export async function parsePoetrylockData(lockData, lockFile) {
export async function parsePoetrylockData(lockData, lockFile, pyProjectFile) {
let pkgList = [];
const rootList = [];
const dependenciesList = [];
const depsMap = {};
const existingPkgMap = {};
const directDepsKeys = {};
const groupDepsKeys = {};
let pkg = null;
let lastPkgRef = undefined;
let depsMode = false;
if (!lockData) {
return pkgList;
return { pkgList, dependenciesList };
}
if (!pyProjectFile && lockFile) {
pyProjectFile = lockFile.replace("poetry.lock", "pyproject.toml");
if (existsSync(pyProjectFile)) {
const pyprojTomlFile = readFileSync(pyProjectFile, { encoding: "utf-8" });
const tomlData = toml.parse(pyprojTomlFile);
if (tomlData?.tool?.poetry) {
for (const adep of Object.keys(tomlData?.tool?.poetry?.dependencies)) {
if (
![
"python",
"py",
"pytest",
"pylint",
"ruff",
"setuptools",
"bandit",
].includes(adep)
) {
directDepsKeys[adep] = true;
}
} // for
if (tomlData?.tool?.poetry?.group) {
for (const agroup of Object.keys(tomlData.tool.poetry.group)) {
for (const adep of Object.keys(
tomlData.tool.poetry.group[agroup]?.dependencies,
)) {
if (!groupDepsKeys[adep]) {
groupDepsKeys[adep] = [];
}
groupDepsKeys[adep].push(agroup);
}
} // for
}
}
}
}
lockData.split("\n").forEach((l) => {
l = l.replace("\r", "");
Expand Down Expand Up @@ -3995,11 +4035,18 @@ export async function parsePoetrylockData(lockData, lockFile) {
],
},
};
// This would help look
if (!existingPkgMap[pkg.name.toLowerCase()]) {
existingPkgMap[pkg.name.toLowerCase()] = pkg["bom-ref"];
pkgList.push(pkg);
if (groupDepsKeys[pkg.name]) {
pkg.scope = "optional";
pkg.properties = groupDepsKeys[pkg.name].map((g) => {
return { name: "cdx:poetry:group", value: g };
});
}
if (directDepsKeys[pkg.name]) {
rootList.push(pkg);
}
// This would help the lookup
existingPkgMap[pkg.name.toLowerCase()] = pkg["bom-ref"];
pkgList.push(pkg);
lastPkgRef = pkg["bom-ref"];
if (!depsMap[lastPkgRef]) {
depsMap[lastPkgRef] = new Set();
Expand Down Expand Up @@ -4057,7 +4104,7 @@ export async function parsePoetrylockData(lockData, lockFile) {
}
return {
pkgList,
rootList: pkgList,
rootList,
dependenciesList,
};
}
Expand Down Expand Up @@ -12469,6 +12516,9 @@ export function isValidIriReference(iri) {
* @returns {Boolean} True if the dependency tree lacks any non-root parents without children. False otherwise.
*/
export function isPartialTree(dependencies, componentsCount = 1) {
if (componentsCount <= 1) {
return false;
}
if (dependencies?.length <= 1) {
return true;
}
Expand All @@ -12478,7 +12528,10 @@ export function isPartialTree(dependencies, componentsCount = 1) {
parentsWithChildsCount++;
}
}
return parentsWithChildsCount <= Math.max(Math.round(componentsCount / 3), 1);
return (
parentsWithChildsCount <
Math.min(Math.round(componentsCount / 3), componentsCount)
);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions lib/helpers/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3500,7 +3500,7 @@ test("parseYarnLock", async () => {
expect(parsedList.dependenciesList.length).toEqual(1);
expect(
isPartialTree(parsedList.dependenciesList, parsedList.pkgList.length),
).toBeTruthy();
).toBeFalsy();
parsedList = await parseYarnLock("./test/data/yarn_locks/yarn-at.lock");
expect(parsedList.pkgList.length).toEqual(4);
expect(parsedList.dependenciesList.length).toEqual(4);
Expand Down Expand Up @@ -3993,7 +3993,7 @@ test("parse poetry.lock", async () => {
readFileSync("./test/data/pdm.lock", { encoding: "utf-8" }),
"./test/data/pdm.lock",
);
expect(retMap.pkgList.length).toEqual(37);
expect(retMap.pkgList.length).toEqual(39);
expect(retMap.dependenciesList.length).toEqual(37);
}, 120000);

Expand Down
2 changes: 1 addition & 1 deletion lib/helpers/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export const validateRefs = (bomJson) => {
if (bomJson?.dependencies) {
if (isPartialTree(bomJson.dependencies, bomJson?.components?.length)) {
warningsList.push(
"Dependency tree is partial with multiple empty dependsOn attribute.",
"Dependency tree is partial with multiple empty dependsOn attributes.",
);
}
for (const dep of bomJson.dependencies) {
Expand Down
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"include": ["*.js"],
"exclude": ["*.test.js", "./types/**/*"],
"include": ["*.js", "lib/**/*.js"],
"exclude": ["*.test.js", "lib/**/*.test.js", "./types/**/*"],
"compilerOptions": {
"allowJs": true,
// Generate d.ts files
Expand Down
1 change: 0 additions & 1 deletion types/analyzer.d.ts.map

This file was deleted.

1 change: 0 additions & 1 deletion types/binary.d.ts.map

This file was deleted.

1 change: 0 additions & 1 deletion types/cbomutils.d.ts.map

This file was deleted.

1 change: 0 additions & 1 deletion types/db.d.ts.map

This file was deleted.

1 change: 0 additions & 1 deletion types/display.d.ts.map

This file was deleted.

1 change: 0 additions & 1 deletion types/docker.d.ts.map

This file was deleted.

1 change: 0 additions & 1 deletion types/envcontext.d.ts.map

This file was deleted.

1 change: 0 additions & 1 deletion types/evinser.d.ts.map

This file was deleted.

1 change: 0 additions & 1 deletion types/index.d.ts.map

This file was deleted.

File renamed without changes.
1 change: 1 addition & 0 deletions types/lib/cli/index.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
1 change: 1 addition & 0 deletions types/lib/evinser/evinser.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
1 change: 1 addition & 0 deletions types/lib/helpers/analyzer.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
1 change: 1 addition & 0 deletions types/lib/helpers/cbomutils.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
1 change: 1 addition & 0 deletions types/lib/helpers/db.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
1 change: 1 addition & 0 deletions types/lib/helpers/display.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
1 change: 1 addition & 0 deletions types/lib/helpers/envcontext.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
1 change: 1 addition & 0 deletions types/lib/helpers/protobom.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion types/utils.d.ts → types/lib/helpers/utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,8 +408,13 @@ export function parsePyProjectToml(tomlFile: string): {};
*
* @param {Object} lockData JSON data from poetry.lock
* @param {string} lockFile Lock file name for evidence
* @param {string} pyProjectFile pyproject.toml file
*/
export function parsePoetrylockData(lockData: any, lockFile: string): Promise<any[] | {
export function parsePoetrylockData(lockData: any, lockFile: string, pyProjectFile: string): Promise<{
pkgList: any[];
dependenciesList: any[];
rootList?: undefined;
} | {
pkgList: any[];
rootList: any[];
dependenciesList: {
Expand Down
Loading

0 comments on commit 5385422

Please sign in to comment.