diff --git a/cargo-cyclonedx/src/cli.rs b/cargo-cyclonedx/src/cli.rs index 5eb2f843..e3f2f9bc 100644 --- a/cargo-cyclonedx/src/cli.rs +++ b/cargo-cyclonedx/src/cli.rs @@ -196,6 +196,11 @@ pub enum ArgsError { FilenameOverrideError(#[from] FilenameOverrideError), } +#[cfg(test)] +pub fn parse_to_config(args: &[&str]) -> SbomConfig { + Args::parse_from(args.iter()).as_config().unwrap() +} + #[cfg(test)] mod tests { use super::*; @@ -228,10 +233,6 @@ mod tests { assert!(!contains_feature(&config, "")); } - fn parse_to_config(args: &[&str]) -> SbomConfig { - Args::parse_from(args.iter()).as_config().unwrap() - } - fn contains_feature(config: &SbomConfig, feature: &str) -> bool { config .features diff --git a/cargo-cyclonedx/src/generator.rs b/cargo-cyclonedx/src/generator.rs index f239f2f7..30b1d155 100644 --- a/cargo-cyclonedx/src/generator.rs +++ b/cargo-cyclonedx/src/generator.rs @@ -779,6 +779,7 @@ fn filtered_dependencies<'a>( /// * `package_name` - Package from which this SBOM was generated /// * `sbom_config` - Configuration options used during generation /// * `target_kinds` - Detailed information on the kinds of targets in `sbom` +#[derive(Debug)] pub struct GeneratedSbom { pub bom: Bom, pub manifest_path: PathBuf, diff --git a/cargo-cyclonedx/src/main.rs b/cargo-cyclonedx/src/main.rs index 51a87e4a..103c2ab7 100644 --- a/cargo-cyclonedx/src/main.rs +++ b/cargo-cyclonedx/src/main.rs @@ -48,6 +48,7 @@ use cargo_cyclonedx::{ config::{SbomConfig, Target}, generator::SbomGenerator, + GeneratedSbom, }; use std::{ @@ -65,22 +66,28 @@ use log::LevelFilter; mod cli; use cli::{Args, Opts}; -fn main() -> anyhow::Result<()> { - let Opts::Bom(args) = Opts::parse(); - setup_logging(&args)?; - +fn generate_sboms(args: &Args) -> Result> { let cli_config = args.as_config()?; - let manifest_path = locate_manifest(&args)?; + let manifest_path = locate_manifest(args)?; log::debug!("Found the Cargo.toml file at {}", manifest_path.display()); log::trace!("Running `cargo metadata` started"); - let metadata = get_metadata(&args, &manifest_path, &cli_config)?; + let metadata = get_metadata(args, &manifest_path, &cli_config)?; log::trace!("Running `cargo metadata` finished"); log::trace!("SBOM generation started"); let boms = SbomGenerator::create_sboms(metadata, &cli_config)?; log::trace!("SBOM generation finished"); + Ok(boms) +} + +fn main() -> anyhow::Result<()> { + let Opts::Bom(args) = Opts::parse(); + setup_logging(&args)?; + + let boms = generate_sboms(&args)?; + log::trace!("SBOM output started"); for bom in boms { bom.write_to_files()?; @@ -163,3 +170,62 @@ fn get_metadata( Ok(cmd.exec()?) } + +#[cfg(test)] +mod tests { + use cyclonedx_bom::prelude::NormalizedString; + + #[test] + fn parse_toml_only_normal() { + use crate::cli; + use crate::generate_sboms; + use clap::Parser; + use cyclonedx_bom::models::component::Scope; + use std::path::PathBuf; + + let mut test_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + test_cargo_toml.push("tests/fixtures/build_then_runtime_dep/Cargo.toml"); + + let path_arg = &format!("--manifest-path={}", test_cargo_toml.display()); + let args = ["cyclonedx", path_arg, "--no-build-deps"]; + let args_parsed = cli::Args::parse_from(args.iter()); + + let sboms = generate_sboms(&args_parsed).unwrap(); + + let components = sboms[0].bom.components.as_ref().unwrap(); + assert!(components + .0 + .iter() + .all(|f| f.scope == Some(Scope::Required))); + } + + #[test] + fn parse_toml_with_excluded() { + use crate::cli; + use crate::generate_sboms; + use clap::Parser; + use cyclonedx_bom::models::component::Scope; + use std::path::PathBuf; + + let mut test_cargo_toml = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + test_cargo_toml.push("tests/fixtures/build_then_runtime_dep/Cargo.toml"); + + let path_arg = &format!("--manifest-path={}", test_cargo_toml.display()); + let args = ["cyclonedx", path_arg]; + let args_parsed = cli::Args::parse_from(args.iter()); + + let sboms = generate_sboms(&args_parsed).unwrap(); + + // build_dep is a build dependency -> excluded + // runtime_dep_of_build_dep is a dependency of a build dependency -> excluded + let components = sboms[0].bom.components.as_ref().unwrap(); + assert!(components + .0 + .iter() + .all(|c| c.name != NormalizedString::new("build_dep") + || c.scope == Some(Scope::Excluded))); + assert!(components.0.iter().all(|c| c.name + != NormalizedString::new("runtime_dep_of_build_dep") + || c.scope == Some(Scope::Excluded))); + } +} diff --git a/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/Cargo.lock b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/Cargo.lock new file mode 100644 index 00000000..1a96d8f9 --- /dev/null +++ b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/Cargo.lock @@ -0,0 +1,31 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "build_dep" +version = "0.1.0" +dependencies = [ + "runtime_dep_of_build_dep", +] + +[[package]] +name = "runtime_dep_of_build_dep" +version = "0.1.0" +dependencies = [ + "runtime_dep_of_runtime_dep", +] + +[[package]] +name = "runtime_dep_of_runtime_dep" +version = "0.1.0" +dependencies = [ + "build_dep", +] + +[[package]] +name = "top_level_crate" +version = "0.1.0" +dependencies = [ + "build_dep", +] diff --git a/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/Cargo.toml b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/Cargo.toml new file mode 100644 index 00000000..a1531f28 --- /dev/null +++ b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +resolver = "2" +members = [ + "top_level_crate", + "build_dep", + "runtime_dep_of_build_dep", +] diff --git a/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/build_dep/Cargo.toml b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/build_dep/Cargo.toml new file mode 100644 index 00000000..27cb1e9a --- /dev/null +++ b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/build_dep/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "build_dep" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +runtime_dep_of_build_dep = {path = "../runtime_dep_of_build_dep"} diff --git a/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/build_dep/src/lib.rs b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/build_dep/src/lib.rs new file mode 100644 index 00000000..1b4a90c9 --- /dev/null +++ b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/build_dep/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +} diff --git a/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep/Cargo.toml b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep/Cargo.toml new file mode 100644 index 00000000..0376fdcf --- /dev/null +++ b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "runtime_dep_of_build_dep" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +runtime_dep_of_runtime_dep = {path = "../runtime_dep_of_runtime_dep"} diff --git a/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep/src/lib.rs b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep/src/lib.rs new file mode 100644 index 00000000..1b4a90c9 --- /dev/null +++ b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_build_dep/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +} diff --git a/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_runtime_dep/Cargo.toml b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_runtime_dep/Cargo.toml new file mode 100644 index 00000000..90a4e046 --- /dev/null +++ b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_runtime_dep/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "runtime_dep_of_runtime_dep" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dev-dependencies] +build_dep = {path = "../build_dep"} diff --git a/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_runtime_dep/src/lib.rs b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_runtime_dep/src/lib.rs new file mode 100644 index 00000000..1b4a90c9 --- /dev/null +++ b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/runtime_dep_of_runtime_dep/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +} diff --git a/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/top_level_crate/Cargo.toml b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/top_level_crate/Cargo.toml new file mode 100644 index 00000000..f2373ea9 --- /dev/null +++ b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/top_level_crate/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "top_level_crate" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +build_dep = {path = "../build_dep"} \ No newline at end of file diff --git a/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/top_level_crate/src/main.rs b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/top_level_crate/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/cargo-cyclonedx/tests/fixtures/build_then_runtime_dep/top_level_crate/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}