From c0715a35931d4e865228ed7e5185cd2bbff02907 Mon Sep 17 00:00:00 2001 From: Chris Henk Date: Fri, 10 Jan 2025 06:19:57 -0800 Subject: [PATCH] Support pwsh format for show-env (#411) --- .github/.cspell/project-dictionary.txt | 1 + README.md | 6 +++ docs/cargo-llvm-cov-show-env.txt | 4 ++ src/cli.rs | 69 +++++++++++++++++++++++--- src/main.rs | 3 +- tests/test.rs | 24 +++++++++ 6 files changed, 99 insertions(+), 8 deletions(-) diff --git a/.github/.cspell/project-dictionary.txt b/.github/.cspell/project-dictionary.txt index 422f712c..df0cd033 100644 --- a/.github/.cspell/project-dictionary.txt +++ b/.github/.cspell/project-dictionary.txt @@ -17,6 +17,7 @@ nextest notcovered profdata profraw +pwsh rmeta rustfilt rustix diff --git a/README.md b/README.md index 3b9dc09a..3b7ce0b4 100644 --- a/README.md +++ b/README.md @@ -479,6 +479,12 @@ Note: cargo-llvm-cov subcommands other than `report` and `clean` may not work co Note: To include coverage for doctests you also need to pass `--doctests` to both `cargo llvm-cov show-env` and `cargo llvm-cov report`. +> The same thing can be achieved in PowerShell 6+ by substituting the source command with: +> +> ```powershell +> Invoke-Expression (cargo llvm-cov show-env --with-pwsh-env-prefix | Out-String) +> ``` + ### Exclude file from coverage To exclude specific file patterns from the report, use the `--ignore-filename-regex` option. diff --git a/docs/cargo-llvm-cov-show-env.txt b/docs/cargo-llvm-cov-show-env.txt index b0a54d0e..30c4da1c 100644 --- a/docs/cargo-llvm-cov-show-env.txt +++ b/docs/cargo-llvm-cov-show-env.txt @@ -8,6 +8,10 @@ OPTIONS: --export-prefix Prepend "export " to each line, so that the output is suitable to be sourced by bash + --with-pwsh-env-prefix + Unicode escape and double quote values + prepend "$env:", so that the output is suitable + to be used with Invoke-Expression in PowerShell 6+. + --doctests Including doc tests (unstable) diff --git a/src/cli.rs b/src/cli.rs index b46ff721..50324aba 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -370,9 +370,60 @@ impl LlvmCovOptions { } #[derive(Debug, Clone)] -pub(crate) struct ShowEnvOptions { +pub(crate) enum ShowEnvFormat { + /// Each line: key=, escaped using [`shell_escape::escape`]. + EscapedKeyValuePair, /// Prepend "export " to each line, so that the output is suitable to be sourced by bash. - pub(crate) export_prefix: bool, + UnixExport, + /// Each value: "$env:{key}={value}", where {value} is PowerShell Unicode escaped e.g. "`u{72}". + Pwsh, +} + +impl ShowEnvFormat { + pub(crate) fn new(export_prefix: bool, with_pwsh_env_prefix: bool) -> Result { + if export_prefix && with_pwsh_env_prefix { + conflicts("--export-prefix", "--with-pwsh-env-prefix")?; + } + + Ok(if export_prefix { + ShowEnvFormat::UnixExport + } else if with_pwsh_env_prefix { + ShowEnvFormat::Pwsh + } else { + ShowEnvFormat::EscapedKeyValuePair + }) + } + + pub(crate) fn export_string(&self, key: &str, value: &str) -> String { + match self { + ShowEnvFormat::EscapedKeyValuePair => { + format!("{key}={}", shell_escape::escape(value.into())) + } + ShowEnvFormat::UnixExport => { + format!("export {key}={}", shell_escape::escape(value.into())) + } + ShowEnvFormat::Pwsh => { + // PowerShell 6+ expects encoded UTF-8 text. Some env vars like CARGO_ENCODED_RUSTFLAGS + // have non-printable binary characters. We can work around this and any other escape + // related considerations by just escaping all characters. Rust's Unicode escape is + // of form "\u{}", but PowerShell expects "`u{}". A replace call fixes + // this. + let value = value.escape_unicode().to_string().replace('\\', "`"); + format!("$env:{key}=\"{value}\"") + } + } + } +} + +impl Default for ShowEnvFormat { + fn default() -> Self { + Self::EscapedKeyValuePair + } +} + +#[derive(Debug, Clone)] +pub(crate) struct ShowEnvOptions { + pub(crate) show_env_format: ShowEnvFormat, } // https://doc.rust-lang.org/nightly/cargo/commands/cargo-test.html#manifest-options @@ -532,6 +583,7 @@ impl Args { // show-env options let mut export_prefix = false; + let mut with_pwsh_env_prefix = false; // options ambiguous between nextest-related and others let mut profile = None; @@ -711,6 +763,7 @@ impl Args { // show-env options Long("export-prefix") => parse_flag!(export_prefix), + Long("with-pwsh-env-prefix") => parse_flag!(with_pwsh_env_prefix), // ambiguous between nextest-related and others will be handled later Long("archive-file") => parse_opt_passthrough!(archive_file), @@ -814,14 +867,18 @@ impl Args { term::set_coloring(&mut color); // unexpected options - match subcommand { - Subcommand::ShowEnv => {} + let show_env_format = match subcommand { + Subcommand::ShowEnv => ShowEnvFormat::new(export_prefix, with_pwsh_env_prefix)?, _ => { if export_prefix { unexpected("--export-prefix", subcommand)?; } + if with_pwsh_env_prefix { + unexpected("--with-pwsh-env-prefix", subcommand)?; + } + ShowEnvFormat::default() } - } + }; if doc || doctests { let flag = if doc { "--doc" } else { "--doctests" }; match subcommand { @@ -1211,7 +1268,7 @@ impl Args { branch, mcdc, }, - show_env: ShowEnvOptions { export_prefix }, + show_env: ShowEnvOptions { show_env_format }, doctests, ignore_run_fail, lib, diff --git a/src/main.rs b/src/main.rs index 6a3899a3..84bb6d90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -160,8 +160,7 @@ struct ShowEnvWriter { impl EnvTarget for ShowEnvWriter { fn set(&mut self, key: &str, value: &str) -> Result<()> { - let prefix = if self.options.export_prefix { "export " } else { "" }; - writeln!(self.writer, "{prefix}{key}={}", shell_escape::escape(value.into())) + writeln!(self.writer, "{}", self.options.show_env_format.export_string(key, value)) .context("failed to write env to stdout") } fn unset(&mut self, key: &str) -> Result<()> { diff --git a/tests/test.rs b/tests/test.rs index a639d49d..06f4a4e0 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -6,6 +6,7 @@ mod auxiliary; use std::path::Path; +use cargo_config2::Flags; use fs_err as fs; use self::auxiliary::{ @@ -286,6 +287,25 @@ fn open_report() { fn show_env() { cargo_llvm_cov("show-env").assert_success().stdout_not_contains("export"); cargo_llvm_cov("show-env").arg("--export-prefix").assert_success().stdout_contains("export"); + + let mut flags = Flags::default(); + flags.push("--deny warnings"); + flags.push("--cfg=tests"); + let flags = flags.encode().unwrap(); + + cargo_llvm_cov("show-env") + .env("CARGO_ENCODED_RUSTFLAGS", flags) + .arg("--with-pwsh-env-prefix") + .assert_success() + // Verify the prefix related content + the encoding of "--" + .stdout_contains("$env:CARGO_ENCODED_RUSTFLAGS=\"`u{2d}`u{2d}") + // Verify binary character didn't lead to incompatible output for pwsh + .stdout_contains("`u{1f}"); + cargo_llvm_cov("show-env") + .arg("--export-prefix") + .arg("--with-pwsh-env-prefix") + .assert_failure() + .stderr_contains("may not be used together with"); } #[test] @@ -298,6 +318,10 @@ fn invalid_arg() { .arg("--export-prefix") .assert_failure() .stderr_contains("invalid option '--export-prefix'"); + cargo_llvm_cov(subcommand) + .arg("--with-pwsh-env-prefix") + .assert_failure() + .stderr_contains("invalid option '--with-pwsh-env-prefix'"); } if !matches!(subcommand, "" | "test") { if matches!(subcommand, "nextest" | "nextest-archive") {