cargo/compiler/
output_sbom.rs1use cargo_util::collections::{HashMap, HashSet};
5use std::collections::{BTreeMap, BTreeSet};
6
7use cargo_util_schemas::sbom::{
8 Sbom, SbomCrate, SbomDependency, SbomDependencyType, SbomFormatVersion, SbomIndex, SbomRustc,
9 SbomTargetKind,
10};
11use itertools::Itertools;
12
13use crate::CargoResult;
14use crate::util::Rustc;
15use crate::workspace::TargetKind;
16
17use super::{BuildRunner, CompileMode, Unit};
18
19impl From<TargetKind> for SbomTargetKind {
20 fn from(t: TargetKind) -> Self {
21 use TargetKind::*;
22 match t {
23 Lib(c) => SbomTargetKind::Lib(c),
24 Bin => SbomTargetKind::Bin,
25 Test => SbomTargetKind::Test,
26 Bench => SbomTargetKind::Bench,
27 ExampleBin | ExampleLib(_) => SbomTargetKind::Example,
28 CustomBuild => SbomTargetKind::CustomBuild,
29 }
30 }
31}
32
33impl From<&Rustc> for SbomRustc {
34 fn from(rustc: &Rustc) -> Self {
35 Self {
36 version: rustc.version.to_string(),
37 wrapper: rustc.wrapper.clone(),
38 workspace_wrapper: rustc.workspace_wrapper.clone(),
39 commit_hash: rustc.commit_hash.clone(),
40 host: rustc.host.to_string(),
41 verbose_version: rustc.verbose_version.clone(),
42 }
43 }
44}
45
46#[tracing::instrument(skip_all)]
48pub fn build_sbom(build_runner: &BuildRunner<'_, '_>, root: &Unit) -> CargoResult<Sbom> {
49 let bcx = build_runner.bcx;
50 let rustc: SbomRustc = bcx.rustc().into();
51
52 let mut crates = Vec::new();
53 let sbom_graph = build_sbom_graph(build_runner, root);
54
55 let indices: HashMap<&Unit, SbomIndex> = sbom_graph
57 .keys()
58 .enumerate()
59 .map(|(i, dep)| (*dep, SbomIndex(i)))
60 .collect();
61
62 for (unit, edges) in sbom_graph {
64 let package_id = unit.pkg.package_id().to_spec();
65 let features = unit.features.iter().map(|f| f.to_string()).collect_vec();
66 let target_kind = unit.target.kind().clone();
67
68 let mut krate = SbomCrate::new(package_id, features, SbomTargetKind::from(target_kind));
69 for (dep, kind) in edges {
70 krate.dependencies.push(SbomDependency {
71 index: indices[dep],
72 kind: kind,
73 });
74 }
75 crates.push(krate);
76 }
77 let target = match root.kind {
78 super::CompileKind::Host => build_runner.bcx.host_triple(),
79 super::CompileKind::Target(target) => target.rustc_target(),
80 };
81 Ok(Sbom {
82 version: SbomFormatVersion(1),
83 crates,
84 root: indices[root],
85 rustc,
86 target: target.to_string(),
87 })
88}
89
90#[tracing::instrument(skip_all)]
95fn build_sbom_graph<'a>(
96 build_runner: &'a BuildRunner<'_, '_>,
97 root: &'a Unit,
98) -> BTreeMap<&'a Unit, BTreeSet<(&'a Unit, SbomDependencyType)>> {
99 tracing::trace!("building sbom graph for {}", root.pkg.package_id());
100
101 let mut queue = Vec::new();
102 let mut sbom_graph: BTreeMap<&Unit, BTreeSet<(&Unit, SbomDependencyType)>> = BTreeMap::new();
103 let mut visited = HashSet::default();
104
105 queue.push((root, root, false));
107 while let Some((node, parent, is_build_dep)) = queue.pop() {
108 let dependencies = sbom_graph.entry(parent).or_default();
109 for dep in build_runner.unit_deps(node) {
110 let dep = &dep.unit;
111 let (next_parent, next_is_build_dep) = if dep.mode == CompileMode::RunCustomBuild {
112 (parent, true)
114 } else {
115 let dep_type = match is_build_dep || dep.target.proc_macro() {
117 false => SbomDependencyType::Normal,
118 true => SbomDependencyType::Build,
119 };
120 dependencies.insert((dep, dep_type));
121 (dep, false)
122 };
123 if visited.insert(dep) {
124 queue.push((dep, next_parent, next_is_build_dep));
125 }
126 }
127 }
128 sbom_graph
129}