diff --git a/.anvil.lock b/.anvil.lock index 6b9924dd..649f032b 100644 --- a/.anvil.lock +++ b/.anvil.lock @@ -1,7 +1,7 @@ version = 1 tool = "anvil" -tool_version = "0.11.0" -catalog_checksum = "sha256:8902caf297ad65a158418bd2ab41801a95c600c99b6140d51c9e7a440000cffa" +tool_version = "0.11.1" +catalog_checksum = "sha256:b5e3da36fa7754d780c43c77e2cff46232de54a440b0798c91f085d2830eee3c" [[file]] path = ".anvil/container/Dockerfile.dockerignore" @@ -125,7 +125,7 @@ checksum = "sha256:7aa0560dc9088b33e80cd55aee0a25090cbd3d3610652afd3734d0ca52ee7 [[file]] path = "justfiles/anvil/checks/llvm-cov.just" -checksum = "sha256:662c85b748f87a0a294e34588ab294851562896739b5cc11f9ad895699e06a94" +checksum = "sha256:5c4d7454b67503ce68a4d68df1783989f5afabc763df5e343bf7abddf974c387" [[file]] path = "justfiles/anvil/checks/loom.just" diff --git a/Cargo.lock b/Cargo.lock index d5bb99c6..3fcbcc21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -415,7 +415,7 @@ dependencies = [ [[package]] name = "cargo-anvil" -version = "0.11.0" +version = "0.11.1" dependencies = [ "assert_cmd", "clap", diff --git a/crates/cargo-anvil/CHANGELOG.md b/crates/cargo-anvil/CHANGELOG.md index 7705b6f2..9d74c8f4 100644 --- a/crates/cargo-anvil/CHANGELOG.md +++ b/crates/cargo-anvil/CHANGELOG.md @@ -2,6 +2,13 @@ ## [Unreleased] +## [0.11.1] - 2026-09-21 + +- 🐛 Bug Fixes + + - normalize shell-quoted coverage arguments + - parse single-quoted llvm-cov paths + ## [0.11.0] - 2026-09-17 - 🐛 Bug Fixes diff --git a/crates/cargo-anvil/Cargo.toml b/crates/cargo-anvil/Cargo.toml index dc3d34d4..05b80759 100644 --- a/crates/cargo-anvil/Cargo.toml +++ b/crates/cargo-anvil/Cargo.toml @@ -3,7 +3,7 @@ [package] name = "cargo-anvil" -version = "0.11.0" +version = "0.11.1" edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/cargo-anvil/README.md b/crates/cargo-anvil/README.md index 13e5ed12..e5a2e9a9 100644 --- a/crates/cargo-anvil/README.md +++ b/crates/cargo-anvil/README.md @@ -400,7 +400,7 @@ More detailed design and operational guidance is available in the This crate was developed as part of The Oxidizer Project. Browse this crate's source code. - [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQblRYhli3L8qob_NSi_WYo69wbWnMVqZw3jJwb3u56HnT6RDphYvRhcoQbBALu36V1VAYbFUDdfp-8dCobobFjKbRep8AbYNMPgi_aMhFhZIGDa2NhcmdvLWFudmlsZjAuMTEuMGtjYXJnb19hbnZpbA + [__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjNhdIQblRYhli3L8qob_NSi_WYo69wbWnMVqZw3jJwb3u56HnT6RDphYvRhcoQbBALu36V1VAYbFUDdfp-8dCobobFjKbRep8AbYNMPgi_aMhFhZIGDa2NhcmdvLWFudmlsZjAuMTEuMWtjYXJnb19hbnZpbA [__link0]: https://github.com/casey/just [__link1]: https://rust-lang.github.io/rustfmt/ [__link10]: https://embarkstudios.github.io/cargo-deny/ @@ -419,9 +419,9 @@ This crate was developed as part of The Oxidizer Project. Br [__link22]: https://mutants.rs/ [__link23]: https://crates.io/crates/cargo-hack [__link24]: https://crates.io/crates/cargo-coverage-gate - [__link25]: https://docs.rs/cargo-anvil/0.11.0/cargo_anvil/?search=Catalog::anvil - [__link26]: https://docs.rs/cargo-anvil/0.11.0/cargo_anvil/?search=Artifact - [__link27]: https://docs.rs/cargo-anvil/0.11.0/cargo_anvil/fn.run_app.html + [__link25]: https://docs.rs/cargo-anvil/0.11.1/cargo_anvil/?search=Catalog::anvil + [__link26]: https://docs.rs/cargo-anvil/0.11.1/cargo_anvil/?search=Artifact + [__link27]: https://docs.rs/cargo-anvil/0.11.1/cargo_anvil/fn.run_app.html [__link3]: https://crates.io/crates/cargo-sort [__link4]: https://crates.io/crates/cargo-heather [__link5]: https://crates.io/crates/cargo-ensure-no-cyclic-deps diff --git a/crates/cargo-anvil/templates/justfiles/anvil/checks/llvm-cov.just b/crates/cargo-anvil/templates/justfiles/anvil/checks/llvm-cov.just index 99e1cb19..a4531ecb 100644 --- a/crates/cargo-anvil/templates/justfiles/anvil/checks/llvm-cov.just +++ b/crates/cargo-anvil/templates/justfiles/anvil/checks/llvm-cov.just @@ -131,7 +131,7 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact } $command = [regex]::Match( $failedProcess.Groups['command'].Value, - '(?s)^(?:"(?[^"]+)"|(?\S+))\s+export\s+(?.*)$' + '(?s)^(?:"(?[^"]+)"|''(?[^'']+)''|(?\S+))\s+export\s+(?.*)$' ) if (-not $command.Success) { Write-Warning 'anvil-llvm-cov: the failed process was not an llvm-cov export command' @@ -139,6 +139,8 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact } $llvmCov = if ($command.Groups['quoted'].Success) { $command.Groups['quoted'].Value + } elseif ($command.Groups['singleQuoted'].Success) { + $command.Groups['singleQuoted'].Value } else { $command.Groups['bare'].Value } @@ -147,10 +149,15 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact return $reportExitCode } $responsePath = [System.IO.Path]::GetFullPath("$OutputPath.llvm-cov.rsp") + $responseArguments = [regex]::Replace( + $command.Groups['arguments'].Value, + "'(?[^']*)'", + { param($match) '"' + $match.Groups['value'].Value.Replace('"', '\"') + '"' } + ) try { [System.IO.File]::WriteAllText( $responsePath, - $command.Groups['arguments'].Value, + $responseArguments, [System.Text.UTF8Encoding]::new($false) ) Write-Host 'anvil-llvm-cov: retrying Windows error 206 with an LLVM response file' diff --git a/crates/cargo-anvil/tests/recipe_contracts.rs b/crates/cargo-anvil/tests/recipe_contracts.rs index ae618668..d87229ef 100644 --- a/crates/cargo-anvil/tests/recipe_contracts.rs +++ b/crates/cargo-anvil/tests/recipe_contracts.rs @@ -359,9 +359,16 @@ if ($args -contains 'bolero' -and $args -contains 'list') { exit [int]$env:FAKE_BOLERO_LIST_EXIT } if ($args -contains 'llvm-cov' -and $args -contains 'report' -and $env:FAKE_LLVM_COV_REPORT_206) { - $command = "$([char]34)$($env:FAKE_LLVM_COV_PATH)$([char]34) export -format=lcov -instr-profile=fake.profdata -object fake-object.exe" + $quote = if ($env:FAKE_LLVM_COV_SINGLE_QUOTES) { [char]39 } else { [char]34 } + $arguments = if ($env:FAKE_LLVM_COV_SINGLE_QUOTES) { + "'-format=lcov' '-instr-profile=fake.profdata' '-object' 'fake-object.exe'" + } else { + '-format=lcov -instr-profile=fake.profdata -object fake-object.exe' + } + $command = "$quote$($env:FAKE_LLVM_COV_PATH)$quote export $arguments" if ($env:FAKE_LLVM_COV_MULTILINE) { - $command = $command.Replace(' -object', "`n-object") + $objectArgument = if ($env:FAKE_LLVM_COV_SINGLE_QUOTES) { " '-object'" } else { ' -object' } + $command = $command.Replace($objectArgument, "`n$($objectArgument.TrimStart())") } Write-Output ( "error: failed to generate report: could not execute process $([char]96)$command$([char]96) " + @@ -2372,6 +2379,10 @@ fn coverage_reports_use_requested_package_scope() { #[cfg(all(windows, not(target_arch = "aarch64")))] #[test] +#[expect( + clippy::too_many_lines, + reason = "one end-to-end fixture covers successful and failed response-file retries" +)] fn windows_coverage_report_retries_error_206_with_response_file() { if !tools_available() { return; @@ -2399,7 +2410,10 @@ fn windows_coverage_report_retries_error_206_with_response_file() { Add-Content -LiteralPath $env:FAKE_LLVM_COV_LOG -Value ($Remaining -join ' ')\n\ $response = $Remaining | Where-Object { $_.StartsWith('@') } | Select-Object -First 1\n\ if (-not $response) { exit 2 }\n\ - Add-Content -LiteralPath $env:FAKE_LLVM_COV_RESPONSE_LOG -Value (Get-Content -LiteralPath $response.Substring(1) -Raw)\n\ + $responseContent = Get-Content -LiteralPath $response.Substring(1) -Raw\n\ + if ($responseContent -match \"'\") { exit 3 }\n\ + if ($responseContent -notmatch '-instr-profile=fake.profdata') { exit 4 }\n\ + Add-Content -LiteralPath $env:FAKE_LLVM_COV_RESPONSE_LOG -Value $responseContent\n\ Write-Output 'TN:'\n\ if ($env:FAKE_LLVM_COV_EXIT) { exit [int]$env:FAKE_LLVM_COV_EXIT }\n\ exit 0\n", @@ -2412,6 +2426,7 @@ fn windows_coverage_report_retries_error_206_with_response_file() { ("FAKE_SECOND_PACKAGE_NAME", OsStr::new("measured")), ("FAKE_LLVM_COV_REPORT_206", OsStr::new("1")), ("FAKE_LLVM_COV_MULTILINE", OsStr::new("1")), + ("FAKE_LLVM_COV_SINGLE_QUOTES", OsStr::new("1")), ("FAKE_LLVM_COV_PATH", llvm_cov.as_os_str()), ("FAKE_LLVM_COV_LOG", llvm_cov_log.as_os_str()), ("FAKE_LLVM_COV_RESPONSE_LOG", response_log.as_os_str()), @@ -2428,7 +2443,11 @@ fn windows_coverage_report_retries_error_206_with_response_file() { assert_eq!(invocations.lines().count(), 2, "invocations:\n{invocations}"); assert_eq!(invocations.matches("export @").count(), 2, "invocations:\n{invocations}"); let responses = fs::read_to_string(&response_log).unwrap(); - assert_eq!(responses.matches("-object fake-object.exe").count(), 2, "responses:\n{responses}"); + assert_eq!( + responses.matches("\n\"-object\" \"fake-object.exe\"").count(), + 2, + "both reports must preserve the multiline command shape:\n{responses}" + ); for config in ["all-features", "no-default"] { let report = tmp.path().join(format!("target/coverage/lcov-{config}.info")); assert_eq!(fs::read_to_string(report).unwrap().trim(), "TN:"); @@ -2455,6 +2474,12 @@ fn windows_coverage_report_retries_error_206_with_response_file() { ], ); assert_failed(&failed, "failed response-file fallback"); + let invocations = fs::read_to_string(&llvm_cov_log).unwrap(); + assert_eq!( + invocations.lines().count(), + 3, + "both quote styles must reach llvm-cov:\n{invocations}" + ); let failed_report = tmp.path().join("target/coverage/lcov-all-features.info"); assert!(!failed_report.exists(), "failed response-file fallback must remove partial report"); assert!( diff --git a/crates/cargo-anvil/tests/snapshots/snapshots__ado_backend.snap b/crates/cargo-anvil/tests/snapshots/snapshots__ado_backend.snap index 86e4f837..75056f9c 100644 --- a/crates/cargo-anvil/tests/snapshots/snapshots__ado_backend.snap +++ b/crates/cargo-anvil/tests/snapshots/snapshots__ado_backend.snap @@ -2858,7 +2858,7 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact } $command = [regex]::Match( $failedProcess.Groups['command'].Value, - '(?s)^(?:"(?[^"]+)"|(?\S+))\s+export\s+(?.*)$' + '(?s)^(?:"(?[^"]+)"|''(?[^'']+)''|(?\S+))\s+export\s+(?.*)$' ) if (-not $command.Success) { Write-Warning 'anvil-llvm-cov: the failed process was not an llvm-cov export command' @@ -2866,6 +2866,8 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact } $llvmCov = if ($command.Groups['quoted'].Success) { $command.Groups['quoted'].Value + } elseif ($command.Groups['singleQuoted'].Success) { + $command.Groups['singleQuoted'].Value } else { $command.Groups['bare'].Value } @@ -2874,10 +2876,15 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact return $reportExitCode } $responsePath = [System.IO.Path]::GetFullPath("$OutputPath.llvm-cov.rsp") + $responseArguments = [regex]::Replace( + $command.Groups['arguments'].Value, + "'(?[^']*)'", + { param($match) '"' + $match.Groups['value'].Value.Replace('"', '\"') + '"' } + ) try { [System.IO.File]::WriteAllText( $responsePath, - $command.Groups['arguments'].Value, + $responseArguments, [System.Text.UTF8Encoding]::new($false) ) Write-Host 'anvil-llvm-cov: retrying Windows error 206 with an LLVM response file' diff --git a/crates/cargo-anvil/tests/snapshots/snapshots__github_backend.snap b/crates/cargo-anvil/tests/snapshots/snapshots__github_backend.snap index edbb39d9..29ecea51 100644 --- a/crates/cargo-anvil/tests/snapshots/snapshots__github_backend.snap +++ b/crates/cargo-anvil/tests/snapshots/snapshots__github_backend.snap @@ -2971,7 +2971,7 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact } $command = [regex]::Match( $failedProcess.Groups['command'].Value, - '(?s)^(?:"(?[^"]+)"|(?\S+))\s+export\s+(?.*)$' + '(?s)^(?:"(?[^"]+)"|''(?[^'']+)''|(?\S+))\s+export\s+(?.*)$' ) if (-not $command.Success) { Write-Warning 'anvil-llvm-cov: the failed process was not an llvm-cov export command' @@ -2979,6 +2979,8 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact } $llvmCov = if ($command.Groups['quoted'].Success) { $command.Groups['quoted'].Value + } elseif ($command.Groups['singleQuoted'].Success) { + $command.Groups['singleQuoted'].Value } else { $command.Groups['bare'].Value } @@ -2987,10 +2989,15 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact return $reportExitCode } $responsePath = [System.IO.Path]::GetFullPath("$OutputPath.llvm-cov.rsp") + $responseArguments = [regex]::Replace( + $command.Groups['arguments'].Value, + "'(?[^']*)'", + { param($match) '"' + $match.Groups['value'].Value.Replace('"', '\"') + '"' } + ) try { [System.IO.File]::WriteAllText( $responsePath, - $command.Groups['arguments'].Value, + $responseArguments, [System.Text.UTF8Encoding]::new($false) ) Write-Host 'anvil-llvm-cov: retrying Windows error 206 with an LLVM response file' diff --git a/crates/cargo-anvil/tests/snapshots/snapshots__local_only.snap b/crates/cargo-anvil/tests/snapshots/snapshots__local_only.snap index d2930f28..27a43ff4 100644 --- a/crates/cargo-anvil/tests/snapshots/snapshots__local_only.snap +++ b/crates/cargo-anvil/tests/snapshots/snapshots__local_only.snap @@ -1655,7 +1655,7 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact } $command = [regex]::Match( $failedProcess.Groups['command'].Value, - '(?s)^(?:"(?[^"]+)"|(?\S+))\s+export\s+(?.*)$' + '(?s)^(?:"(?[^"]+)"|''(?[^'']+)''|(?\S+))\s+export\s+(?.*)$' ) if (-not $command.Success) { Write-Warning 'anvil-llvm-cov: the failed process was not an llvm-cov export command' @@ -1663,6 +1663,8 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact } $llvmCov = if ($command.Groups['quoted'].Success) { $command.Groups['quoted'].Value + } elseif ($command.Groups['singleQuoted'].Success) { + $command.Groups['singleQuoted'].Value } else { $command.Groups['bare'].Value } @@ -1671,10 +1673,15 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact return $reportExitCode } $responsePath = [System.IO.Path]::GetFullPath("$OutputPath.llvm-cov.rsp") + $responseArguments = [regex]::Replace( + $command.Groups['arguments'].Value, + "'(?[^']*)'", + { param($match) '"' + $match.Groups['value'].Value.Replace('"', '\"') + '"' } + ) try { [System.IO.File]::WriteAllText( $responsePath, - $command.Groups['arguments'].Value, + $responseArguments, [System.Text.UTF8Encoding]::new($false) ) Write-Host 'anvil-llvm-cov: retrying Windows error 206 with an LLVM response file' diff --git a/justfiles/anvil/checks/llvm-cov.just b/justfiles/anvil/checks/llvm-cov.just index 99e1cb19..a4531ecb 100644 --- a/justfiles/anvil/checks/llvm-cov.just +++ b/justfiles/anvil/checks/llvm-cov.just @@ -131,7 +131,7 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact } $command = [regex]::Match( $failedProcess.Groups['command'].Value, - '(?s)^(?:"(?[^"]+)"|(?\S+))\s+export\s+(?.*)$' + '(?s)^(?:"(?[^"]+)"|''(?[^'']+)''|(?\S+))\s+export\s+(?.*)$' ) if (-not $command.Success) { Write-Warning 'anvil-llvm-cov: the failed process was not an llvm-cov export command' @@ -139,6 +139,8 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact } $llvmCov = if ($command.Groups['quoted'].Success) { $command.Groups['quoted'].Value + } elseif ($command.Groups['singleQuoted'].Success) { + $command.Groups['singleQuoted'].Value } else { $command.Groups['bare'].Value } @@ -147,10 +149,15 @@ anvil-llvm-cov: anvil-llvm-cov-validate-prereqs anvil-impact return $reportExitCode } $responsePath = [System.IO.Path]::GetFullPath("$OutputPath.llvm-cov.rsp") + $responseArguments = [regex]::Replace( + $command.Groups['arguments'].Value, + "'(?[^']*)'", + { param($match) '"' + $match.Groups['value'].Value.Replace('"', '\"') + '"' } + ) try { [System.IO.File]::WriteAllText( $responsePath, - $command.Groups['arguments'].Value, + $responseArguments, [System.Text.UTF8Encoding]::new($false) ) Write-Host 'anvil-llvm-cov: retrying Windows error 206 with an LLVM response file'