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
14 changes: 10 additions & 4 deletions src/cargo/core/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1129,16 +1129,22 @@ impl<'gctx> Workspace<'gctx> {
let current_dir = self.current_manifest.parent().unwrap();
let root_pkg = self.packages.get(root);

// FIXME: Make this more generic by using a relative path resolver between member and root.
let members_msg = match current_dir.strip_prefix(root_dir) {
Ok(rel) => format!(
// Use pathdiff to handle finding the relative path between the current package
// and the workspace root. This usually does a good job of handling `..` and
// other weird things.
// Normalize paths first to ensure `../` components are resolved if possible,
// which helps `diff_paths` find the most direct relative path.
let current_dir = paths::normalize_path(current_dir);
let root_dir = paths::normalize_path(root_dir);
let members_msg = match pathdiff::diff_paths(&current_dir, &root_dir) {
Some(rel) => format!(
"this may be fixable by adding `{}` to the \
`workspace.members` array of the manifest \
located at: {}",
rel.display(),
root.display()
),
Err(_) => format!(
None => format!(
"this may be fixable by adding a member to \
the `workspace.members` array of the \
manifest located at: {}",
Expand Down
123 changes: 123 additions & 0 deletions tests/testsuite/workspaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2755,3 +2755,126 @@ fn fix_only_check_manifest_path_member() {
"#]])
.run();
}

#[cargo_test]
fn error_if_member_is_outside_root() {
// This test ensures that if a member is physically outside the workspace root,
// we get a helpful error message.
//
// Setup:
// root/Cargo.toml (workspace, members = [])
// member/Cargo.toml (package, workspace = "../root")

let _root = project()
.at("root")
.file(
"Cargo.toml",
r#"
[package]
name = "root"
version = "0.1.0"
edition = "2015"

[workspace]
members = []
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

let member = project()
.at("member")
.file(
"Cargo.toml",
r#"
[package]
name = "member"
version = "0.1.0"
edition = "2015"
workspace = "../root"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

// The error message should reflect that these paths are unrelated (Old Behavior)
member.cargo("build")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] current package believes it's in a workspace when it's not:
current: [ROOT]/member/Cargo.toml
workspace: [ROOT]/root/Cargo.toml

this may be fixable by adding `../member` to the `workspace.members` array of the manifest located at: [ROOT]/root/Cargo.toml
Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest.

"#]])
.run();
}

#[cargo_test]
fn error_if_manifest_path_is_relative() {
// This test simulates running cargo usage with a relative --manifest-path
// that includes `..` to verify normalization and suggestions.
//
// Directory structure:
// root/Cargo.toml
// root/subdir/
// outside/Cargo.toml (workspace = "../root")

let root = project()
.at("root")
.file(
"Cargo.toml",
r#"
[package]
name = "root"
version = "0.1.0"
edition = "2015"

[workspace]
members = []
"#,
)
.file("src/main.rs", "fn main() {}")
.file("subdir/file", "")
.build();

let _outside = project()
.at("outside")
.file(
"Cargo.toml",
r#"
[package]
name = "outside"
version = "0.1.0"
edition = "2015"
workspace = "../root"
"#,
)
.file("src/main.rs", "fn main() {}")
.build();

// Run from `root/subdir` pointing to `../../outside/Cargo.toml`
// The workspace root is at `root`.
// The package is at `outside`.
// Relative path from root to outside is `../outside`.

// We execute inside `root`, but targeting the outside package.
root.cargo("build")
.cwd(root.root().join("subdir"))
.arg("-v")
.arg("--manifest-path")
.arg("../../outside/Cargo.toml")
.with_status(101)
.with_stderr_data(str![[r#"
[ERROR] current package believes it's in a workspace when it's not:
current: [ROOT]/outside/Cargo.toml
workspace: [ROOT]/root/Cargo.toml

this may be fixable by adding `../outside` to the `workspace.members` array of the manifest located at: [ROOT]/root/Cargo.toml
Alternatively, to keep it out of the workspace, add the package to the `workspace.exclude` array, or add an empty `[workspace]` table to the package's manifest.

"#]])
.run();
}