1use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
5use std::path::PathBuf;
6
7use cargo_util_schemas::core::PackageIdSpec;
8use itertools::Itertools;
9use serde::Serialize;
10
11use crate::core::TargetKind;
12use crate::util::interning::InternedString;
13use crate::util::Rustc;
14use crate::CargoResult;
15
16use super::{BuildRunner, CompileMode, Unit};
17
18#[derive(Serialize, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
20pub struct SbomFormatVersion(u32);
21
22#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Serialize)]
23#[serde(rename_all = "snake_case")]
24enum SbomDependencyType {
25 Normal,
27 Build,
30}
31
32#[derive(Serialize, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
33struct SbomIndex(usize);
34
35#[derive(Serialize, Clone, Debug)]
36#[serde(rename_all = "snake_case")]
37struct SbomDependency {
38 index: SbomIndex,
39 kind: SbomDependencyType,
40}
41
42#[derive(Serialize, Clone, Debug)]
43#[serde(rename_all = "snake_case")]
44struct SbomCrate {
45 id: PackageIdSpec,
46 features: Vec<String>,
47 dependencies: Vec<SbomDependency>,
48 kind: TargetKind,
49}
50
51impl SbomCrate {
52 pub fn new(unit: &Unit) -> Self {
53 let package_id = unit.pkg.package_id().to_spec();
54 let features = unit.features.iter().map(|f| f.to_string()).collect_vec();
55 Self {
56 id: package_id,
57 features,
58 dependencies: Vec::new(),
59 kind: unit.target.kind().clone(),
60 }
61 }
62}
63
64#[derive(Serialize, Clone)]
65#[serde(rename_all = "snake_case")]
66struct SbomRustc {
67 version: String,
68 wrapper: Option<PathBuf>,
69 workspace_wrapper: Option<PathBuf>,
70 commit_hash: Option<String>,
71 host: String,
72 verbose_version: String,
73}
74
75impl From<&Rustc> for SbomRustc {
76 fn from(rustc: &Rustc) -> Self {
77 Self {
78 version: rustc.version.to_string(),
79 wrapper: rustc.wrapper.clone(),
80 workspace_wrapper: rustc.workspace_wrapper.clone(),
81 commit_hash: rustc.commit_hash.clone(),
82 host: rustc.host.to_string(),
83 verbose_version: rustc.verbose_version.clone(),
84 }
85 }
86}
87
88#[derive(Serialize)]
89#[serde(rename_all = "snake_case")]
90pub struct Sbom {
91 version: SbomFormatVersion,
92 root: SbomIndex,
93 crates: Vec<SbomCrate>,
94 rustc: SbomRustc,
95 target: InternedString,
96}
97
98pub fn build_sbom(build_runner: &BuildRunner<'_, '_>, root: &Unit) -> CargoResult<Sbom> {
100 let bcx = build_runner.bcx;
101 let rustc: SbomRustc = bcx.rustc().into();
102
103 let mut crates = Vec::new();
104 let sbom_graph = build_sbom_graph(build_runner, root);
105
106 let indicies: HashMap<&Unit, SbomIndex> = sbom_graph
108 .keys()
109 .enumerate()
110 .map(|(i, dep)| (*dep, SbomIndex(i)))
111 .collect();
112
113 for (unit, edges) in sbom_graph {
115 let mut krate = SbomCrate::new(unit);
116 for (dep, kind) in edges {
117 krate.dependencies.push(SbomDependency {
118 index: indicies[dep],
119 kind: kind,
120 });
121 }
122 crates.push(krate);
123 }
124 let target = match root.kind {
125 super::CompileKind::Host => build_runner.bcx.host_triple(),
126 super::CompileKind::Target(target) => target.rustc_target(),
127 };
128 Ok(Sbom {
129 version: SbomFormatVersion(1),
130 crates,
131 root: indicies[root],
132 rustc,
133 target,
134 })
135}
136
137fn build_sbom_graph<'a>(
142 build_runner: &'a BuildRunner<'_, '_>,
143 root: &'a Unit,
144) -> BTreeMap<&'a Unit, BTreeSet<(&'a Unit, SbomDependencyType)>> {
145 tracing::trace!("building sbom graph for {}", root.pkg.package_id());
146
147 let mut queue = Vec::new();
148 let mut sbom_graph: BTreeMap<&Unit, BTreeSet<(&Unit, SbomDependencyType)>> = BTreeMap::new();
149 let mut visited = HashSet::new();
150
151 queue.push((root, root, false));
153 while let Some((node, parent, is_build_dep)) = queue.pop() {
154 let dependencies = sbom_graph.entry(parent).or_default();
155 for dep in build_runner.unit_deps(node) {
156 let dep = &dep.unit;
157 let (next_parent, next_is_build_dep) = if dep.mode == CompileMode::RunCustomBuild {
158 (parent, true)
160 } else {
161 let dep_type = match is_build_dep || dep.target.proc_macro() {
163 false => SbomDependencyType::Normal,
164 true => SbomDependencyType::Build,
165 };
166 dependencies.insert((dep, dep_type));
167 tracing::trace!(
168 "adding sbom edge {} -> {} ({:?})",
169 parent.pkg.package_id(),
170 dep.pkg.package_id(),
171 dep_type,
172 );
173 (dep, false)
174 };
175 if visited.insert(dep) {
176 queue.push((dep, next_parent, next_is_build_dep));
177 }
178 }
179 }
180 sbom_graph
181}