Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,30 +77,40 @@ jobs:
- run: cargo install --path . --debug
- name: Test cargo llvm-cov nextest
run: |
unset RUSTFLAGS
cargo llvm-cov nextest --text --fail-under-lines 50
cargo llvm-cov nextest --text --fail-under-lines 50 --profile default --cargo-profile dev
cargo llvm-cov nextest --text --fail-under-lines 50 --profile ci
cargo llvm-cov nextest --text --fail-under-lines 50 --profile ci --cargo-profile dev
cargo clean
cd -- ../real1
cargo llvm-cov nextest-archive --archive-file a.tar.zst
cargo llvm-cov nextest --archive-file a.tar.zst --text --fail-under-lines 70
cargo llvm-cov report --nextest-archive-file a.tar.zst --fail-under-lines 70
rm -- a.tar.zst
cargo clean
rm -- a.tar.zst
cargo llvm-cov nextest-archive --archive-file a.tar.zst --release
cargo llvm-cov nextest --archive-file a.tar.zst --text --fail-under-lines 70
cargo llvm-cov report --nextest-archive-file a.tar.zst --fail-under-lines 70
rm -- a.tar.zst
cargo clean
rm -- a.tar.zst
cargo llvm-cov nextest-archive --archive-file a.tar.zst --cargo-profile custom
cargo llvm-cov nextest --archive-file a.tar.zst --text --fail-under-lines 70
cargo llvm-cov report --nextest-archive-file a.tar.zst --fail-under-lines 70
rm -- a.tar.zst
cargo clean
rm -- a.tar.zst
host=$(rustc -vV | grep -E '^host:' | cut -d' ' -f2)
cargo llvm-cov nextest-archive --archive-file a.tar.zst --target "${host}"
cargo llvm-cov nextest --archive-file a.tar.zst --text --fail-under-lines 70
cargo llvm-cov report --nextest-archive-file a.tar.zst --fail-under-lines 70
cargo clean
rm -- a.tar.zst
cd -- ../build_dir
cargo llvm-cov nextest --text --fail-under-lines 100
cargo llvm-cov nextest --text --fail-under-lines 100 --profile default --cargo-profile dev
cargo llvm-cov nextest --text --fail-under-lines 100 --profile ci
cargo llvm-cov nextest --text --fail-under-lines 100 --profile ci --cargo-profile dev
cargo clean
working-directory: tests/fixtures/crates/bin_crate
- name: Test nightly-specific options, old Cargo compatibility, trybuild compatibility
run: |
Expand Down
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ Note: In this file, do not use the hard wrap in the middle of a sentence for com

## [Unreleased]

