diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 455bbb5a44523..06bd7f34e9b5a 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -201,6 +201,10 @@ impl Workspace { let path = std::path::absolute(path) .map_err(WorkspaceError::Normalize)? .clone(); + // Remove `.` and `..` + let path = uv_fs::normalize_path(&path); + // Trim trailing slashes. + let path = path.components().collect::(); let project_path = path .ancestors() @@ -1363,6 +1367,10 @@ impl ProjectWorkspace { let project_path = std::path::absolute(install_path) .map_err(WorkspaceError::Normalize)? .clone(); + // Remove `.` and `..` + let project_path = uv_fs::normalize_path(&project_path); + // Trim trailing slashes. + let project_path = project_path.components().collect::(); // Check if workspaces are explicitly disabled for the project. if project_pyproject_toml diff --git a/crates/uv/tests/it/build.rs b/crates/uv/tests/it/build.rs index b18690b527ff1..6d55a5a01d431 100644 --- a/crates/uv/tests/it/build.rs +++ b/crates/uv/tests/it/build.rs @@ -2084,3 +2084,65 @@ fn venv_included_in_sdist() -> Result<()> { Ok(()) } + +/// Ensure that workspace discovery works with and without trailing slash. +/// +/// +#[test] +fn test_workspace_trailing_slash() { + let context = TestContext::new("3.12"); + + // Create a workspace with a root and a member. + context.init().arg("--lib").assert().success(); + context.init().arg("--lib").arg("child").assert().success(); + + uv_snapshot!(context.filters(), context.build().arg("child"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building source distribution (uv build backend)... + Building wheel from source distribution (uv build backend)... + Successfully built dist/child-0.1.0.tar.gz + Successfully built dist/child-0.1.0-py3-none-any.whl + "); + + // Check that workspace discovery still works. + uv_snapshot!(context.filters(), context.build().arg("child/"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building source distribution (uv build backend)... + Building wheel from source distribution (uv build backend)... + Successfully built dist/child-0.1.0.tar.gz + Successfully built dist/child-0.1.0-py3-none-any.whl + "); + + // Check general normalization too. + uv_snapshot!(context.filters(), context.build().arg("./child/"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building source distribution (uv build backend)... + Building wheel from source distribution (uv build backend)... + Successfully built dist/child-0.1.0.tar.gz + Successfully built dist/child-0.1.0-py3-none-any.whl + "); + + uv_snapshot!(context.filters(), context.build().arg("./child/../child/"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Building source distribution (uv build backend)... + Building wheel from source distribution (uv build backend)... + Successfully built dist/child-0.1.0.tar.gz + Successfully built dist/child-0.1.0-py3-none-any.whl + "); +}