Fix missing dependencies on synthetic root in SBOM export (#17363)

This commit is contained in:
Tom Schafer
2026-01-08 18:19:34 +00:00
committed by GitHub
parent 6d9c8f853f
commit 29285db48e
2 changed files with 104 additions and 7 deletions

View File

@@ -349,7 +349,7 @@ pub fn from_lock<'lock>(
let mut dependencies = create_dependencies(&nodes, &component_builder);
// With `--all-packages`, use synthetic root which depends on workspace root and all workspace members.
// With `--all-packages`, use synthetic root which depends on root and all workspace members.
// This ensures that we don't have any dangling components resulting from workspace packages not depended on by the workspace root.
if all_packages {
let synthetic_root = component_builder.create_synthetic_root_component(root);
@@ -357,19 +357,29 @@ pub fn from_lock<'lock>(
.bom_ref
.clone()
.expect("bom-ref should always exist");
let workspace_root = metadata.component.replace(synthetic_root);
let root = metadata.component.replace(synthetic_root);
if let Some(workspace_root) = workspace_root {
let mut synthetic_root_deps = workspace_member_ids
.iter()
.filter_map(|c| component_builder.get_component(c))
.map(|c| c.bom_ref.clone().expect("bom-ref should always exist"))
.collect::<Vec<_>>();
if let Some(ref root_component) = root
&& let Some(ref root_bom_ref) = root_component.bom_ref
{
synthetic_root_deps.push(root_bom_ref.clone());
}
if let Some(workspace_root) = root {
components.push(workspace_root);
}
dependencies.push(Dependency {
dependency_ref: synthetic_root_bom_ref,
dependencies: workspace_member_ids
.iter()
.filter_map(|c| component_builder.get_component(c))
.map(|c| c.bom_ref.clone().expect("bom-ref should always exist"))
dependencies: synthetic_root_deps
.into_iter()
.sorted_unstable()
.unique()
.collect(),
});
}

View File

@@ -6257,6 +6257,93 @@ fn cyclonedx_export_workspace_all_packages() -> Result<()> {
Ok(())
}
#[test]
fn cyclonedx_export_all_packages_non_workspace_root_dependency() -> Result<()> {
let context = TestContext::new("3.12").with_cyclonedx_filters();
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["urllib3==2.2.0"]
[build-system]
requires = ["setuptools>=42"]
build-backend = "setuptools.build_meta"
"#,
)?;
context.lock().assert().success();
uv_snapshot!(context.filters(), context.export().arg("--format").arg("cyclonedx1.5").arg("--all-packages"), @r#"
success: true
exit_code: 0
----- stdout -----
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"serialNumber": "[SERIAL_NUMBER]",
"metadata": {
"timestamp": "[TIMESTAMP]",
"tools": [
{
"vendor": "Astral Software Inc.",
"name": "uv",
"version": "[VERSION]"
}
],
"component": {
"type": "library",
"bom-ref": "my-project-3",
"name": "my-project"
}
},
"components": [
{
"type": "library",
"bom-ref": "urllib3-2@2.2.0",
"name": "urllib3",
"version": "2.2.0",
"purl": "pkg:pypi/urllib3@2.2.0"
},
{
"type": "library",
"bom-ref": "my-project-1@0.1.0",
"name": "my-project",
"version": "0.1.0"
}
],
"dependencies": [
{
"ref": "my-project-1@0.1.0",
"dependsOn": [
"urllib3-2@2.2.0"
]
},
{
"ref": "urllib3-2@2.2.0",
"dependsOn": []
},
{
"ref": "my-project-3",
"dependsOn": [
"my-project-1@0.1.0"
]
}
]
}
----- stderr -----
Resolved 2 packages in [TIME]
warning: `uv export --format=cyclonedx1.5` is experimental and may change without warning. Pass `--preview-features sbom-export` to disable this warning.
"#);
Ok(())
}
// Contains a combination of combination of workspace and registry deps, with another workspace dep not depended on by the root
#[test]
fn cyclonedx_export_workspace_mixed_dependencies() -> Result<()> {