diff --git a/crates/uv-resolver/src/lock/installable.rs b/crates/uv-resolver/src/lock/installable.rs index b6d93904f..36534b497 100644 --- a/crates/uv-resolver/src/lock/installable.rs +++ b/crates/uv-resolver/src/lock/installable.rs @@ -65,7 +65,7 @@ pub trait Installable<'lock> { for root_name in self.roots() { let dist = self .lock() - .find_by_name(root_name) + .find_by_markers(root_name, marker_env.markers()) .map_err(|_| LockErrorKind::MultipleRootPackages { name: root_name.clone(), })? @@ -97,7 +97,7 @@ pub trait Installable<'lock> { for root_name in self.roots() { let dist = self .lock() - .find_by_name(root_name) + .find_by_markers(root_name, marker_env.markers()) .map_err(|_| LockErrorKind::MultipleRootPackages { name: root_name.clone(), })? diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index f0934617c..2fe64af1c 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -3,7 +3,7 @@ use std::ops::Deref; use std::path::Path; use std::sync::Arc; -use anyhow::{Context, Result}; +use anyhow::Result; use itertools::Itertools; use owo_colors::OwoColorize; use serde::Serialize; @@ -99,39 +99,15 @@ pub(crate) async fn sync( SyncTarget::Script(script) } else { // Identify the project. - let project = if frozen { - VirtualProject::discover( - project_dir, - &DiscoveryOptions { - members: MemberDiscovery::None, - ..DiscoveryOptions::default() - }, - &workspace_cache, - ) - .await? - } else if let [name] = package.as_slice() { - VirtualProject::Project( - Workspace::discover(project_dir, &DiscoveryOptions::default(), &workspace_cache) - .await? - .with_current_project(name.clone()) - .with_context(|| format!("Package `{name}` not found in workspace"))?, - ) - } else { - let project = VirtualProject::discover( - project_dir, - &DiscoveryOptions::default(), - &workspace_cache, - ) - .await?; - - for name in &package { - if !project.workspace().packages().contains_key(name) { - return Err(anyhow::anyhow!("Package `{name}` not found in workspace",)); - } + let discovery = if frozen { + DiscoveryOptions { + members: MemberDiscovery::None, + ..DiscoveryOptions::default() } - - project + } else { + DiscoveryOptions::default() }; + let project = VirtualProject::discover(project_dir, &discovery, &workspace_cache).await?; // TODO(lucab): improve warning content // diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index 46095a838..3384f2d7e 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -377,6 +377,96 @@ fn multiple_packages() -> Result<()> { Ok(()) } +/// Sync a non-workspace member. +#[test] +fn non_root() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "root" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "anyio==4.3.0 ; sys_platform == 'darwin'", + "anyio==4.2.0 ; sys_platform == 'win32'", + ] + "#, + )?; + + context.lock().assert().success(); + + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("anyio"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "); + + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("iniconfig"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + error: Could not find root package `iniconfig` + "); + + Ok(()) +} + +/// Attempt to sync an ambiguous non-workspace member. +#[test] +fn non_root_non_unique() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "root" + version = "0.1.0" + requires-python = ">=3.12" + + [project.optional-dependencies] + foo = ["anyio==4.3.0"] + bar = ["anyio==4.2.0"] + + [tool.uv] + conflicts = [ + [ + { extra = "foo" }, + { extra = "bar" }, + ], + ] + "#, + )?; + + context.lock().assert().success(); + + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("anyio"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + error: Found multiple packages matching `anyio` + "); + + Ok(()) +} + /// Test json output #[test] fn sync_json() -> Result<()> {