use cargo_cyclonedx::{
config::{SbomConfig, Target},
generator::SbomGenerator,
};
use std::{
io::{self},
path::{Path, PathBuf},
};
use cargo_metadata::{self, CargoOpt, Metadata};
use anyhow::Result;
use clap::Parser;
use env_logger::Builder;
use log::LevelFilter;
mod cli;
use cli::{Args, Opts};
fn main() -> anyhow::Result<()> {
let Opts::Bom(args) = Opts::parse();
setup_logging(&args)?;
let cli_config = args.as_config()?;
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)?;
log::trace!("Running `cargo metadata` finished");
log::trace!("SBOM generation started");
let boms = SbomGenerator::create_sboms(metadata, &cli_config)?;
log::trace!("SBOM generation finished");
log::trace!("SBOM output started");
for bom in boms {
bom.write_to_files()?;
}
log::trace!("SBOM output finished");
Ok(())
}
fn setup_logging(args: &Args) -> anyhow::Result<()> {
let mut builder = Builder::new();
let level_filter = if args.quiet >= 2 {
LevelFilter::Off
} else {
match args.verbose {
0 => LevelFilter::Warn,
1 => LevelFilter::Info,
2 => LevelFilter::Debug,
_ => LevelFilter::Trace,
}
};
builder.filter_level(level_filter);
builder.parse_default_env(); builder.try_init()?;
Ok(())
}
fn locate_manifest(args: &Args) -> Result<PathBuf, io::Error> {
if let Some(manifest_path) = &args.manifest_path {
let manifest_path = manifest_path.canonicalize()?;
log::info!(
"Using manually specified Cargo.toml manifest located at: {}",
manifest_path.to_string_lossy()
);
Ok(manifest_path)
} else {
let manifest_path = std::env::current_dir()?.join("Cargo.toml");
log::info!(
"Using Cargo.toml manifest located at: {}",
manifest_path.to_string_lossy()
);
Ok(manifest_path)
}
}
fn get_metadata(
args: &Args,
manifest_path: &Path,
config: &SbomConfig,
) -> anyhow::Result<Metadata> {
let mut cmd = cargo_metadata::MetadataCommand::new();
cmd.manifest_path(manifest_path);
if let Some(feature_configuration) = config.features.as_ref() {
if feature_configuration.all_features {
cmd.features(CargoOpt::AllFeatures);
}
if feature_configuration.no_default_features {
cmd.features(CargoOpt::NoDefaultFeatures);
}
if !feature_configuration.features.is_empty() {
cmd.features(CargoOpt::SomeFeatures(
feature_configuration.features.clone(),
));
}
}
if args.quiet == 0 {
cmd.verbose(true);
}
if let Some(Target::SingleTarget(target)) = config.target.as_ref() {
cmd.other_options(vec!["--filter-platform".to_owned(), target.to_owned()]);
}
Ok(cmd.exec()?)
}