Skip to content

Commit

Permalink
Refactor storing of package dependencies
Browse files Browse the repository at this point in the history
All dependencies for a package are indices into the `packages` list now.
This sets the correct association between a dependency & its associated
package.

* remove `SbomDependency` struct
  • Loading branch information
justahero committed Aug 5, 2024
1 parent 0c7f60f commit 496d62e
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 115 deletions.
113 changes: 49 additions & 64 deletions src/cargo/core/compiler/output_sbom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,36 +39,6 @@ impl<const V: u32> Serialize for SbomFormatVersion<V> {
}
}

/// A package dependency
#[derive(Serialize, Clone, Debug)]
struct SbomDependency {
name: String,
package_id: PackageIdSpec,
version: Option<Version>,
features: Vec<String>,
}

impl From<&UnitDep> for SbomDependency {
fn from(dep: &UnitDep) -> Self {
let package_id = dep.unit.pkg.package_id().to_spec();
let name = package_id.name().to_string();
let version = package_id.version();
let features = dep
.unit
.features
.iter()
.map(|f| f.to_string())
.collect_vec();

Self {
name,
package_id,
version,
features,
}
}
}

/// A profile can be overriden for individual packages.
///
/// This wraps a [`Profile`] object.
Expand Down Expand Up @@ -129,25 +99,27 @@ struct SbomPackage {
features: Vec<String>,
build_type: SbomBuildType,
extern_crate_name: String,
dependencies: Vec<SbomDependency>,
/// Indices into the `packages` array
dependencies: Vec<usize>,
}

