Skip to content

Commit 0b4ad65

Browse files
committed
Trim trailing slashes to fix workspace discovery
Previously, workspace discovery would miss a valid workspace root is the project path has a trailing slash. This would cause `uv build` to use the wrong target directory. This is technically a breaking change (different directory), OTOH it's also a bugfix (wrong directory is currently used). Fixes #13914
1 parent 9214855 commit 0b4ad65

2 files changed

Lines changed: 70 additions & 0 deletions

File tree

crates/uv-workspace/src/workspace.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ impl Workspace {
201201
let path = std::path::absolute(path)
202202
.map_err(WorkspaceError::Normalize)?
203203
.clone();
204+
// Remove `.` and `..`
205+
let path = uv_fs::normalize_path(&path);
206+
// Trim trailing slashes.
207+
let path = path.components().collect::<PathBuf>();
204208

205209
let project_path = path
206210
.ancestors()
@@ -1363,6 +1367,10 @@ impl ProjectWorkspace {
13631367
let project_path = std::path::absolute(install_path)
13641368
.map_err(WorkspaceError::Normalize)?
13651369
.clone();
1370+
// Remove `.` and `..`
1371+
let project_path = uv_fs::normalize_path(&project_path);
1372+
// Trim trailing slashes.
1373+
let project_path = project_path.components().collect::<PathBuf>();
13661374

13671375
// Check if workspaces are explicitly disabled for the project.
13681376
if project_pyproject_toml

crates/uv/tests/it/build.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2084,3 +2084,65 @@ fn venv_included_in_sdist() -> Result<()> {
20842084

20852085
Ok(())
20862086
}
2087+
2088+
/// Ensure that workspace discovery works with and without trailing slash.
2089+
///
2090+
/// <https://github.com/astral-sh/uv/issues/13914>
2091+
#[test]
2092+
fn test_workspace_trailing_slash() {
2093+
let context = TestContext::new("3.12");
2094+
2095+
// Create a workspace with a root and a member.
2096+
context.init().arg("--lib").assert().success();
2097+
context.init().arg("--lib").arg("child").assert().success();
2098+
2099+
uv_snapshot!(context.filters(), context.build().arg("child"), @r"
2100+
success: true
2101+
exit_code: 0
2102+
----- stdout -----
2103+
2104+
----- stderr -----
2105+
Building source distribution (uv build backend)...
2106+
Building wheel from source distribution (uv build backend)...
2107+
Successfully built dist/child-0.1.0.tar.gz
2108+
Successfully built dist/child-0.1.0-py3-none-any.whl
2109+
");
2110+
2111+
// Check that workspace discovery still works.
2112+
uv_snapshot!(context.filters(), context.build().arg("child/"), @r"
2113+
success: true
2114+
exit_code: 0
2115+
----- stdout -----
2116+
2117+
----- stderr -----
2118+
Building source distribution (uv build backend)...
2119+
Building wheel from source distribution (uv build backend)...
2120+
Successfully built dist/child-0.1.0.tar.gz
2121+
Successfully built dist/child-0.1.0-py3-none-any.whl
2122+
");
2123+
2124+
// Check general normalization too.
2125+
uv_snapshot!(context.filters(), context.build().arg("./child/"), @r"
2126+
success: true
2127+
exit_code: 0
2128+
----- stdout -----
2129+
2130+
----- stderr -----
2131+
Building source distribution (uv build backend)...
2132+
Building wheel from source distribution (uv build backend)...
2133+
Successfully built dist/child-0.1.0.tar.gz
2134+
Successfully built dist/child-0.1.0-py3-none-any.whl
2135+
");
2136+
2137+
uv_snapshot!(context.filters(), context.build().arg("./child/../child/"), @r"
2138+
success: true
2139+
exit_code: 0
2140+
----- stdout -----
2141+
2142+
----- stderr -----
2143+
Building source distribution (uv build backend)...
2144+
Building wheel from source distribution (uv build backend)...
2145+
Successfully built dist/child-0.1.0.tar.gz
2146+
Successfully built dist/child-0.1.0-py3-none-any.whl
2147+
");
2148+
}

0 commit comments

Comments
 (0)