From 485e0d27489608524d8b61eb8889d9b1e188c26c Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Tue, 27 Aug 2024 21:39:46 -0400 Subject: [PATCH] Avoid including non-excluded members in parent workspaces (#6735) ## Summary If you're in a directory, and there's workspace above it, we check if the directory is excluded from the workspace members... But not if it's _included_ in the first place. Closes https://github.com/astral-sh/uv/issues/6732. --- crates/uv-workspace/src/workspace.rs | 26 +++++++++++- crates/uv/tests/workspace.rs | 60 ++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 3ef58426952a..7509dd4f8675 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -1006,6 +1006,14 @@ async fn find_workspace( .and_then(|tool| tool.uv.as_ref()) .and_then(|uv| uv.workspace.as_ref()) { + if !is_included_in_workspace(project_root, workspace_root, workspace)? { + debug!( + "Found workspace root `{}`, but project is not included", + workspace_root.simplified_display() + ); + return Ok(None); + } + if is_excluded_from_workspace(project_root, workspace_root, workspace)? { debug!( "Found workspace root `{}`, but project is excluded", @@ -1101,6 +1109,21 @@ pub fn check_nested_workspaces(inner_workspace_root: &Path, options: &DiscoveryO .and_then(|tool| tool.uv.as_ref()) .and_then(|uv| uv.workspace.as_ref()) { + let is_included = match is_included_in_workspace( + inner_workspace_root, + outer_workspace_root, + workspace, + ) { + Ok(contents) => contents, + Err(err) => { + warn!( + "Invalid pyproject.toml `{}`: {err}", + pyproject_toml_path.simplified_display() + ); + return; + } + }; + let is_excluded = match is_excluded_from_workspace( inner_workspace_root, outer_workspace_root, @@ -1115,7 +1138,8 @@ pub fn check_nested_workspaces(inner_workspace_root: &Path, options: &DiscoveryO return; } }; - if !is_excluded { + + if is_included && !is_excluded { warn_user!( "Nested workspaces are not supported, but outer workspace (`{}`) includes `{}`", outer_workspace_root.simplified_display().cyan(), diff --git a/crates/uv/tests/workspace.rs b/crates/uv/tests/workspace.rs index 7f452ba05a6f..eac9d7e175d9 100644 --- a/crates/uv/tests/workspace.rs +++ b/crates/uv/tests/workspace.rs @@ -948,6 +948,66 @@ fn workspace_hidden_member() -> Result<()> { Ok(()) } +/// Ensure that workspace discovery accepts valid hidden directories. +#[test] +fn workspace_non_included_member() -> Result<()> { + let context = TestContext::new("3.12"); + + // Build the main workspace ... + let workspace = context.temp_dir.child("workspace"); + workspace.child("pyproject.toml").write_str(indoc! {r#" + [tool.uv.workspace] + members = ["packages/*"] + "#})?; + + // ... with a ... + let deps = indoc! {r#" + dependencies = ["b"] + + [tool.uv.sources] + b = { workspace = true } + "#}; + make_project(&workspace.join("packages").join("a"), "a", deps)?; + + // ... and b. + let deps = indoc! {r" + dependencies = [] + "}; + make_project(&workspace.join("packages").join("b"), "b", deps)?; + + // ... and c, which is _not_ a member, but also isn't explicitly excluded. + let deps = indoc! {r" + dependencies = [] + "}; + make_project(&workspace.join("c"), "c", deps)?; + + // Locking from `c` should not include any workspace members. + uv_snapshot!(context.filters(), context.lock().current_dir(workspace.join("c")), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using Python 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 1 package in [TIME] + "### + ); + + let lock: SourceLock = toml::from_str(&fs_err::read_to_string( + workspace.join("c").join("uv.lock"), + )?)?; + + assert_json_snapshot!(lock.sources(), @r###" + { + "c": { + "editable": "." + } + } + "###); + + Ok(()) +} + /// Ensure workspace members inherit sources from the root, if not specified in the member. /// /// In such cases, relative paths should be resolved relative to the workspace root, rather than