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::util::Rustc;
14use crate::util::interning::InternedString;
15use crate::workspace::TargetKind;
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
99#[tracing::instrument(skip_all)]
101pub fn build_sbom(build_runner: &BuildRunner<'_, '_>, root: &Unit) -> CargoResult<Sbom> {
102 let bcx = build_runner.bcx;
103 let rustc: SbomRustc = bcx.rustc().into();
104
105 let mut crates = Vec::new();
106 let sbom_graph = build_sbom_graph(build_runner, root);
107
108 let indices: HashMap<&Unit, SbomIndex> = sbom_graph
110 .keys()
111 .enumerate()
112 .map(|(i, dep)| (*dep, SbomIndex(i)))
113 .collect();
114
115 for (unit, edges) in sbom_graph {
117 let mut krate = SbomCrate::new(unit);
118 for (dep, kind) in edges {
119 krate.dependencies.push(SbomDependency {
120 index: indices[dep],
121 kind: kind,
122 });
123 }
124 crates.push(krate);
125 }
126 let target = match root.kind {
127 super::CompileKind::Host => build_runner.bcx.host_triple(),
128 super::CompileKind::Target(target) => target.rustc_target(),
129 };
130 Ok(Sbom {
131 version: SbomFormatVersion(1),
132 crates,
133 root: indices[root],
134 rustc,
135 target,
136 })
137}
138
139#[tracing::instrument(skip_all)]
144fn build_sbom_graph<'a>(
145 build_runner: &'a BuildRunner<'_, '_>,
146 root: &'a Unit,
147) -> BTreeMap<&'a Unit, BTreeSet<(&'a Unit, SbomDependencyType)>> {
148 tracing::trace!("building sbom graph for {}", root.pkg.package_id());
149
150 let mut queue = Vec::new();
151 let mut sbom_graph: BTreeMap<&Unit, BTreeSet<(&Unit, SbomDependencyType)>> = BTreeMap::new();
152 let mut visited = HashSet::default();
153
154 queue.push((root, root, false));
156 while let Some((node, parent, is_build_dep)) = queue.pop() {
157 let dependencies = sbom_graph.entry(parent).or_default();
158 for dep in build_runner.unit_deps(node) {
159 let dep = &dep.unit;
160 let (next_parent, next_is_build_dep) = if dep.mode == CompileMode::RunCustomBuild {
161 (parent, true)
163 } else {
164 let dep_type = match is_build_dep || dep.target.proc_macro() {
166 false => SbomDependencyType::Normal,
167 true => SbomDependencyType::Build,
168 };
169 dependencies.insert((dep, dep_type));
170 (dep, false)
171 };
172 if visited.insert(dep) {
173 queue.push((dep, next_parent, next_is_build_dep));
174 }
175 }
176 }
177 sbom_graph
178}