Skip to content

Commit 5f833db

Browse files
committed
feat: Add SBOM pre-cursor files
Adds a new option `build.sbom` that adds generation of a JSON file containing dependency information alongside compiled artifacts.
1 parent 392d68b commit 5f833db

File tree

17 files changed

+1099
-23
lines changed

17 files changed

+1099
-23
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cargo-test-support/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cargo-test-support"
3-
version = "0.7.2"
3+
version = "0.7.3"
44
edition.workspace = true
55
rust-version = "1.85" # MSRV:1
66
license.workspace = true

crates/cargo-test-support/src/lib.rs

+10
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,16 @@ impl Project {
415415
.join(paths::get_lib_filename(name, kind))
416416
}
417417

418+
/// Path to a dynamic library.
419+
/// ex: `/path/to/cargo/target/cit/t0/foo/target/debug/examples/libex.dylib`
420+
pub fn dylib(&self, name: &str) -> PathBuf {
421+
self.target_debug_dir().join(format!(
422+
"{}{name}{}",
423+
env::consts::DLL_PREFIX,
424+
env::consts::DLL_SUFFIX
425+
))
426+
}
427+
418428
/// Path to a debug binary.
419429
///
420430
/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/foo/target/debug/foo`

src/cargo/core/compiler/build_config.rs

+14
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ pub struct BuildConfig {
4848
pub future_incompat_report: bool,
4949
/// Which kinds of build timings to output (empty if none).
5050
pub timing_outputs: Vec<TimingOutput>,
51+
/// Output SBOM precursor files.
52+
pub sbom: bool,
5153
}
5254

5355
fn default_parallelism() -> CargoResult<u32> {
@@ -99,6 +101,17 @@ impl BuildConfig {
99101
},
100102
};
101103

104+
// If sbom flag is set, it requires the unstable feature
105+
let sbom = match (cfg.sbom, gctx.cli_unstable().sbom) {
106+
(Some(sbom), true) => sbom,
107+
(Some(_), false) => {
108+
gctx.shell()
109+
.warn("ignoring 'sbom' config, pass `-Zsbom` to enable it")?;
110+
false
111+
}
112+
(None, _) => false,
113+
};
114+
102115
Ok(BuildConfig {
103116
requested_kinds,
104117
jobs,
@@ -115,6 +128,7 @@ impl BuildConfig {
115128
export_dir: None,
116129
future_incompat_report: false,
117130
timing_outputs: Vec::new(),
131+
sbom,
118132
})
119133
}
120134

src/cargo/core/compiler/build_context/target_info.rs

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ pub enum FileFlavor {
7272
Rmeta,
7373
/// Piece of external debug information (e.g., `.dSYM`/`.pdb` file).
7474
DebugInfo,
75+
/// SBOM (Software Bill of Materials pre-cursor) file (e.g. cargo-sbon.json).
76+
Sbom,
7577
}
7678

7779
/// Type of each file generated by a Unit.

src/cargo/core/compiler/build_runner/compilation_files.rs

+25-1
Original file line numberDiff line numberDiff line change
@@ -495,13 +495,37 @@ impl<'a, 'gctx: 'a> CompilationFiles<'a, 'gctx> {
495495
CompileMode::Test
496496
| CompileMode::Build
497497
| CompileMode::Bench
498-
| CompileMode::Check { .. } => self.calc_outputs_rustc(unit, bcx)?,
498+
| CompileMode::Check { .. } => {
499+
let mut outputs = self.calc_outputs_rustc(unit, bcx)?;
500+
if bcx.build_config.sbom && bcx.gctx.cli_unstable().sbom {
501+
let sbom_files: Vec<_> = outputs
502+
.iter()
503+
.filter(|o| matches!(o.flavor, FileFlavor::Normal | FileFlavor::Linkable))
504+
.map(|output| OutputFile {
505+
path: Self::append_sbom_suffix(&output.path),
506+
hardlink: output.hardlink.as_ref().map(Self::append_sbom_suffix),
507+
export_path: output.export_path.as_ref().map(Self::append_sbom_suffix),
508+
flavor: FileFlavor::Sbom,
509+
})
510+
.collect();
511+
outputs.extend(sbom_files.into_iter());
512+
}
513+
outputs
514+
}
499515
};
500516
debug!("Target filenames: {:?}", ret);
501517

502518
Ok(Arc::new(ret))
503519
}
504520

521+
/// Append the SBOM suffix to the file name.
522+
fn append_sbom_suffix(link: &PathBuf) -> PathBuf {
523+
const SBOM_FILE_EXTENSION: &str = ".cargo-sbom.json";
524+
let mut link_buf = link.clone().into_os_string();
525+
link_buf.push(SBOM_FILE_EXTENSION);
526+
PathBuf::from(link_buf)
527+
}
528+
505529
/// Computes the actual, full pathnames for all the files generated by rustc.
506530
///
507531
/// The `OutputFile` also contains the paths where those files should be

src/cargo/core/compiler/build_runner/mod.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,10 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
309309

310310
fn collect_tests_and_executables(&mut self, unit: &Unit) -> CargoResult<()> {
311311
for output in self.outputs(unit)?.iter() {
312-
if output.flavor == FileFlavor::DebugInfo || output.flavor == FileFlavor::Auxiliary {
312+
if matches!(
313+
output.flavor,
314+
FileFlavor::DebugInfo | FileFlavor::Auxiliary | FileFlavor::Sbom
315+
) {
313316
continue;
314317
}
315318

@@ -446,6 +449,16 @@ impl<'a, 'gctx> BuildRunner<'a, 'gctx> {
446449
self.files().metadata(unit).unit_id()
447450
}
448451

452+
/// Returns the list of SBOM output file paths for a given [`Unit`].
453+
pub fn sbom_output_files(&self, unit: &Unit) -> CargoResult<Vec<PathBuf>> {
454+
Ok(self
455+
.outputs(unit)?
456+
.iter()
457+
.filter(|o| o.flavor == FileFlavor::Sbom)
458+
.map(|o| o.path.clone())
459+
.collect())
460+
}
461+
449462
pub fn is_primary_package(&self, unit: &Unit) -> bool {
450463
self.primary_packages.contains(&unit.pkg.package_id())
451464
}

src/cargo/core/compiler/fingerprint/mod.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -1517,7 +1517,12 @@ fn calculate_normal(
15171517
let outputs = build_runner
15181518
.outputs(unit)?
15191519
.iter()
1520-
.filter(|output| !matches!(output.flavor, FileFlavor::DebugInfo | FileFlavor::Auxiliary))
1520+
.filter(|output| {
1521+
!matches!(
1522+
output.flavor,
1523+
FileFlavor::DebugInfo | FileFlavor::Auxiliary | FileFlavor::Sbom
1524+
)
1525+
})
15211526
.map(|output| output.path.clone())
15221527
.collect();
15231528

src/cargo/core/compiler/mod.rs

+15-2
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub(crate) mod layout;
4747
mod links;
4848
mod lto;
4949
mod output_depinfo;
50+
mod output_sbom;
5051
pub mod rustdoc;
5152
pub mod standard_lib;
5253
mod timings;
@@ -60,7 +61,7 @@ use std::env;
6061
use std::ffi::{OsStr, OsString};
6162
use std::fmt::Display;
6263
use std::fs::{self, File};
63-
use std::io::{BufRead, Write};
64+
use std::io::{BufRead, BufWriter, Write};
6465
use std::path::{Path, PathBuf};
6566
use std::sync::Arc;
6667

@@ -85,6 +86,7 @@ use self::job_queue::{Job, JobQueue, JobState, Work};
8586
pub(crate) use self::layout::Layout;
8687
pub use self::lto::Lto;
8788
use self::output_depinfo::output_depinfo;
89+
use self::output_sbom::build_sbom;
8890
use self::unit_graph::UnitDep;
8991
use crate::core::compiler::future_incompat::FutureIncompatReport;
9092
pub use crate::core::compiler::unit::{Unit, UnitInterner};
@@ -307,6 +309,8 @@ fn rustc(
307309
let script_metadata = build_runner.find_build_script_metadata(unit);
308310
let is_local = unit.is_local();
309311
let artifact = unit.artifact;
312+
let sbom_files = build_runner.sbom_output_files(unit)?;
313+
let sbom = build_sbom(build_runner, unit)?;
310314

311315
let hide_diagnostics_for_scrape_unit = build_runner.bcx.unit_can_fail_for_docscraping(unit)
312316
&& !matches!(
@@ -392,6 +396,12 @@ fn rustc(
392396
if build_plan {
393397
state.build_plan(buildkey, rustc.clone(), outputs.clone());
394398
} else {
399+
for file in sbom_files {
400+
tracing::debug!("writing sbom to {}", file.display());
401+
let outfile = BufWriter::new(paths::create(&file)?);
402+
serde_json::to_writer(outfile, &sbom)?;
403+
}
404+
395405
let result = exec
396406
.exec(
397407
&rustc,
@@ -685,6 +695,7 @@ where
685695
/// completion of other units will be added later in runtime, such as flags
686696
/// from build scripts.
687697
fn prepare_rustc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<ProcessBuilder> {
698+
let gctx = build_runner.bcx.gctx;
688699
let is_primary = build_runner.is_primary_package(unit);
689700
let is_workspace = build_runner.bcx.ws.is_member(&unit.pkg);
690701

@@ -700,7 +711,7 @@ fn prepare_rustc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult
700711
base.args(args);
701712
}
702713
base.args(&unit.rustflags);
703-
if build_runner.bcx.gctx.cli_unstable().binary_dep_depinfo {
714+
if gctx.cli_unstable().binary_dep_depinfo {
704715
base.arg("-Z").arg("binary-dep-depinfo");
705716
}
706717
if build_runner.bcx.gctx.cli_unstable().checksum_freshness {
@@ -709,6 +720,8 @@ fn prepare_rustc(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> CargoResult
709720

710721
if is_primary {
711722
base.env("CARGO_PRIMARY_PACKAGE", "1");
723+
let file_list = std::env::join_paths(build_runner.sbom_output_files(unit)?)?;
724+
base.env("CARGO_SBOM_PATH", file_list);
712725
}
713726

714727
if unit.target.is_test() || unit.target.is_bench() {

src/cargo/core/compiler/output_depinfo.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,12 @@ pub fn output_depinfo(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> Ca
141141
.map(|f| render_filename(f, basedir))
142142
.collect::<CargoResult<Vec<_>>>()?;
143143

144-
for output in build_runner
145-
.outputs(unit)?
146-
.iter()
147-
.filter(|o| !matches!(o.flavor, FileFlavor::DebugInfo | FileFlavor::Auxiliary))
148-
{
144+
for output in build_runner.outputs(unit)?.iter().filter(|o| {
145+
!matches!(
146+
o.flavor,
147+
FileFlavor::DebugInfo | FileFlavor::Auxiliary | FileFlavor::Sbom
148+
)
149+
}) {
149150
if let Some(ref link_dst) = output.hardlink {
150151
let output_path = link_dst.with_extension("d");
151152
if success {

0 commit comments

Comments
 (0)