Allow non-root packages in uv sync

This commit is contained in:
Charlie Marsh 2025-10-31 22:06:59 -04:00
parent 31d031c319
commit e03b033de0
3 changed files with 100 additions and 34 deletions

View File

@ -65,7 +65,7 @@ pub trait Installable<'lock> {
for root_name in self.roots() { for root_name in self.roots() {
let dist = self let dist = self
.lock() .lock()
.find_by_name(root_name) .find_by_markers(root_name, marker_env.markers())
.map_err(|_| LockErrorKind::MultipleRootPackages { .map_err(|_| LockErrorKind::MultipleRootPackages {
name: root_name.clone(), name: root_name.clone(),
})? })?
@ -97,7 +97,7 @@ pub trait Installable<'lock> {
for root_name in self.roots() { for root_name in self.roots() {
let dist = self let dist = self
.lock() .lock()
.find_by_name(root_name) .find_by_markers(root_name, marker_env.markers())
.map_err(|_| LockErrorKind::MultipleRootPackages { .map_err(|_| LockErrorKind::MultipleRootPackages {
name: root_name.clone(), name: root_name.clone(),
})? })?

View File

@ -3,7 +3,7 @@ use std::ops::Deref;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use anyhow::{Context, Result}; use anyhow::Result;
use itertools::Itertools; use itertools::Itertools;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use serde::Serialize; use serde::Serialize;
@ -99,39 +99,15 @@ pub(crate) async fn sync(
SyncTarget::Script(script) SyncTarget::Script(script)
} else { } else {
// Identify the project. // Identify the project.
let project = if frozen { let discovery = if frozen {
VirtualProject::discover( DiscoveryOptions {
project_dir,
&DiscoveryOptions {
members: MemberDiscovery::None, members: MemberDiscovery::None,
..DiscoveryOptions::default() ..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 { } else {
let project = VirtualProject::discover( DiscoveryOptions::default()
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",));
}
}
project
}; };
let project = VirtualProject::discover(project_dir, &discovery, &workspace_cache).await?;
// TODO(lucab): improve warning content // TODO(lucab): improve warning content
// <https://github.com/astral-sh/uv/issues/7428> // <https://github.com/astral-sh/uv/issues/7428>

View File

@ -377,6 +377,96 @@ fn multiple_packages() -> Result<()> {
Ok(()) 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 json output
#[test] #[test]
fn sync_json() -> Result<()> { fn sync_json() -> Result<()> {