mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-03 18:38:21 +00:00
uv/tests: adds a test with mutually exclusive extras across a workspace
This tests comes from here: https://github.com/astral-sh/uv/pull/8976#issuecomment-2473672199 And it was originally thought of by Konsti. This test case is the motivation for making `package` optional in `conflicts` instead of forbidding it entirely.
This commit is contained in:
parent
bb78e00a87
commit
c2c9bd9557
1 changed files with 203 additions and 0 deletions
|
@ -3356,6 +3356,209 @@ fn lock_conflicting_extra_unconditional() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// This tests how we deal with mutually conflicting extras that span multiple
|
||||
/// packages in a workspace.
|
||||
#[test]
|
||||
fn lock_conflicting_extra_nested_across_workspace() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let root_pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||
root_pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "dummy"
|
||||
version = "0.1.0"
|
||||
requires-python = "==3.12.*"
|
||||
|
||||
[project.optional-dependencies]
|
||||
project1 = [
|
||||
"proxy1[project1]",
|
||||
]
|
||||
project2 = [
|
||||
"proxy1[project2]"
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
proxy1 = { path = "./proxy1" }
|
||||
dummysub = { workspace = true }
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["dummysub"]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv]
|
||||
conflicts = [
|
||||
[
|
||||
{ extra = "project1" },
|
||||
{ extra = "project2" },
|
||||
],
|
||||
]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let sub_pyproject_toml = context.temp_dir.child("dummysub").child("pyproject.toml");
|
||||
sub_pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "dummysub"
|
||||
version = "0.1.0"
|
||||
requires-python = "==3.12.*"
|
||||
|
||||
[project.optional-dependencies]
|
||||
project1 = [
|
||||
"proxy1[project1]",
|
||||
]
|
||||
project2 = [
|
||||
"proxy1[project2]"
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
proxy1 = { path = "../proxy1" }
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv]
|
||||
conflicts = [
|
||||
[
|
||||
{ extra = "project1" },
|
||||
{ extra = "project2" },
|
||||
],
|
||||
]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let proxy1_pyproject_toml = context.temp_dir.child("proxy1").child("pyproject.toml");
|
||||
proxy1_pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "proxy1"
|
||||
version = "0.1.0"
|
||||
requires-python = "==3.12.*"
|
||||
dependencies = []
|
||||
|
||||
[project.optional-dependencies]
|
||||
project1 = ["anyio==4.1.0"]
|
||||
project2 = ["anyio==4.2.0"]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"#,
|
||||
)?;
|
||||
|
||||
// In the scheme above, we declare that `dummy[project1]` conflicts
|
||||
// with `dummy[project2]`, and that `dummysub[project1]` conflicts
|
||||
// with `dummysub[project2]`. But we don't account for the fact that
|
||||
// `dummy[project1]` conflicts with `dummysub[project2]` and that
|
||||
// `dummy[project2]` conflicts with `dummysub[project1]`. So we end
|
||||
// up with a resolution failure.
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: false
|
||||
exit_code: 1
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
× No solution found when resolving dependencies:
|
||||
╰─▶ Because dummy[project2] depends on proxy1[project2] and only proxy1[project2]==0.1.0 is available, we can conclude that dummy[project2] depends on proxy1[project2]==0.1.0. (1)
|
||||
|
||||
Because proxy1[project1]==0.1.0 depends on anyio==4.1.0 and proxy1[project2]==0.1.0 depends on anyio==4.2.0, we can conclude that proxy1[project1]==0.1.0 and proxy1[project2]==0.1.0 are incompatible.
|
||||
And because we know from (1) that dummy[project2] depends on proxy1[project2]==0.1.0, we can conclude that dummy[project2] and proxy1[project1]==0.1.0 are incompatible.
|
||||
And because only proxy1[project1]==0.1.0 is available and dummysub[project1] depends on proxy1[project1], we can conclude that dummysub[project1] and dummy[project2] are incompatible.
|
||||
And because your workspace requires dummy[project2] and dummysub[project1], we can conclude that your workspace's requirements are unsatisfiable.
|
||||
"###);
|
||||
|
||||
// Now let's write out the full set of conflicts, taking
|
||||
// advantage of the optional `package` key.
|
||||
root_pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "dummy"
|
||||
version = "0.1.0"
|
||||
requires-python = "==3.12.*"
|
||||
|
||||
[project.optional-dependencies]
|
||||
project1 = [
|
||||
"proxy1[project1]",
|
||||
]
|
||||
project2 = [
|
||||
"proxy1[project2]"
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
proxy1 = { path = "./proxy1" }
|
||||
dummysub = { workspace = true }
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["dummysub"]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.uv]
|
||||
conflicts = [
|
||||
[
|
||||
{ extra = "project1" },
|
||||
{ extra = "project2" },
|
||||
],
|
||||
[
|
||||
{ package = "dummysub", extra = "project1" },
|
||||
{ package = "dummysub", extra = "project2" },
|
||||
],
|
||||
[
|
||||
{ extra = "project1" },
|
||||
{ package = "dummysub", extra = "project2" },
|
||||
],
|
||||
[
|
||||
{ package = "dummysub", extra = "project1" },
|
||||
{ extra = "project2" },
|
||||
],
|
||||
]
|
||||
"#,
|
||||
)?;
|
||||
// And we can remove the conflicts from `dummysub` since
|
||||
// there specified in `dummy`.
|
||||
sub_pyproject_toml.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "dummysub"
|
||||
version = "0.1.0"
|
||||
requires-python = "==3.12.*"
|
||||
|
||||
[project.optional-dependencies]
|
||||
project1 = [
|
||||
"proxy1[project1]",
|
||||
]
|
||||
project2 = [
|
||||
"proxy1[project2]"
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
proxy1 = { path = "../proxy1" }
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
"#,
|
||||
)?;
|
||||
// And now things should work.
|
||||
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 7 packages in [TIME]
|
||||
"###);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Show updated dependencies on `lock --upgrade`.
|
||||
#[test]
|
||||
fn lock_upgrade_log() -> Result<()> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue