mirror of
https://github.com/astral-sh/uv
synced 2026-06-21 13:47:25 +00:00
Treat workspace members with an intermediate pyproject.toml as standalone (#19926)
## Summary Based on #19924. Handle the case where a `pyproject.toml` sits between a member and its workspace root. This unintentionally supported case regressed when adding workspace caching, which memorises which projects are members of a workspace without requiring re-discovery, ignoring a `pyproject.toml` in the middle. Before caching, workspace discovery for this project starting from the project itself would terminate at the intermediate `pyroject.toml` and treat it as standalone project, which this hack restores. Fixes #19916 ## Test Plan Added a regression test, unlike for Konsti's version of the PR, we have existing coverage for the case where we mess this up so we don't need a second test to cover it. Co-authored-by: konstin <konstin@mailbox.org>
This commit is contained in:
committed by
GitHub
parent
a058272654
commit
3723315ee6
@@ -60,6 +60,13 @@ impl WorkspaceCache {
|
||||
match result {
|
||||
Ok(workspace) => {
|
||||
for package in workspace.packages.values() {
|
||||
// Historically, upward workspace discovery stopped at an intermediate
|
||||
// `pyproject.toml`, so don't map this member to the outer workspace in that
|
||||
// case.
|
||||
// See: <https://github.com/astral-sh/uv/issues/19916>
|
||||
if has_intermediate_pyproject(&workspace.install_path, &package.root) {
|
||||
continue;
|
||||
}
|
||||
self.workspaces
|
||||
.done(package.root.clone(), Ok(workspace.clone()));
|
||||
}
|
||||
@@ -99,6 +106,24 @@ impl WorkspaceCache {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` when a `pyproject.toml` sits between the member project directory and the
|
||||
/// workspace root.
|
||||
fn has_intermediate_pyproject(workspace_root: &Path, project_dir: &Path) -> bool {
|
||||
if project_dir == workspace_root {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Ok(_) = project_dir.strip_prefix(workspace_root) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
project_dir
|
||||
.ancestors()
|
||||
.skip(1)
|
||||
.take_while(|ancestor| *ancestor != workspace_root)
|
||||
.any(|ancestor| ancestor.join("pyproject.toml").is_file())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WorkspaceError(Arc<WorkspaceErrorKind>);
|
||||
|
||||
|
||||
@@ -10228,6 +10228,119 @@ fn lock_no_workspace_source() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Regression test for <https://github.com/astral-sh/uv/issues/19916>.
|
||||
///
|
||||
/// Lock a workspace with a member that also supports standalone installation via platform-specific
|
||||
/// path sources.
|
||||
#[test]
|
||||
fn lock_workspace_member_with_standalone_path_source() -> Result<()> {
|
||||
let context = uv_test::test_context!("3.12");
|
||||
|
||||
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
pyproject_toml.write_str(
|
||||
r#"
|
||||
[tool.uv.workspace]
|
||||
members = ["root/app", "root/lib"]
|
||||
|
||||
[tool.uv.sources]
|
||||
lib = { workspace = true }
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let root = context.temp_dir.child("root");
|
||||
root.child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "root"
|
||||
version = "0.1.0"
|
||||
|
||||
[tool.uv.sources]
|
||||
lib = { path = "lib" }
|
||||
"#,
|
||||
)?;
|
||||
|
||||
root.child("app").child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["lib"]
|
||||
|
||||
[tool.uv.sources]
|
||||
lib = [
|
||||
{ path = "../lib", editable = true, marker = "sys_platform == 'darwin'" },
|
||||
{ path = "../lib", editable = true, marker = "sys_platform != 'darwin'" },
|
||||
]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
root.child("lib").child("pyproject.toml").write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "lib"
|
||||
version = "0.1.0"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
uv_snapshot!(context.filters(), context.lock(), @"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 2 packages in [TIME]
|
||||
");
|
||||
|
||||
let lock = context.read("uv.lock");
|
||||
|
||||
insta::with_settings!({
|
||||
filters => context.filters(),
|
||||
}, {
|
||||
assert_snapshot!(
|
||||
lock, @r#"
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.12"
|
||||
resolution-markers = [
|
||||
"sys_platform == 'darwin'",
|
||||
"sys_platform != 'darwin'",
|
||||
]
|
||||
|
||||
[options]
|
||||
exclude-newer = "2024-03-25T00:00:00Z"
|
||||
|
||||
[manifest]
|
||||
members = [
|
||||
"app",
|
||||
"lib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "root/app" }
|
||||
dependencies = [
|
||||
{ name = "lib" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "lib", marker = "sys_platform != 'darwin'", editable = "root/lib" },
|
||||
{ name = "lib", marker = "sys_platform == 'darwin'", editable = "root/lib" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lib"
|
||||
version = "0.1.0"
|
||||
source = { editable = "root/lib" }
|
||||
"#
|
||||
);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Lock a workspace with a member that's a peer to the root.
|
||||
#[test]
|
||||
fn lock_peer_member() -> Result<()> {
|
||||
|
||||
Reference in New Issue
Block a user