- Update minimal version of `cargo-config2` to 0.1.38 to improve support for target names that contain ".". ([#446](https://github.com/taiki-e/cargo-llvm-cov/pull/446))
- Support Cargo `build-dir` that added in Cargo 1.91. ([#452](https://github.com/taiki-e/cargo-llvm-cov/pull/452))

- Update minimal version of `cargo-config2` to 0.1.38 to improve support for target names that contain ".". ([#446](https://github.com/taiki-e/cargo-llvm-cov/issues/446))

## [0.6.19] - 2025-09-07

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,7 @@ You may need to click the "Watch" label in the bottom bar of VS Code to display
You can override these environment variables to change cargo-llvm-cov's behavior on your system:

- `CARGO_LLVM_COV_TARGET_DIR` -- Location of where to place all generated artifacts, relative to the current working directory. Default to `<cargo_target_dir>/llvm-cov-target`.
- `CARGO_LLVM_COV_BUILD_DIR` -- Location of where intermediate build artifacts will be stored, relative to the current working directory. Default to `<cargo_target_dir>/llvm-cov-target`.
- `CARGO_LLVM_COV_SETUP` -- Control behavior if `llvm-tools-preview` component is not installed. See [#219] for more.
- `LLVM_COV` -- Override the path to `llvm-cov`. You may need to specify both this and `LLVM_PROFDATA` environment variables if you are using [`--include-ffi` flag](#get-coverage-of-cc-code-linked-to-rust-librarybinary) or if you are using a toolchain installed without via rustup. `llvm-cov` version must be compatible with the LLVM version used in rustc.
- `LLVM_PROFDATA` -- Override the path to `llvm-profdata`. See `LLVM_COV` environment variable for more.
Expand Down
60 changes: 39 additions & 21 deletions src/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use camino::{Utf8Path, Utf8PathBuf};
use cargo_config2::Config;

use crate::{
cli::{Args, ManifestOptions, Subcommand},
cli::{ManifestOptions, Subcommand},
context::Context,
env,
metadata::Metadata,
Expand All @@ -21,6 +21,7 @@ pub(crate) struct Workspace {
pub(crate) current_manifest: Utf8PathBuf,

pub(crate) target_dir: Utf8PathBuf,
pub(crate) build_dir: Option<Utf8PathBuf>,
pub(crate) output_dir: Utf8PathBuf,
pub(crate) doctests_dir: Utf8PathBuf,
pub(crate) profdata_file: Utf8PathBuf,
Expand Down Expand Up @@ -92,18 +93,32 @@ impl Workspace {
.is_ok_and(|s| s.contains("doctest-in-workspace"));
}

let target_dir =
if let Some(path) = env::var("CARGO_LLVM_COV_TARGET_DIR")?.map(Utf8PathBuf::from) {
let mut base: Utf8PathBuf = env::current_dir()?.try_into()?;
base.push(path);
let (target_dir, build_dir) = if let Some(mut target_dir) =
env::var("CARGO_LLVM_COV_TARGET_DIR")?.map(Utf8PathBuf::from)
{
let mut base: Utf8PathBuf = env::current_dir()?.try_into()?;
target_dir = base.join(target_dir);
let build_dir = if let Some(build_dir) =
env::var("CARGO_LLVM_COV_BUILD_DIR")?.map(Utf8PathBuf::from)
{
base.push(build_dir);
base
} else if show_env {
metadata.target_directory.clone()
} else {
// If we change RUSTFLAGS, all dependencies will be recompiled. Therefore,
// use a subdirectory of the target directory as the actual target directory.
metadata.target_directory.join("llvm-cov-target")
target_dir.clone()
};
(target_dir, build_dir)
} else if show_env {
(metadata.target_directory.clone(), metadata.build_directory().to_owned())
} else {
// If we change RUSTFLAGS, all dependencies will be recompiled. Therefore,
// use a subdirectory of the target directory as the actual target directory.
(
metadata.target_directory.join("llvm-cov-target"),
metadata.build_directory().join("llvm-cov-target"),
)
};
// The scope of --target-dir's effect depends on whether build-dir is specified in the config.
let build_dir = config.build.build_dir.as_ref().and(Some(build_dir));
let output_dir = metadata.target_directory.join("llvm-cov");
let doctests_dir = target_dir.join("doctestbins");

Expand All @@ -116,6 +131,7 @@ impl Workspace {
metadata,
current_manifest,
target_dir,
build_dir,
output_dir,
doctests_dir,
profdata_file,
Expand Down Expand Up @@ -222,7 +238,16 @@ pub(crate) fn test_or_run_args(cx: &Context, cmd: &mut ProcessBuilder) {
cmd.arg("--manifest-path");
cmd.arg(&cx.ws.current_manifest);

add_target_dir(&cx.args, cmd, &cx.ws.target_dir);
// https://github.com/taiki-e/cargo-llvm-cov/issues/265
if matches!(cx.args.subcommand, Subcommand::Nextest { archive_file: true }) {
cmd.arg("--extract-to");
} else {
cmd.arg("--target-dir");
}
cmd.arg(cx.ws.target_dir.as_str());
if let Some(build_dir) = &cx.ws.build_dir {
cmd.env("CARGO_BUILD_BUILD_DIR", build_dir.as_str());
}

for cargo_arg in &cx.args.cargo_args {
cmd.arg(cargo_arg);
Expand Down Expand Up @@ -257,6 +282,9 @@ pub(crate) fn clean_args(cx: &Context, cmd: &mut ProcessBuilder) {

cmd.arg("--target-dir");
cmd.arg(cx.ws.target_dir.as_str());
if let Some(build_dir) = &cx.ws.build_dir {
cmd.env("CARGO_BUILD_BUILD_DIR", build_dir.as_str());
}

cx.args.manifest.cargo_args(cmd);

Expand All @@ -265,13 +293,3 @@ pub(crate) fn clean_args(cx: &Context, cmd: &mut ProcessBuilder) {
cmd.arg(format!("-{}", "v".repeat(cx.args.verbose as usize - 1)));
}
}

// https://github.com/taiki-e/cargo-llvm-cov/issues/265
fn add_target_dir(args: &Args, cmd: &mut ProcessBuilder, target_dir: &Utf8Path) {
if matches!(args.subcommand, Subcommand::Nextest { archive_file: true }) {
cmd.arg("--extract-to");
} else {
cmd.arg("--target-dir");
}
cmd.arg(target_dir.as_str());
}
6 changes: 6 additions & 0 deletions src/clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ pub(crate) fn run(args: &mut Args) -> Result<()> {
for dir in &[&ws.target_dir, &ws.output_dir] {
rm_rf(dir, args.verbose != 0)?;
}
if let Some(dir) = &ws.build_dir {
rm_rf(dir, args.verbose != 0)?;
}
return Ok(());
}

Expand Down Expand Up @@ -92,6 +95,9 @@ fn clean_ws(
for args in args_set {
let mut cmd = ws.cargo(verbose);
cmd.args(["clean", "--target-dir", ws.target_dir.as_str()]).args(&package_args);
if let Some(build_dir) = &ws.build_dir {
cmd.env("CARGO_BUILD_BUILD_DIR", build_dir.as_str());
}
cmd.args(args);
if verbose > 0 {
cmd.arg(format!("-{}", "v".repeat(verbose as usize)));
Expand Down
8 changes: 8 additions & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,14 @@ impl Context {
if args.subcommand.call_cargo_nextest() { "cargo-nextest" } else { "cargo" }
);
}
if ws.config.build.build_dir.is_some()
&& matches!(
args.subcommand,
Subcommand::Nextest { archive_file: true } | Subcommand::NextestArchive
)
{
warn!("nextest archive may not work with Cargo build-dir");
}

let (llvm_cov, llvm_profdata): (PathBuf, PathBuf) = match (
env::var_os("LLVM_COV").map(PathBuf::from),
Expand Down
36 changes: 33 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ fn try_main() -> Result<()> {
};
set_env(cx, writer, IsNextest(true))?; // Include envs for nextest.
writer.set("CARGO_LLVM_COV_TARGET_DIR", cx.ws.metadata.target_directory.as_str())?;
writer.set("CARGO_LLVM_COV_BUILD_DIR", cx.ws.metadata.build_directory().as_str())?;
writer.writer.flush()?;
}
Subcommand::Report { .. } => {
Expand Down Expand Up @@ -791,8 +792,10 @@ fn object_files(cx: &Context) -> Result<Vec<OsString>> {
// This is not the ideal way, but the way unstable book says it is cannot support them.
// https://doc.rust-lang.org/nightly/rustc/instrument-coverage.html#tips-for-listing-the-binaries-automatically
let mut target_dir = cx.ws.target_dir.clone();
let mut build_dir = cx.ws.build_dir.clone();
let mut auto_detect_profile = false;
if cx.args.subcommand.read_nextest_archive() {
// TODO: build-dir
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct BinariesMetadata {
Expand Down Expand Up @@ -852,9 +855,12 @@ fn object_files(cx: &Context) -> Result<Vec<OsString>> {
}
}
if !auto_detect_profile {
// https://doc.rust-lang.org/nightly/cargo/guide/build-cache.html
// https://doc.rust-lang.org/nightly/cargo/reference/build-cache.html
if let Some(target) = &cx.args.target {
target_dir.push(target);
if let Some(build_dir) = &mut build_dir {
build_dir.push(target);
}
}
// https://doc.rust-lang.org/nightly/cargo/reference/profiles.html#custom-profiles
let profile = match cx.args.cargo_profile.as_deref() {
Expand All @@ -864,6 +870,9 @@ fn object_files(cx: &Context) -> Result<Vec<OsString>> {
Some(p) => p,
};
target_dir.push(profile);
if let Some(build_dir) = &mut build_dir {
build_dir.push(profile);
}
}
for f in walk_target_dir(cx, &target_dir) {
let f = f.path();
Expand All @@ -876,6 +885,22 @@ fn object_files(cx: &Context) -> Result<Vec<OsString>> {
}
}
searched_dir.push_str(target_dir.as_str());
if let Some(build_dir) = &build_dir {
if target_dir != *build_dir {
for f in walk_target_dir(cx, build_dir) {
let f = f.path();
if is_object(cx, f) {
if let Some(file_stem) = fs::file_stem_recursive(f).unwrap().to_str() {
if re.is_match(file_stem) {
files.push(make_relative(cx, f).to_owned().into_os_string());
}
}
}
}
searched_dir.push(',');
searched_dir.push_str(build_dir.as_str());
}
}
if cx.args.doctests {
for f in glob::glob(
Utf8Path::new(&glob::Pattern::escape(cx.ws.doctests_dir.as_str()))
Expand Down Expand Up @@ -931,10 +956,10 @@ fn object_files(cx: &Context) -> Result<Vec<OsString>> {
files.sort_unstable();

if files.is_empty() {
warn!(
bail!(
"not found object files (searched directories: {searched_dir}); this may occur if \
show-env subcommand is used incorrectly (see docs or other warnings), or unsupported \
commands are used",
commands or configs are used",
);
}
Ok(files)
Expand Down Expand Up @@ -1313,6 +1338,11 @@ fn ignore_filename_regex(cx: &Context, object_files: &[OsString]) -> Result<Opti
));
}
out.push_abs_path(&cx.ws.target_dir);
if let Some(build_dir) = &cx.ws.build_dir {
if *build_dir != cx.ws.target_dir {
out.push_abs_path(build_dir);
}
}
if cx.args.remap_path_prefix {
if let Some(path) = env::home_dir() {
out.push_abs_path(path);
Expand Down
20 changes: 14 additions & 6 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use std::{collections::HashMap, ffi::OsStr, path::Path};

use anyhow::{Context as _, Result, format_err};
use camino::Utf8PathBuf;
use camino::{Utf8Path, Utf8PathBuf};
use serde_json::{Map, Value};

type Object = Map<String, Value>;
Expand Down Expand Up @@ -33,6 +33,8 @@ pub(crate) struct Metadata {
/// The absolute path to the root of the workspace.
pub(crate) workspace_root: Utf8PathBuf,
pub(crate) target_directory: Utf8PathBuf,
/// This is always `None` if running with a version of Cargo older than 1.91.
build_directory: Option<Utf8PathBuf>,
}

impl Metadata {
Expand Down Expand Up @@ -67,8 +69,18 @@ impl Metadata {
workspace_members,
workspace_root: map.remove_string("workspace_root")?,
target_directory: map.remove_string("target_directory")?,
// This field was added in Rust 1.91.
build_directory: if map.contains_key("build_directory") {
Some(map.remove_string("build_directory")?)
} else {
None
},
})
}

pub(crate) fn build_directory(&self) -> &Utf8Path {
self.build_directory.as_deref().unwrap_or(&self.target_directory)
}
}

pub(crate) struct Package {
Expand Down Expand Up @@ -110,11 +122,7 @@ impl Target {

// #[allow(clippy::option_option)]
// fn allow_null<T>(value: Value, f: impl FnOnce(Value) -> Option<T>) -> Option<Option<T>> {
// if value.is_null() {
// Some(None)
// } else {
// f(value).map(Some)
// }
// if value.is_null() { Some(None) } else { f(value).map(Some) }
// }

fn into_string<S: From<String>>(value: Value) -> Option<S> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"coverage":{"src/lib.rs":{"1":"1/1","2":"2/3","3":"1/1","6":"1/1","10":"2/2","11":"1/1"}}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
1| 1|fn func(x: i32) -> bool {
2| 1| if x < 0 { true } else { false }
^0
3| 1|}
4| |
5| |#[test]
6| 1|fn test() {
7| | #[cfg(a)]
8| | assert!(!func(1));
9| | #[cfg(not(a))]
10| 1| assert!(func(-1));
11| 1|}
Loading