diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index 156ea24ca55..d46a87b5cf1 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -96,16 +96,27 @@ pub(crate) async fn export( ExportTarget::Script(script) } else { let project = if frozen.is_some() { - VirtualProject::discover( - project_dir, - &DiscoveryOptions { - members: MemberDiscovery::None, - ..DiscoveryOptions::default() + let options = DiscoveryOptions { + members: if package.is_empty() { + MemberDiscovery::None + } else { + MemberDiscovery::Existing }, - cache, - workspace_cache, - ) - .await? + ..DiscoveryOptions::default() + }; + + if let [name] = package.as_slice() { + VirtualProject::discover_with_package( + project_dir, + &options, + cache, + workspace_cache, + name.clone(), + ) + .await? + } else { + VirtualProject::discover(project_dir, &options, cache, workspace_cache).await? + } } else if let [name] = package.as_slice() { VirtualProject::discover_with_package( project_dir, diff --git a/crates/uv/src/commands/project/install_target.rs b/crates/uv/src/commands/project/install_target.rs index ac6ca37dea4..0ed5986f5f6 100644 --- a/crates/uv/src/commands/project/install_target.rs +++ b/crates/uv/src/commands/project/install_target.rs @@ -151,6 +151,12 @@ impl<'lock> Installable<'lock> for InstallTarget<'lock> { return true; } + // Workspace-root groups must be requested explicitly when a member is selected. + // Defaults belong to the selected member, not to an inherited workspace root. + if groups.contains_because_default(group) { + return false; + } + !workspace.packages().get(*name).is_some_and(|member| { let pyproject = member.pyproject_toml(); pyproject diff --git a/crates/uv/tests/project/export.rs b/crates/uv/tests/project/export.rs index 2400ccaa2cb..2113d0cae63 100644 --- a/crates/uv/tests/project/export.rs +++ b/crates/uv/tests/project/export.rs @@ -1051,7 +1051,6 @@ fn requirements_txt_workspace_member_ignores_root_default_group() -> Result<()> .arg("--package").arg("child"), @" exit_code: 0 (success) ----- stdout ----- - iniconfig==2.0.0 typing-extensions==4.10.0 "); @@ -1062,7 +1061,6 @@ fn requirements_txt_workspace_member_ignores_root_default_group() -> Result<()> .arg("--package").arg("child"), @" exit_code: 0 (success) ----- stdout ----- - iniconfig==2.0.0 typing-extensions==4.10.0 ----- stderr ----- @@ -1134,7 +1132,6 @@ fn requirements_txt_frozen_workspace_member_group_precedence() -> Result<()> { exit_code: 0 (success) ----- stdout ----- iniconfig==2.0.0 - sniffio==1.3.1 typing-extensions==4.10.0 "); @@ -1148,7 +1145,6 @@ fn requirements_txt_frozen_workspace_member_group_precedence() -> Result<()> { exit_code: 0 (success) ----- stdout ----- iniconfig==2.0.0 - sniffio==1.3.1 typing-extensions==4.10.0 "); @@ -1163,7 +1159,6 @@ fn requirements_txt_frozen_workspace_member_group_precedence() -> Result<()> { ----- stdout ----- idna==3.6 iniconfig==2.0.0 - sniffio==1.3.1 typing-extensions==4.10.0 "); diff --git a/crates/uv/tests/project/run.rs b/crates/uv/tests/project/run.rs index 66ae5951286..26fa24db3cd 100644 --- a/crates/uv/tests/project/run.rs +++ b/crates/uv/tests/project/run.rs @@ -6577,13 +6577,12 @@ fn run_target_workspace_discovery_workspace_group_defaults() -> Result<()> { .arg("child/scripts/groups.py"), @" exit_code: 0 (success) ----- stdout ----- - installed: typing_extensions, sniffio + installed: typing_extensions ----- stderr ----- Resolved 6 packages in [TIME] - Prepared 2 packages in [TIME] - Installed 2 packages in [TIME] - + sniffio==1.3.1 + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + typing-extensions==4.10.0 "); diff --git a/crates/uv/tests/sync/sync.rs b/crates/uv/tests/sync/sync.rs index d67bd4cc7ea..579fde66cc3 100644 --- a/crates/uv/tests/sync/sync.rs +++ b/crates/uv/tests/sync/sync.rs @@ -1348,12 +1348,38 @@ fn sync_non_project_dev_dependencies() -> Result<()> { + urllib3==2.2.1 "); - // Selecting a member still includes the non-project root's default dependency group. + // Selecting a member excludes the non-project root's default dependency group. uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child"), @" exit_code: 0 (success) ----- stderr ----- Resolved 11 packages in [TIME] - Checked 10 packages in [TIME] + Uninstalled 8 packages in [TIME] + - anyio==4.3.0 + - certifi==2024.2.2 + - charset-normalizer==3.3.2 + - idna==3.6 + - pysocks==1.7.1 + - requests==2.31.0 + - sniffio==1.3.1 + - urllib3==2.2.1 + "); + + // Explicitly requesting the root's group still includes its dependencies. + uv_snapshot!(context.filters(), context.sync() + .arg("--package").arg("child") + .arg("--group").arg("dev"), @" + exit_code: 0 (success) + ----- stderr ----- + Resolved 11 packages in [TIME] + Installed 8 packages in [TIME] + + anyio==4.3.0 + + certifi==2024.2.2 + + charset-normalizer==3.3.2 + + idna==3.6 + + pysocks==1.7.1 + + requests==2.31.0 + + sniffio==1.3.1 + + urllib3==2.2.1 "); Ok(())