diff --git a/README.md b/README.md index 664dbffd..8049f063 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,17 @@ cargo llvm-cov --no-report --features b cargo llvm-cov --no-run --lcov ``` +In combination with the `show-env` subcommand, coverage can also be produced from arbitrary binaries: + +```sh +cargo llvm-cov clean --workspace # remove artifacts that may affect the coverage results +source <(cargo llvm-cov show-env --export-prefix) +cargo build # build rust binaries +# commands using binaries in target/debug/*, including `cargo test` +# ... +cargo llvm-cov --no-run --lcov # generate report without tests +``` + To exclude specific file patterns from the report, use the `--ignore-filename-regex` option. ```sh diff --git a/src/cli.rs b/src/cli.rs index 763d58e0..0d043f36 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -177,6 +177,14 @@ pub(crate) enum Subcommand { )] Run(Box), + /// Output the environment set by cargo-llvm-cov to build Rust projects. + #[clap( + bin_name = "cargo llvm-cov show-env", + max_term_width = MAX_TERM_WIDTH, + setting = AppSettings::DeriveDisplayOrder, + )] + ShowEnv(ShowEnvOptions), + /// Remove artifacts that cargo-llvm-cov has generated in the past #[clap( bin_name = "cargo llvm-cov clean", @@ -430,6 +438,13 @@ impl RunOptions { } } +#[derive(Debug, Parser)] +pub(crate) struct ShowEnvOptions { + /// Prepend "export " to each line, so that the output is suitable to be sourced by bash. + #[clap(long)] + pub(crate) export_prefix: bool, +} + #[derive(Debug, Parser)] pub(crate) struct CleanOptions { /// Remove artifacts that may affect the coverage results of packages in the workspace. diff --git a/src/main.rs b/src/main.rs index af87fdbe..185037e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ use std::{ use anyhow::{Context as _, Result}; use camino::{Utf8Path, Utf8PathBuf}; use clap::Parser; -use cli::RunOptions; +use cli::{RunOptions, ShowEnvOptions}; use regex::Regex; use walkdir::WalkDir; @@ -57,6 +57,7 @@ fn main() { fn try_main() -> Result<()> { let Opts::LlvmCov(mut args) = Opts::parse(); + let cx = &context_from_args(&mut args)?; match args.subcommand { Some(Subcommand::Demangle) => { @@ -92,6 +93,10 @@ fn try_main() -> Result<()> { } } + Some(Subcommand::ShowEnv(options)) => { + set_env(cx, &mut ShowEnvWriter { target: std::io::stdout(), options }); + } + None => { term::set_quiet(args.quiet); if args.doctests { @@ -102,18 +107,6 @@ fn try_main() -> Result<()> { warn!("--doc option is unstable"); } - let cx = &Context::new( - args.build(), - args.manifest(), - args.cov(), - args.workspace, - &args.exclude, - &args.package, - args.quiet, - args.doctests, - args.no_run, - )?; - clean::clean_partial(cx)?; create_dirs(cx)?; match (args.no_run, cx.cov.no_report) { @@ -134,6 +127,20 @@ fn try_main() -> Result<()> { Ok(()) } +fn context_from_args(args: &mut Args) -> Result { + Context::new( + args.build(), + args.manifest(), + args.cov(), + args.workspace, + &args.exclude, + &args.package, + args.quiet, + args.doctests, + args.no_run, + ) +} + fn create_dirs(cx: &Context) -> Result<()> { fs::create_dir_all(&cx.ws.target_dir)?; @@ -153,7 +160,35 @@ fn create_dirs(cx: &Context) -> Result<()> { Ok(()) } -fn set_env(cx: &Context, cmd: &mut ProcessBuilder) { +trait EnvTarget { + fn set(&mut self, key: &str, value: &str); +} + +impl EnvTarget for ProcessBuilder { + fn set(&mut self, key: &str, value: &str) { + self.env(key, value); + } +} + +struct ShowEnvWriter { + target: W, + options: ShowEnvOptions, +} + +impl EnvTarget for ShowEnvWriter { + fn set(&mut self, key: &str, value: &str) { + writeln!( + self.target, + r#"{prefix}{key}="{value}""#, + prefix = if self.options.export_prefix { "export " } else { "" }, + key = key, + value = value, + ) + .expect("failed to write to stdout"); + } +} + +fn set_env(cx: &Context, target: &mut impl EnvTarget) { let llvm_profile_file = cx.ws.target_dir.join(format!("{}-%m.profraw", cx.ws.package_name)); let rustflags = &mut cx.ws.config.rustflags().unwrap_or_default(); @@ -192,12 +227,12 @@ fn set_env(cx: &Context, cmd: &mut ProcessBuilder) { } } - cmd.env("RUSTFLAGS", &rustflags); + target.set("RUSTFLAGS", rustflags); if let Some(rustdocflags) = rustdocflags { - cmd.env("RUSTDOCFLAGS", &rustdocflags); + target.set("RUSTDOCFLAGS", rustdocflags); } - cmd.env("LLVM_PROFILE_FILE", &*llvm_profile_file); - cmd.env("CARGO_INCREMENTAL", "0"); + target.set("LLVM_PROFILE_FILE", llvm_profile_file.as_str()); + target.set("CARGO_INCREMENTAL", "0"); } fn run_test(cx: &Context, args: &Args) -> Result<()> { diff --git a/tests/long-help.txt b/tests/long-help.txt index ad0ddf33..dd69ef95 100644 --- a/tests/long-help.txt +++ b/tests/long-help.txt @@ -196,6 +196,8 @@ OPTIONS: SUBCOMMANDS: run Run a binary or example and generate coverage report + show-env + Output the environment set by cargo-llvm-cov to build Rust projects clean Remove artifacts that cargo-llvm-cov has generated in the past help diff --git a/tests/short-help.txt b/tests/short-help.txt index 1e531c1b..a3a76ea6 100644 --- a/tests/short-help.txt +++ b/tests/short-help.txt @@ -147,6 +147,7 @@ OPTIONS: Print version information SUBCOMMANDS: - run Run a binary or example and generate coverage report - clean Remove artifacts that cargo-llvm-cov has generated in the past - help Print this message or the help of the given subcommand(s) + run Run a binary or example and generate coverage report + show-env Output the environment set by cargo-llvm-cov to build Rust projects + clean Remove artifacts that cargo-llvm-cov has generated in the past + help Print this message or the help of the given subcommand(s)