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