From 5fd203e2753d8dc8fd3f35d88f39caa216cd0d66 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Wed, 15 Jul 2026 20:48:05 -0500 Subject: [PATCH 1/8] Reuse compiled workspace exclusion patterns --- .../uv-bench/benches/workspace_discovery.rs | 31 ++++-- crates/uv-workspace/src/workspace.rs | 96 +++++++++++++++++-- 2 files changed, 111 insertions(+), 16 deletions(-) diff --git a/crates/uv-bench/benches/workspace_discovery.rs b/crates/uv-bench/benches/workspace_discovery.rs index 6809a303ed7..a29415bffe5 100644 --- a/crates/uv-bench/benches/workspace_discovery.rs +++ b/crates/uv-bench/benches/workspace_discovery.rs @@ -21,6 +21,7 @@ const EXCLUDE_NEWER: &str = "2024-08-08"; /// Mirroring the airflow workspace size at time of writing. const MEMBER_COUNT: usize = 127; +const EXCLUDE_COUNT: usize = 128; const OPTIONAL_DEPENDENCY_GROUP_COUNT: usize = 122; const DEPENDENCY_GROUP_COUNT: usize = 64; const UNUSED_ROOT_TABLE_COUNT: usize = 128; @@ -36,7 +37,7 @@ fn provider_requirement(member_index: usize) -> String { /// Create a synthetic workspace with a root and many members, returning the directories to run /// discovery from. -fn create_workspace(root: &Path) -> Vec { +fn create_workspace(root: &Path, exclude_count: usize) -> Vec { let mut discovery_roots = Vec::with_capacity(MEMBER_COUNT + 1); discovery_roots.push(root.to_path_buf()); @@ -53,13 +54,13 @@ fn create_workspace(root: &Path) -> Vec { discovery_roots.push(member_root); } - fs_err::write(root.join("pyproject.toml"), root_pyproject()) + fs_err::write(root.join("pyproject.toml"), root_pyproject(exclude_count)) .expect("Failed to write workspace root pyproject.toml"); discovery_roots } -fn root_pyproject() -> String { +fn root_pyproject(exclude_count: usize) -> String { let dependencies: Vec = (0..MEMBER_COUNT).map(provider_requirement).collect(); let optional_dependencies: toml::Table = (0..OPTIONAL_DEPENDENCY_GROUP_COUNT) @@ -104,6 +105,10 @@ fn root_pyproject() -> String { }) .collect(); + let excludes: Vec = (0..exclude_count) + .map(|exclude_index| format!("./packages/excluded-{exclude_index:03}")) + .collect(); + // Generate some unrelated work for the toml parser, mimicking real tool configuration. let generated: toml::Table = (0..UNUSED_ROOT_TABLE_COUNT) .map(|table_index| { @@ -171,6 +176,7 @@ fn root_pyproject() -> String { [tool.uv.workspace] members = ["packages/*"] + exclude = (excludes) [tool.linter] generated = (generated) @@ -258,8 +264,20 @@ fn member_pyproject(member_index: usize) -> String { } fn discover_workspace_from_all_members(c: &mut Criterion) { + discover_workspace(c, "discover_workspace_from_all_members", 0); +} + +fn discover_workspace_from_all_members_with_excludes(c: &mut Criterion) { + discover_workspace( + c, + "discover_workspace_from_all_members_with_excludes", + EXCLUDE_COUNT, + ); +} + +fn discover_workspace(c: &mut Criterion, name: &str, exclude_count: usize) { let dir = tempfile::tempdir().expect("Failed to create temporary directory"); - let discovery_roots = create_workspace(dir.path()); + let discovery_roots = create_workspace(dir.path(), exclude_count); let cache = Cache::from_path(dir.path().join(".uv-cache")); let options = DiscoveryOptions::default(); let runtime = tokio::runtime::Builder::new_current_thread() @@ -267,7 +285,7 @@ fn discover_workspace_from_all_members(c: &mut Criterion) { .build() .expect("Failed to create Tokio runtime"); - c.bench_function("discover_workspace_from_all_members", |b| { + c.bench_function(name, |b| { b.iter(|| { let workspace_cache = WorkspaceCache::default(); for root in &discovery_roots { @@ -287,7 +305,7 @@ fn discover_workspace_from_all_members(c: &mut Criterion) { fn run_python_version_synthetic_workspace(c: &mut Criterion) { let workspace_dir = tempfile::tempdir().expect("Failed to create temporary directory"); - create_workspace(workspace_dir.path()); + create_workspace(workspace_dir.path(), 0); let cache_dir = workspace_dir.path().join(".uv-cache"); let runtime = tokio::runtime::Builder::new_current_thread() @@ -365,6 +383,7 @@ fn run_cli( criterion_group!( workspace_discovery, discover_workspace_from_all_members, + discover_workspace_from_all_members_with_excludes, run_python_version_synthetic_workspace ); criterion_main!(workspace_discovery); diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index ff8fe3bfc81..4aa0bfbba3a 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -1158,6 +1158,12 @@ impl Workspace { ); } + let exclude_patterns = if matches!(options.members, MemberDiscovery::None) { + Vec::new() + } else { + workspace_exclude_patterns(workspace_root, workspace_definition)? + }; + // Add all other workspace members. for member_glob in workspace_definition.clone().members.unwrap_or_default() { // Normalize the member glob to remove leading `./` and other relative path components @@ -1205,7 +1211,10 @@ impl Workspace { } // If the member is excluded, ignore it. - if is_excluded_from_workspace(&member_root, workspace_root, workspace_definition)? { + if exclude_patterns + .iter() + .any(|pattern| pattern.matches_path(&member_root)) + { debug!( "Ignoring workspace member: `{}`", member_root.simplified_display() @@ -1947,15 +1956,7 @@ fn is_excluded_from_workspace( workspace: &ToolUvWorkspace, ) -> Result { for exclude_glob in workspace.exclude.iter().flatten() { - // Normalize the exclude glob to remove leading `./` and other relative path components - let normalized_glob = normalize_path(Path::new(exclude_glob.as_str())); - let absolute_glob = PathBuf::from(glob::Pattern::escape( - workspace_root.simplified().to_string_lossy().as_ref(), - )) - .join(normalized_glob.as_ref()); - let absolute_glob = absolute_glob.to_string_lossy(); - let exclude_pattern = glob::Pattern::new(&absolute_glob) - .map_err(|err| WorkspaceErrorKind::Pattern(absolute_glob.to_string(), err))?; + let exclude_pattern = workspace_exclude_pattern(workspace_root, exclude_glob)?; if exclude_pattern.matches_path(project_path) { return Ok(true); } @@ -1963,6 +1964,35 @@ fn is_excluded_from_workspace( Ok(false) } +/// Compile the normalized workspace exclusion patterns. +fn workspace_exclude_patterns( + workspace_root: &Path, + workspace: &ToolUvWorkspace, +) -> Result, WorkspaceError> { + workspace + .exclude + .iter() + .flatten() + .map(|exclude_glob| workspace_exclude_pattern(workspace_root, exclude_glob)) + .collect() +} + +/// Compile a normalized workspace exclusion pattern. +fn workspace_exclude_pattern( + workspace_root: &Path, + exclude_glob: &glob::Pattern, +) -> Result { + // Normalize the exclude glob to remove leading `./` and other relative path components. + let normalized_glob = normalize_path(Path::new(exclude_glob.as_str())); + let absolute_glob = PathBuf::from(glob::Pattern::escape( + workspace_root.simplified().to_string_lossy().as_ref(), + )) + .join(normalized_glob.as_ref()); + let absolute_glob = absolute_glob.to_string_lossy(); + glob::Pattern::new(&absolute_glob) + .map_err(|err| WorkspaceErrorKind::Pattern(absolute_glob.to_string(), err).into()) +} + /// Check if we're in the `tool.uv.workspace.members` of a workspace. fn is_included_in_workspace( project_path: &Path, @@ -3298,6 +3328,52 @@ mod tests { Ok(()) } + #[tokio::test] + async fn exclude_package_with_normalized_glob_and_escaped_root() -> Result<()> { + let temp_dir = tempfile::TempDir::new()?; + let root = ChildPath::new(temp_dir.path()).child("workspace[glob]?"); + + root.child("pyproject.toml").write_str( + r#" + [project] + name = "albatross" + version = "0.1.0" + requires-python = ">=3.12" + + [tool.uv.workspace] + members = ["./packages/*"] + exclude = ["./ignored/../packages/excluded", "./packages/./excluded-glob-*"] + "#, + )?; + + for member in ["included", "excluded", "excluded-glob-one"] { + root.child("packages") + .child(member) + .child("pyproject.toml") + .write_str(&format!( + r#" + [project] + name = "{member}" + version = "0.1.0" + requires-python = ">=3.12" + "#, + ))?; + } + + let (project, _) = temporary_test(root.as_ref()).await.unwrap(); + assert_json_snapshot!( + project.workspace().packages().keys().collect::>(), + @r#" + [ + "albatross", + "included" + ] + "# + ); + + Ok(()) + } + #[test] fn read_dependency_groups() { let toml = r#" From 71c8b2c4c1fe471507f3bd1d411782ffc98a2faa Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 6 Aug 2026 08:41:56 -0500 Subject: [PATCH 2/8] Centralize workspace exclusion matching --- crates/uv-workspace/src/workspace.rs | 85 +++++++++++++++------------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 4aa0bfbba3a..02e698705b3 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -8,7 +8,7 @@ use std::hash::BuildHasherDefault; use std::path::{Path, PathBuf}; use std::sync::Arc; -use glob::{GlobError, PatternError, glob}; +use glob::{GlobError, Pattern, PatternError, glob}; use itertools::Itertools; use rustc_hash::{FxHashSet, FxHasher}; use tracing::{debug, trace, warn}; @@ -1158,10 +1158,10 @@ impl Workspace { ); } - let exclude_patterns = if matches!(options.members, MemberDiscovery::None) { - Vec::new() + let exclusions = if matches!(options.members, MemberDiscovery::None) { + WorkspaceExclusions::default() } else { - workspace_exclude_patterns(workspace_root, workspace_definition)? + WorkspaceExclusions::new(workspace_root, workspace_definition)? }; // Add all other workspace members. @@ -1211,10 +1211,7 @@ impl Workspace { } // If the member is excluded, ignore it. - if exclude_patterns - .iter() - .any(|pattern| pattern.matches_path(&member_root)) - { + if exclusions.matches(&member_root) { debug!( "Ignoring workspace member: `{}`", member_root.simplified_display() @@ -1955,42 +1952,50 @@ fn is_excluded_from_workspace( workspace_root: &Path, workspace: &ToolUvWorkspace, ) -> Result { - for exclude_glob in workspace.exclude.iter().flatten() { - let exclude_pattern = workspace_exclude_pattern(workspace_root, exclude_glob)?; - if exclude_pattern.matches_path(project_path) { - return Ok(true); - } - } - Ok(false) + Ok(WorkspaceExclusions::new(workspace_root, workspace)?.matches(project_path)) } -/// Compile the normalized workspace exclusion patterns. -fn workspace_exclude_patterns( - workspace_root: &Path, - workspace: &ToolUvWorkspace, -) -> Result, WorkspaceError> { - workspace - .exclude - .iter() - .flatten() - .map(|exclude_glob| workspace_exclude_pattern(workspace_root, exclude_glob)) - .collect() +/// Compiled workspace exclusion patterns. +#[derive(Debug, Default)] +struct WorkspaceExclusions { + patterns: Vec, } -/// Compile a normalized workspace exclusion pattern. -fn workspace_exclude_pattern( - workspace_root: &Path, - exclude_glob: &glob::Pattern, -) -> Result { - // Normalize the exclude glob to remove leading `./` and other relative path components. - let normalized_glob = normalize_path(Path::new(exclude_glob.as_str())); - let absolute_glob = PathBuf::from(glob::Pattern::escape( - workspace_root.simplified().to_string_lossy().as_ref(), - )) - .join(normalized_glob.as_ref()); - let absolute_glob = absolute_glob.to_string_lossy(); - glob::Pattern::new(&absolute_glob) - .map_err(|err| WorkspaceErrorKind::Pattern(absolute_glob.to_string(), err).into()) +impl WorkspaceExclusions { + /// Compile the normalized workspace exclusion patterns. + fn new(workspace_root: &Path, workspace: &ToolUvWorkspace) -> Result { + let patterns = workspace + .exclude + .iter() + .flatten() + .map(|exclude_glob| Self::compile_pattern(workspace_root, exclude_glob)) + .collect::>()?; + + Ok(Self { patterns }) + } + + /// Return whether any workspace exclusion matches the project path. + fn matches(&self, project_path: &Path) -> bool { + self.patterns + .iter() + .any(|pattern| pattern.matches_path(project_path)) + } + + /// Compile a normalized workspace exclusion pattern. + fn compile_pattern( + workspace_root: &Path, + exclude_glob: &Pattern, + ) -> Result { + // Normalize the exclude glob to remove leading `./` and other relative path components. + let normalized_glob = normalize_path(Path::new(exclude_glob.as_str())); + let absolute_glob = PathBuf::from(Pattern::escape( + workspace_root.simplified().to_string_lossy().as_ref(), + )) + .join(normalized_glob.as_ref()); + let absolute_glob = absolute_glob.to_string_lossy(); + Pattern::new(&absolute_glob) + .map_err(|err| WorkspaceErrorKind::Pattern(absolute_glob.to_string(), err).into()) + } } /// Check if we're in the `tool.uv.workspace.members` of a workspace. From a7f86c3ec3a1be2ec537ae62db4bd91e2d6636e1 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 6 Aug 2026 08:42:16 -0500 Subject: [PATCH 3/8] Initialize workspace exclusions only when needed --- crates/uv-workspace/src/workspace.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 02e698705b3..af4153f8c3a 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -1158,11 +1158,7 @@ impl Workspace { ); } - let exclusions = if matches!(options.members, MemberDiscovery::None) { - WorkspaceExclusions::default() - } else { - WorkspaceExclusions::new(workspace_root, workspace_definition)? - }; + let mut exclusions = None; // Add all other workspace members. for member_glob in workspace_definition.clone().members.unwrap_or_default() { @@ -1211,7 +1207,16 @@ impl Workspace { } // If the member is excluded, ignore it. - if exclusions.matches(&member_root) { + if exclusions.is_none() { + exclusions = Some(WorkspaceExclusions::new( + workspace_root, + workspace_definition, + )?); + } + if exclusions + .as_ref() + .is_some_and(|exclusions| exclusions.matches(&member_root)) + { debug!( "Ignoring workspace member: `{}`", member_root.simplified_display() @@ -1956,7 +1961,7 @@ fn is_excluded_from_workspace( } /// Compiled workspace exclusion patterns. -#[derive(Debug, Default)] +#[derive(Debug)] struct WorkspaceExclusions { patterns: Vec, } From a4329f9cf800f0d4377b7b804119cc17b27dcdc7 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 6 Aug 2026 08:43:34 -0500 Subject: [PATCH 4/8] Reuse parsed workspace exclusion patterns --- crates/uv-workspace/src/workspace.rs | 84 +++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index af4153f8c3a..2d7bca6f911 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -1,5 +1,6 @@ //! Resolve the current [`ProjectWorkspace`] or [`Workspace`]. +use std::borrow::Cow; use std::collections::{BTreeMap, BTreeSet}; use std::error::Error; use std::fmt; @@ -1962,13 +1963,24 @@ fn is_excluded_from_workspace( /// Compiled workspace exclusion patterns. #[derive(Debug)] -struct WorkspaceExclusions { - patterns: Vec, +struct WorkspaceExclusions<'workspace> { + workspace_root: &'workspace Path, + patterns: Vec>, } -impl WorkspaceExclusions { +/// A workspace exclusion that can reuse its parsed pattern or requires normalization. +#[derive(Debug)] +enum WorkspaceExclusion<'workspace> { + Relative(&'workspace Pattern), + Absolute(Pattern), +} + +impl<'workspace> WorkspaceExclusions<'workspace> { /// Compile the normalized workspace exclusion patterns. - fn new(workspace_root: &Path, workspace: &ToolUvWorkspace) -> Result { + fn new( + workspace_root: &'workspace Path, + workspace: &'workspace ToolUvWorkspace, + ) -> Result { let patterns = workspace .exclude .iter() @@ -1976,29 +1988,45 @@ impl WorkspaceExclusions { .map(|exclude_glob| Self::compile_pattern(workspace_root, exclude_glob)) .collect::>()?; - Ok(Self { patterns }) + Ok(Self { + workspace_root, + patterns, + }) } /// Return whether any workspace exclusion matches the project path. fn matches(&self, project_path: &Path) -> bool { - self.patterns - .iter() - .any(|pattern| pattern.matches_path(project_path)) + let relative_path = project_path + .simplified() + .strip_prefix(self.workspace_root.simplified()) + .ok(); + + self.patterns.iter().any(|pattern| match pattern { + WorkspaceExclusion::Relative(pattern) => { + relative_path.is_some_and(|relative_path| pattern.matches_path(relative_path)) + } + WorkspaceExclusion::Absolute(pattern) => pattern.matches_path(project_path), + }) } - /// Compile a normalized workspace exclusion pattern. + /// Reuse an already compiled relative pattern or compile its normalized absolute equivalent. fn compile_pattern( workspace_root: &Path, - exclude_glob: &Pattern, - ) -> Result { + exclude_glob: &'workspace Pattern, + ) -> Result, WorkspaceError> { // Normalize the exclude glob to remove leading `./` and other relative path components. let normalized_glob = normalize_path(Path::new(exclude_glob.as_str())); + if matches!(&normalized_glob, Cow::Borrowed(_)) && normalized_glob.is_relative() { + return Ok(WorkspaceExclusion::Relative(exclude_glob)); + } + let absolute_glob = PathBuf::from(Pattern::escape( workspace_root.simplified().to_string_lossy().as_ref(), )) .join(normalized_glob.as_ref()); let absolute_glob = absolute_glob.to_string_lossy(); Pattern::new(&absolute_glob) + .map(WorkspaceExclusion::Absolute) .map_err(|err| WorkspaceErrorKind::Pattern(absolute_glob.to_string(), err).into()) } } @@ -3341,7 +3369,8 @@ mod tests { #[tokio::test] async fn exclude_package_with_normalized_glob_and_escaped_root() -> Result<()> { let temp_dir = tempfile::TempDir::new()?; - let root = ChildPath::new(temp_dir.path()).child("workspace[glob]?"); + let temp_dir_root = ChildPath::new(temp_dir.path()); + let root = temp_dir_root.child("workspace[glob]?"); root.child("pyproject.toml").write_str( r#" @@ -3351,12 +3380,22 @@ mod tests { requires-python = ">=3.12" [tool.uv.workspace] - members = ["./packages/*"] - exclude = ["./ignored/../packages/excluded", "./packages/./excluded-glob-*"] + members = ["./packages/*", "../external-*"] + exclude = [ + "packages/excluded-borrowed-*", + "./ignored/../packages/excluded", + "./packages/./excluded-glob-*", + "../external-excluded", + ] "#, )?; - for member in ["included", "excluded", "excluded-glob-one"] { + for member in [ + "included", + "excluded", + "excluded-glob-one", + "excluded-borrowed-one", + ] { root.child("packages") .child(member) .child("pyproject.toml") @@ -3370,12 +3409,27 @@ mod tests { ))?; } + for member in ["external-included", "external-excluded"] { + temp_dir_root + .child(member) + .child("pyproject.toml") + .write_str(&format!( + r#" + [project] + name = "{member}" + version = "0.1.0" + requires-python = ">=3.12" + "#, + ))?; + } + let (project, _) = temporary_test(root.as_ref()).await.unwrap(); assert_json_snapshot!( project.workspace().packages().keys().collect::>(), @r#" [ "albatross", + "external-included", "included" ] "# From edef15232d86d3a32192666e0246eda36f2d0236 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 6 Aug 2026 08:43:59 -0500 Subject: [PATCH 5/8] Preserve the baseline workspace benchmark fixture --- crates/uv-bench/benches/workspace_discovery.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/uv-bench/benches/workspace_discovery.rs b/crates/uv-bench/benches/workspace_discovery.rs index a29415bffe5..5b69ad116ec 100644 --- a/crates/uv-bench/benches/workspace_discovery.rs +++ b/crates/uv-bench/benches/workspace_discovery.rs @@ -105,9 +105,15 @@ fn root_pyproject(exclude_count: usize) -> String { }) .collect(); - let excludes: Vec = (0..exclude_count) - .map(|exclude_index| format!("./packages/excluded-{exclude_index:03}")) - .collect(); + let mut workspace = toml::toml! { + members = ["packages/*"] + }; + if exclude_count > 0 { + let excludes: Vec = (0..exclude_count) + .map(|exclude_index| format!("./packages/excluded-{exclude_index:03}")) + .collect(); + workspace.insert("exclude".to_string(), toml::Value::from(excludes)); + } // Generate some unrelated work for the toml parser, mimicking real tool configuration. let generated: toml::Table = (0..UNUSED_ROOT_TABLE_COUNT) @@ -173,10 +179,7 @@ fn root_pyproject(exclude_count: usize) -> String { [tool.uv] package = false sources = (sources) - - [tool.uv.workspace] - members = ["packages/*"] - exclude = (excludes) + workspace = (workspace) [tool.linter] generated = (generated) From e508fc36635cbfb4f752c787ad71721eb1234567 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 6 Aug 2026 08:44:09 -0500 Subject: [PATCH 6/8] Benchmark reusable workspace exclusion patterns --- crates/uv-bench/benches/workspace_discovery.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv-bench/benches/workspace_discovery.rs b/crates/uv-bench/benches/workspace_discovery.rs index 5b69ad116ec..b247e3248c4 100644 --- a/crates/uv-bench/benches/workspace_discovery.rs +++ b/crates/uv-bench/benches/workspace_discovery.rs @@ -110,7 +110,7 @@ fn root_pyproject(exclude_count: usize) -> String { }; if exclude_count > 0 { let excludes: Vec = (0..exclude_count) - .map(|exclude_index| format!("./packages/excluded-{exclude_index:03}")) + .map(|exclude_index| format!("packages/excluded-{exclude_index:03}")) .collect(); workspace.insert("exclude".to_string(), toml::Value::from(excludes)); } From 8565e5bd1fc2068eb6d73b9f96e5c21a7bfc2a98 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 6 Aug 2026 08:44:30 -0500 Subject: [PATCH 7/8] Propagate workspace exclusion test failures --- crates/uv-workspace/src/workspace.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 2d7bca6f911..03716ee85aa 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -3423,7 +3423,9 @@ mod tests { ))?; } - let (project, _) = temporary_test(root.as_ref()).await.unwrap(); + let (project, _) = temporary_test(root.as_ref()) + .await + .map_err(|(error, _)| error)?; assert_json_snapshot!( project.workspace().packages().keys().collect::>(), @r#" From c0bb4195f85aadb79dc69e31e000093f055600fc Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 6 Aug 2026 08:52:57 -0500 Subject: [PATCH 8/8] Use standard lazy initialization for workspace exclusions --- crates/uv-workspace/src/workspace.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 03716ee85aa..ed3ae17a1b2 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -1159,6 +1159,7 @@ impl Workspace { ); } + // Prepare exclusions only after finding a member that is not explicitly ignored. let mut exclusions = None; // Add all other workspace members. @@ -1208,15 +1209,13 @@ impl Workspace { } // If the member is excluded, ignore it. - if exclusions.is_none() { - exclusions = Some(WorkspaceExclusions::new( - workspace_root, - workspace_definition, - )?); - } if exclusions + .get_or_insert_with(|| { + WorkspaceExclusions::new(workspace_root, workspace_definition) + }) .as_ref() - .is_some_and(|exclusions| exclusions.matches(&member_root)) + .map_err(WorkspaceError::clone)? + .matches(&member_root) { debug!( "Ignoring workspace member: `{}`",