From fba918329131f43e97ae96438dcb9479c001b4f8 Mon Sep 17 00:00:00 2001 From: Tanmay Arya Date: Thu, 4 Dec 2025 13:14:58 +0530 Subject: [PATCH 1/2] add(tests): Show os errors with misconfigured target paths --- tests/testsuite/bad_config.rs | 443 ++++++++++++++++++++++++++++++++++ 1 file changed, 443 insertions(+) diff --git a/tests/testsuite/bad_config.rs b/tests/testsuite/bad_config.rs index ca90a7eaa9a..1dd44ba07c5 100644 --- a/tests/testsuite/bad_config.rs +++ b/tests/testsuite/bad_config.rs @@ -3755,3 +3755,446 @@ please set bin.path in Cargo.toml "#]]) .run(); } + +#[cargo_test] +fn nonexistent_example_target_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[example]] + name = "bar" + path = "examples/null.rs" + "#, + ) + .build(); + p.cargo("check --examples") + .with_status(101) + .with_stderr_data(str![[r#" +[CHECKING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `examples/null.rs`: [NOT_FOUND] + +[ERROR] could not compile `foo` (example "bar") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn nonexistent_library_target_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [lib] + path = "src/null.rs" + "#, + ) + .file("src/lib.rs", "fn bar() {}") + .build(); + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[CHECKING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null.rs`: [NOT_FOUND] + +[ERROR] could not compile `foo` (lib) due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn nonexistent_binary_target_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[bin]] + name = "null" + path = "src/null.rs" + "#, + ) + .build(); + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[CHECKING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null.rs`: [NOT_FOUND] + +[ERROR] could not compile `foo` (bin "null") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn nonexistent_test_target_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[test]] + name = "null" + path = "src/null.rs" + "#, + ) + .build(); + p.cargo("test") + .with_status(101) + .with_stderr_data(str![[r#" +[COMPILING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null.rs`: [NOT_FOUND] + +[ERROR] could not compile `foo` (test "null") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn nonexistent_bench_target_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[bench]] + name = "null" + path = "src/null.rs" + "#, + ) + .build(); + p.cargo("bench") + .with_status(101) + .with_stderr_data(str![[r#" +[COMPILING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null.rs`: [NOT_FOUND] + +[ERROR] could not compile `foo` (bench "null") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn directory_as_example_target_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[example]] + name = "bar" + path = "examples/bar" + "#, + ) + .file("examples/bar/.temp", "") + .build(); + p.cargo("check --example bar") + .with_status(101) + .with_stderr_data(str![[r#" +[CHECKING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `examples/bar`: Is a directory (os error 21) + +[ERROR] could not compile `foo` (example "bar") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn directory_as_library_target_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [lib] + path = "src/null" + "#, + ) + .file("src/null/.temp", "") + .build(); + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[CHECKING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null`: Is a directory (os error 21) + +[ERROR] could not compile `foo` (lib) due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn directory_as_binary_target_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[bin]] + name = "null" + path = "src/null" + "#, + ) + .file("src/null/.temp", "") + .build(); + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[CHECKING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null`: Is a directory (os error 21) + +[ERROR] could not compile `foo` (bin "null") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn directory_as_test_target_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[test]] + name = "null" + path = "src/null" + "#, + ) + .file("src/null/.temp", "") + .build(); + p.cargo("test") + .with_status(101) + .with_stderr_data(str![[r#" +[COMPILING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null`: Is a directory (os error 21) + +[ERROR] could not compile `foo` (test "null") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn directory_as_bench_target_path() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[bench]] + name = "null" + path = "src/null" + "#, + ) + .file("src/null/.temp", "") + .build(); + p.cargo("bench") + .with_status(101) + .with_stderr_data(str![[r#" +[COMPILING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null`: Is a directory (os error 21) + +[ERROR] could not compile `foo` (bench "null") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn directory_as_example_target_path_with_entrypoint() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[example]] + name = "bar" + path = "examples/bar" + "#, + ) + .file("examples/bar/main.rs", "fn main() {}") + .build(); + p.cargo("check --example bar") + .with_status(101) + .with_stderr_data(str![[r#" +[CHECKING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `examples/bar`: Is a directory (os error 21) + +[ERROR] could not compile `foo` (example "bar") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn directory_as_library_target_path_with_entrypoint() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [lib] + path = "src/null" + "#, + ) + .file("src/null/lib.rs", "fn foo() {}") + .build(); + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[CHECKING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null`: Is a directory (os error 21) + +[ERROR] could not compile `foo` (lib) due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn directory_as_binary_target_path_with_entrypoint() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[bin]] + name = "null" + path = "src/null" + "#, + ) + .file("src/null/main.rs", "fn main() {}") + .build(); + p.cargo("check") + .with_status(101) + .with_stderr_data(str![[r#" +[CHECKING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null`: Is a directory (os error 21) + +[ERROR] could not compile `foo` (bin "null") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn directory_as_test_target_path_with_entrypoint() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[test]] + name = "null" + path = "src/null" + "#, + ) + .file("src/null/main.rs", "fn main() {}") + .build(); + p.cargo("test") + .with_status(101) + .with_stderr_data(str![[r#" +[COMPILING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null`: Is a directory (os error 21) + +[ERROR] could not compile `foo` (test "null") due to 1 previous error + +"#]]) + .run(); +} + +#[cargo_test] +fn directory_as_bench_target_path_with_entrypoint() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + edition = "2024" + + [[bench]] + name = "null" + path = "src/null" + "#, + ) + .file("src/null/main.rs", "fn main() {}") + .build(); + p.cargo("bench") + .with_status(101) + .with_stderr_data(str![[r#" +[COMPILING] foo v1.0.0 ([ROOT]/foo) +[ERROR] couldn't read `src/null`: Is a directory (os error 21) + +[ERROR] could not compile `foo` (bench "null") due to 1 previous error + +"#]]) + .run(); +} From f203f097ed3a7a99ce39815f55c1d17179047f10 Mon Sep 17 00:00:00 2001 From: Tanmay Arya Date: Fri, 5 Dec 2025 22:49:48 +0530 Subject: [PATCH 2/2] fix(build-analysis): validate target source paths of root units --- src/cargo/ops/cargo_compile/mod.rs | 122 ++++++++++++++++++++++++++++- tests/testsuite/bad_config.rs | 110 ++++++++++++-------------- 2 files changed, 171 insertions(+), 61 deletions(-) diff --git a/src/cargo/ops/cargo_compile/mod.rs b/src/cargo/ops/cargo_compile/mod.rs index f95e907e6a6..cc90bddea93 100644 --- a/src/cargo/ops/cargo_compile/mod.rs +++ b/src/cargo/ops/cargo_compile/mod.rs @@ -60,7 +60,7 @@ use crate::util::log_message::LogMessage; use crate::util::{CargoResult, StableHasher}; mod compile_filter; -use annotate_snippets::Level; +use annotate_snippets::{Group, Level, Origin}; pub use compile_filter::{CompileFilter, FilterRule, LibRule}; pub(super) mod unit_generator; @@ -535,6 +535,27 @@ pub fn create_bcx<'a, 'gctx>( .extend(args); } + // Validate target src path for each root unit + let mut error_count: usize = 0; + for unit in &units { + if let Some(target_src_path) = unit.target.src_path().path() { + validate_target_path_as_source_file( + gctx, + target_src_path, + unit.target.name(), + unit.target.kind(), + unit.pkg.manifest_path(), + &mut error_count, + )? + } + } + if error_count > 0 { + let plural: &str = if error_count > 1 { "s" } else { "" }; + anyhow::bail!( + "could not compile due to {error_count} previous target resolution error{plural}" + ); + } + if honor_rust_version.unwrap_or(true) { let rustc_version = target_data.rustc.version.clone().into(); @@ -602,6 +623,105 @@ where `` is the latest version supporting rustc {rustc_version}" Ok(bcx) } +// Checks if a target path exists and is a source file, not a directory +fn validate_target_path_as_source_file( + gctx: &GlobalContext, + target_path: &std::path::Path, + target_name: &str, + target_kind: &TargetKind, + unit_manifest_path: &std::path::Path, + error_count: &mut usize, +) -> CargoResult<()> { + if !target_path.exists() { + *error_count += 1; + + let err_msg = format!( + "can't find {} `{}` at path `{}`", + target_kind.description(), + target_name, + target_path.display() + ); + + let group = Group::with_title(Level::ERROR.primary_title(err_msg)).element(Origin::path( + unit_manifest_path.to_str().unwrap_or_default(), + )); + + gctx.shell().print_report(&[group], true)?; + } else if target_path.is_dir() { + *error_count += 1; + + // suggest setting the path to a likely entrypoint + let main_rs = target_path.join("main.rs"); + let lib_rs = target_path.join("lib.rs"); + + let suggested_files_opt = match target_kind { + TargetKind::Lib(_) => { + if lib_rs.exists() { + Some(format!("`{}`", lib_rs.display())) + } else { + None + } + } + TargetKind::Bin => { + if main_rs.exists() { + Some(format!("`{}`", main_rs.display())) + } else { + None + } + } + TargetKind::Test => { + if main_rs.exists() { + Some(format!("`{}`", main_rs.display())) + } else { + None + } + } + TargetKind::ExampleBin => { + if main_rs.exists() { + Some(format!("`{}`", main_rs.display())) + } else { + None + } + } + TargetKind::Bench => { + if main_rs.exists() { + Some(format!("`{}`", main_rs.display())) + } else { + None + } + } + TargetKind::ExampleLib(_) => { + if lib_rs.exists() { + Some(format!("`{}`", lib_rs.display())) + } else { + None + } + } + TargetKind::CustomBuild => None, + }; + + let err_msg = format!( + "path `{}` for {} `{}` is a directory, but a source file was expected.", + target_path.display(), + target_kind.description(), + target_name, + ); + let mut group = Group::with_title(Level::ERROR.primary_title(err_msg)).element( + Origin::path(unit_manifest_path.to_str().unwrap_or_default()), + ); + + if let Some(suggested_files) = suggested_files_opt { + group = group.element( + Level::HELP.message(format!("an entry point exists at {}", suggested_files)), + ); + } + + gctx.shell().print_report(&[group], true)?; + } + + Ok(()) +} + /// This is used to rebuild the unit graph, sharing host dependencies if possible, /// and applying other unit adjustments based on the whole graph. /// diff --git a/tests/testsuite/bad_config.rs b/tests/testsuite/bad_config.rs index 1dd44ba07c5..a19395f63c9 100644 --- a/tests/testsuite/bad_config.rs +++ b/tests/testsuite/bad_config.rs @@ -3776,10 +3776,9 @@ fn nonexistent_example_target_path() { p.cargo("check --examples") .with_status(101) .with_stderr_data(str![[r#" -[CHECKING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `examples/null.rs`: [NOT_FOUND] - -[ERROR] could not compile `foo` (example "bar") due to 1 previous error +[ERROR] can't find example `bar` at path `[ROOT]/foo/examples/null.rs` + --> [ROOT]/foo/Cargo.toml +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -3805,10 +3804,9 @@ fn nonexistent_library_target_path() { p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" -[CHECKING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null.rs`: [NOT_FOUND] - -[ERROR] could not compile `foo` (lib) due to 1 previous error +[ERROR] can't find lib `foo` at path `[ROOT]/foo/src/null.rs` + --> [ROOT]/foo/Cargo.toml +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -3834,10 +3832,9 @@ fn nonexistent_binary_target_path() { p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" -[CHECKING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null.rs`: [NOT_FOUND] - -[ERROR] could not compile `foo` (bin "null") due to 1 previous error +[ERROR] can't find bin `null` at path `[ROOT]/foo/src/null.rs` + --> [ROOT]/foo/Cargo.toml +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -3863,10 +3860,9 @@ fn nonexistent_test_target_path() { p.cargo("test") .with_status(101) .with_stderr_data(str![[r#" -[COMPILING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null.rs`: [NOT_FOUND] - -[ERROR] could not compile `foo` (test "null") due to 1 previous error +[ERROR] can't find integration-test `null` at path `[ROOT]/foo/src/null.rs` + --> [ROOT]/foo/Cargo.toml +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -3892,10 +3888,9 @@ fn nonexistent_bench_target_path() { p.cargo("bench") .with_status(101) .with_stderr_data(str![[r#" -[COMPILING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null.rs`: [NOT_FOUND] - -[ERROR] could not compile `foo` (bench "null") due to 1 previous error +[ERROR] can't find bench `null` at path `[ROOT]/foo/src/null.rs` + --> [ROOT]/foo/Cargo.toml +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -3922,10 +3917,9 @@ fn directory_as_example_target_path() { p.cargo("check --example bar") .with_status(101) .with_stderr_data(str![[r#" -[CHECKING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `examples/bar`: Is a directory (os error 21) - -[ERROR] could not compile `foo` (example "bar") due to 1 previous error +[ERROR] path `[ROOT]/foo/examples/bar` for example `bar` is a directory, but a source file was expected. + --> [ROOT]/foo/Cargo.toml +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -3951,10 +3945,9 @@ fn directory_as_library_target_path() { p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" -[CHECKING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null`: Is a directory (os error 21) - -[ERROR] could not compile `foo` (lib) due to 1 previous error +[ERROR] path `[ROOT]/foo/src/null` for lib `foo` is a directory, but a source file was expected. + --> [ROOT]/foo/Cargo.toml +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -3981,10 +3974,9 @@ fn directory_as_binary_target_path() { p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" -[CHECKING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null`: Is a directory (os error 21) - -[ERROR] could not compile `foo` (bin "null") due to 1 previous error +[ERROR] path `[ROOT]/foo/src/null` for bin `null` is a directory, but a source file was expected. + --> [ROOT]/foo/Cargo.toml +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -4011,10 +4003,9 @@ fn directory_as_test_target_path() { p.cargo("test") .with_status(101) .with_stderr_data(str![[r#" -[COMPILING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null`: Is a directory (os error 21) - -[ERROR] could not compile `foo` (test "null") due to 1 previous error +[ERROR] path `[ROOT]/foo/src/null` for integration-test `null` is a directory, but a source file was expected. + --> [ROOT]/foo/Cargo.toml +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -4041,10 +4032,9 @@ fn directory_as_bench_target_path() { p.cargo("bench") .with_status(101) .with_stderr_data(str![[r#" -[COMPILING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null`: Is a directory (os error 21) - -[ERROR] could not compile `foo` (bench "null") due to 1 previous error +[ERROR] path `[ROOT]/foo/src/null` for bench `null` is a directory, but a source file was expected. + --> [ROOT]/foo/Cargo.toml +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -4071,10 +4061,10 @@ fn directory_as_example_target_path_with_entrypoint() { p.cargo("check --example bar") .with_status(101) .with_stderr_data(str![[r#" -[CHECKING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `examples/bar`: Is a directory (os error 21) - -[ERROR] could not compile `foo` (example "bar") due to 1 previous error +[ERROR] path `[ROOT]/foo/examples/bar` for example `bar` is a directory, but a source file was expected. + --> [ROOT]/foo/Cargo.toml + = [HELP] an entry point exists at `[ROOT]/foo/examples/bar/main.rs` +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -4100,10 +4090,10 @@ fn directory_as_library_target_path_with_entrypoint() { p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" -[CHECKING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null`: Is a directory (os error 21) - -[ERROR] could not compile `foo` (lib) due to 1 previous error +[ERROR] path `[ROOT]/foo/src/null` for lib `foo` is a directory, but a source file was expected. + --> [ROOT]/foo/Cargo.toml + = [HELP] an entry point exists at `[ROOT]/foo/src/null/lib.rs` +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -4130,10 +4120,10 @@ fn directory_as_binary_target_path_with_entrypoint() { p.cargo("check") .with_status(101) .with_stderr_data(str![[r#" -[CHECKING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null`: Is a directory (os error 21) - -[ERROR] could not compile `foo` (bin "null") due to 1 previous error +[ERROR] path `[ROOT]/foo/src/null` for bin `null` is a directory, but a source file was expected. + --> [ROOT]/foo/Cargo.toml + = [HELP] an entry point exists at `[ROOT]/foo/src/null/main.rs` +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -4160,10 +4150,10 @@ fn directory_as_test_target_path_with_entrypoint() { p.cargo("test") .with_status(101) .with_stderr_data(str![[r#" -[COMPILING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null`: Is a directory (os error 21) - -[ERROR] could not compile `foo` (test "null") due to 1 previous error +[ERROR] path `[ROOT]/foo/src/null` for integration-test `null` is a directory, but a source file was expected. + --> [ROOT]/foo/Cargo.toml + = [HELP] an entry point exists at `[ROOT]/foo/src/null/main.rs` +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run(); @@ -4190,10 +4180,10 @@ fn directory_as_bench_target_path_with_entrypoint() { p.cargo("bench") .with_status(101) .with_stderr_data(str![[r#" -[COMPILING] foo v1.0.0 ([ROOT]/foo) -[ERROR] couldn't read `src/null`: Is a directory (os error 21) - -[ERROR] could not compile `foo` (bench "null") due to 1 previous error +[ERROR] path `[ROOT]/foo/src/null` for bench `null` is a directory, but a source file was expected. + --> [ROOT]/foo/Cargo.toml + = [HELP] an entry point exists at `[ROOT]/foo/src/null/main.rs` +[ERROR] could not compile due to 1 previous target resolution error "#]]) .run();