impl SbomPackage {
pub fn new(
dep: &UnitDep,
dependencies: Vec<SbomDependency>,
build_type: SbomBuildType,
root_profile: &Profile,
) -> Self {
let package_id = dep.unit.pkg.package_id().to_spec();
pub fn new(unit_dep: &UnitDep, root_profile: &Profile) -> Self {
let package_id = unit_dep.unit.pkg.package_id().to_spec();
let package = package_id.name().to_string();
let profile = if &dep.unit.profile != root_profile {
Some((&dep.unit.profile).into())
let profile = if &unit_dep.unit.profile != root_profile {
Some((&unit_dep.unit.profile).into())
} else {
None
};
let build_type = if unit_dep.unit.mode.is_run_custom_build() {
SbomBuildType::Build
} else {
SbomBuildType::Normal
};

let version = package_id.version();
let features = dep
let features = unit_dep
.unit
.features
.iter()
Expand All @@ -161,8 +133,8 @@ impl SbomPackage {
version,
features,
build_type,
extern_crate_name: dep.extern_crate_name.to_string(),
dependencies,
extern_crate_name: unit_dep.extern_crate_name.to_string(),
dependencies: Vec::new(),
}
}
}
Expand Down Expand Up @@ -267,41 +239,54 @@ pub fn output_sbom(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> Cargo
}

/// Fetch all dependencies, including transitive ones. A dependency can also appear multiple times
/// if it's included with different versions.
/// if it's using different settings, e.g. profile, features or crate versions.
///
/// A package's `dependencies` is a list of indices into the `packages` array.
fn collect_packages(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> Vec<SbomPackage> {
let unit_graph = &build_runner.bcx.unit_graph;
let root_deps = build_runner.unit_deps(unit);
let root_profile = &unit.profile;

let mut result = Vec::new();
let mut packages = Vec::new();
let mut queue: BTreeSet<&UnitDep> = root_deps.iter().collect();
let mut visited = BTreeSet::new();
let mut visited_dependencies = BTreeSet::new();

while let Some(package) = queue.pop_first() {
if visited.contains(package) {
// each package should appear only once in the `packages` block
while let Some(dependency) = queue.pop_first() {
if visited_dependencies.contains(dependency) {
continue;
}

let build_type = if package.unit.mode.is_run_custom_build() {
SbomBuildType::Build
} else {
SbomBuildType::Normal
};
let mut dependencies: BTreeSet<&UnitDep> = unit_graph[&dependency.unit].iter().collect();

let mut dependencies: BTreeSet<&UnitDep> = unit_graph[&package.unit].iter().collect();
let sbom_dependencies = dependencies.iter().map(|dep| (*dep).into()).collect_vec();
// each seen package is returned
packages.push(SbomPackage::new(dependency, root_profile));

result.push(SbomPackage::new(
package,
sbom_dependencies,
build_type,
root_profile,
));
// append dependencies to back of queue
queue.append(&mut dependencies);
visited_dependencies.insert(dependency);
}

visited.insert(package);
// Collect all indices for each package's dependencies
//
// At this point `visited_packages` contains the [`UnitDep`] the `packages` Vec is generated with.
// They have the same number of objects in the same order, therefore using an index is fine.
for (index, package) in visited_dependencies.iter().enumerate() {
let dependencies: BTreeSet<&UnitDep> = unit_graph[&package.unit].iter().collect();

queue.append(&mut dependencies);
let mut indices = dependencies
.iter()
.filter_map(|dep| {
visited_dependencies
.iter()
.position(|unit_dep| dep == unit_dep)
})
.collect::<Vec<_>>();

if let Some(package) = packages.get_mut(index) {
package.dependencies.append(&mut indices);
}
}

result
packages
}
58 changes: 7 additions & 51 deletions tests/testsuite/sbom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,14 +353,7 @@ fn build_sbom_with_simple_build_script() {
"packages": [
{
"build_type": "build",
"dependencies": [
{
"features": [],
"name": "foo",
"package_id": "path+file://[..]/foo#0.0.1",
"version": "0.0.1"
}
],
"dependencies": [],
"extern_crate_name": "build_script_build",
"features": [],
"package": "foo",
Expand Down Expand Up @@ -388,7 +381,7 @@ fn build_sbom_with_simple_build_script() {
"features": [],
"build_type": "normal",
"extern_crate_name": "build_script_build",
"dependencies": [],
"dependencies": [0],
"profile": {
"codegen_backend": null,
"codegen_units": null,
Expand Down Expand Up @@ -507,14 +500,7 @@ fn build_sbom_with_build_dependencies() {
"features": [],
"build_type": "normal",
"extern_crate_name": "bar",
"dependencies": [
{
"name": "bar",
"package_id": "registry+[..]#bar@0.1.0",
"version": "0.1.0",
"features": []
}
]
"dependencies": [2]
},
{
"package_id": "registry+[..]#bar@0.1.0",
Expand All @@ -523,14 +509,7 @@ fn build_sbom_with_build_dependencies() {
"features": [],
"build_type": "build",
"extern_crate_name": "build_script_build",
"dependencies": [
{
"name": "bar",
"package_id": "registry+[..]#bar@0.1.0",
"version": "0.1.0",
"features": []
}
],
"dependencies": [3],
"profile": {
"codegen_backend": null,
"codegen_units": null,
Expand All @@ -553,14 +532,7 @@ fn build_sbom_with_build_dependencies() {
"features": [],
"build_type": "normal",
"extern_crate_name": "build_script_build",
"dependencies": [
{
"name": "baz",
"package_id": "registry+[..]#baz@0.1.0",
"version": "0.1.0",
"features": []
}
],
"dependencies": [1],
"profile": {
"codegen_backend": null,
"codegen_units": null,
Expand Down Expand Up @@ -699,14 +671,7 @@ fn build_sbom_crate_uses_different_features_for_build_and_normal_dependencies()
"packages": [
{
"build_type": "build",
"dependencies": [
{
"features": [],
"name": "a",
"package_id": "path+file:///[..]#a@0.1.0",
"version": "0.1.0"
}
],
"dependencies": [2],
"extern_crate_name": "build_script_build",
"features": [],
"package": "a",
Expand All @@ -729,16 +694,7 @@ fn build_sbom_crate_uses_different_features_for_build_and_normal_dependencies()
},
{
"build_type": "normal",
"dependencies": [
{
"features": [
"f2"
],
"name": "b",
"package_id": "path+file:///[..]b#0.0.1",
"version": "0.0.1"
}
],
"dependencies": [0],
"extern_crate_name": "build_script_build",
"features": [],
"package": "a",
Expand Down

0 comments on commit 496d62e

Please sign in to comment.