Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/uv-resolver/src/lock/export/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ impl<'lock> ExportableRequirements<'lock> {
.or_insert_with(|| graph.add_node(Node::Package(dist)));
graph.add_edge(root, index, Edge::Prod(MarkerTree::TRUE));

// Track the activated project in the list of known conflicts.
if let Some(conflicts) = conflicts.as_mut() {
conflicts.insert(ConflictItem::from(dist.id.name.clone()), MarkerTree::TRUE);
}

// Push its dependencies on the queue.
queue.push_back((dist, None));
let mut root_extras = Vec::new();
Expand Down
322 changes: 322 additions & 0 deletions crates/uv/tests/it/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8766,6 +8766,32 @@ fn export_package_conflicting_workspace_members() -> Result<()> {
Resolved 4 packages in [TIME]
");

uv_snapshot!(context.filters(), context.export().arg("--format").arg("pylock.toml").arg("--package").arg("project"), @r#"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --format pylock.toml --package project
lock-version = "1.0"
created-by = "uv"
requires-python = ">=3.12"

[[packages]]
name = "project"
directory = { path = ".", editable = true }

[[packages]]
name = "sortedcontainers"
version = "2.3.0"
index = "https://pypi.org/simple"
sdist = { url = "https://files.pythonhosted.org/packages/14/10/6a9481890bae97da9edd6e737c9c3dec6aea3fc2fa53b0934037b35c89ea/sortedcontainers-2.3.0.tar.gz", upload-time = 2020-11-09T00:03:52Z, size = 30509, hashes = { sha256 = "59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1" } }
wheels = [{ url = "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", upload-time = 2020-11-09T00:03:50Z, size = 29479, hashes = { sha256 = "37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f" } }]

----- stderr -----
warning: Declaring conflicts for packages (`package = ...`) is experimental and may change without warning. Pass `--preview-features package-conflicts` to disable this warning.
Resolved 4 packages in [TIME]
"#);

// Export `--package child` should include sortedcontainers 2.4.0.
uv_snapshot!(context.filters(), context.export().arg("--package").arg("child"), @r"
success: true
Expand All @@ -8784,6 +8810,302 @@ fn export_package_conflicting_workspace_members() -> Result<()> {
Resolved 4 packages in [TIME]
");

uv_snapshot!(context.filters(), context.export().arg("--format").arg("pylock.toml").arg("--package").arg("child"), @r#"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --format pylock.toml --package child
lock-version = "1.0"
created-by = "uv"
requires-python = ">=3.12"

[[packages]]
name = "child"
directory = { path = "child", editable = true }

[[packages]]
name = "sortedcontainers"
version = "2.4.0"
index = "https://pypi.org/simple"
sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", upload-time = 2021-05-16T22:03:42Z, size = 30594, hashes = { sha256 = "25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88" } }
wheels = [{ url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", upload-time = 2021-05-16T22:03:41Z, size = 29575, hashes = { sha256 = "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" } }]

----- stderr -----
warning: Declaring conflicts for packages (`package = ...`) is experimental and may change without warning. Pass `--preview-features package-conflicts` to disable this warning.
Resolved 4 packages in [TIME]
"#);

Ok(())
}

/// Exporting a single workspace member that is part of a package-level conflict
/// should include its dependencies (not produce empty output).
///
/// See: <https://github.com/astral-sh/uv/issues/18608>
#[test]
fn requirements_txt_conflicting_workspace_member_package() -> Result<()> {
let context = uv_test::test_context!("3.12");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []

[tool.uv.workspace]
members = ["child-a", "child-b"]

[tool.uv.sources]
child-a = { workspace = true }
child-b = { workspace = true }

[tool.uv]
conflicts = [
[
{ package = "child-a" },
{ package = "child-b" },
],
]

[build-system]
requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build"
"#,
)?;

let child_a = context.temp_dir.child("child-a");
child_a.child("pyproject.toml").write_str(
r#"
[project]
name = "child-a"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["sortedcontainers==2.3.0"]

[build-system]
requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build"
"#,
)?;

let child_b = context.temp_dir.child("child-b");
child_b.child("pyproject.toml").write_str(
r#"
[project]
name = "child-b"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["sortedcontainers==2.4.0"]

[build-system]
requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build"
"#,
)?;

context.lock().assert().success();

// Exporting child-a should include its dependency (sortedcontainers==2.3.0).
uv_snapshot!(context.filters(), context.export().arg("--package").arg("child-a"), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --package child-a
-e ./child-a
sortedcontainers==2.3.0 \
--hash=sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f \
--hash=sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1
# via child-a

----- stderr -----
warning: Declaring conflicts for packages (`package = ...`) is experimental and may change without warning. Pass `--preview-features package-conflicts` to disable this warning.
Resolved 5 packages in [TIME]
");

// Exporting child-b should include its dependency (sortedcontainers==2.4.0).
uv_snapshot!(context.filters(), context.export().arg("--package").arg("child-b"), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --package child-b
-e ./child-b
sortedcontainers==2.4.0 \
--hash=sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88 \
--hash=sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0
# via child-b

----- stderr -----
warning: Declaring conflicts for packages (`package = ...`) is experimental and may change without warning. Pass `--preview-features package-conflicts` to disable this warning.
Resolved 5 packages in [TIME]
");

// Exporting child-a to `pylock.toml` should also include its dependency.
uv_snapshot!(context.filters(), context.export().arg("--format").arg("pylock.toml").arg("--package").arg("child-a"), @r#"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --format pylock.toml --package child-a
lock-version = "1.0"
created-by = "uv"
requires-python = ">=3.12"

[[packages]]
name = "child-a"
directory = { path = "child-a", editable = true }

[[packages]]
name = "sortedcontainers"
version = "2.3.0"
index = "https://pypi.org/simple"
sdist = { url = "https://files.pythonhosted.org/packages/14/10/6a9481890bae97da9edd6e737c9c3dec6aea3fc2fa53b0934037b35c89ea/sortedcontainers-2.3.0.tar.gz", upload-time = 2020-11-09T00:03:52Z, size = 30509, hashes = { sha256 = "59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1" } }
wheels = [{ url = "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", upload-time = 2020-11-09T00:03:50Z, size = 29479, hashes = { sha256 = "37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f" } }]

----- stderr -----
warning: Declaring conflicts for packages (`package = ...`) is experimental and may change without warning. Pass `--preview-features package-conflicts` to disable this warning.
Resolved 5 packages in [TIME]
"#);

// Exporting child-b to `pylock.toml` should also include its dependency.
uv_snapshot!(context.filters(), context.export().arg("--format").arg("pylock.toml").arg("--package").arg("child-b"), @r#"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --format pylock.toml --package child-b
lock-version = "1.0"
created-by = "uv"
requires-python = ">=3.12"

[[packages]]
name = "child-b"
directory = { path = "child-b", editable = true }

[[packages]]
name = "sortedcontainers"
version = "2.4.0"
index = "https://pypi.org/simple"
sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", upload-time = 2021-05-16T22:03:42Z, size = 30594, hashes = { sha256 = "25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88" } }
wheels = [{ url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", upload-time = 2021-05-16T22:03:41Z, size = 29575, hashes = { sha256 = "a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" } }]

----- stderr -----
warning: Declaring conflicts for packages (`package = ...`) is experimental and may change without warning. Pass `--preview-features package-conflicts` to disable this warning.
Resolved 5 packages in [TIME]
"#);

// Exporting both conflicting packages should fail.
uv_snapshot!(context.filters(), context.export().arg("--all-packages"), @"
success: false
exit_code: 2
----- stdout -----

----- stderr -----
warning: Declaring conflicts for packages (`package = ...`) is experimental and may change without warning. Pass `--preview-features package-conflicts` to disable this warning.
Resolved 5 packages in [TIME]
error: Package `child-a` and package `child-b` are incompatible with the declared conflicts: {child-a, child-b}
");

Ok(())
}

/// Exporting the workspace root package should continue to include its dependencies
/// even when it conflicts with another workspace member.
#[test]
fn requirements_txt_conflicting_workspace_root_package() -> Result<()> {
let context = uv_test::test_context!("3.12");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["sortedcontainers==2.3.0"]

[tool.uv.workspace]
members = ["child"]

[tool.uv]
conflicts = [
[
{ package = "project" },
{ package = "child" },
],
]

[build-system]
requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build"
"#,
)?;

let child = context.temp_dir.child("child");
child.child("pyproject.toml").write_str(
r#"
[project]
name = "child"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["sortedcontainers==2.4.0"]

[build-system]
requires = ["uv_build>=0.7,<10000"]
build-backend = "uv_build"
"#,
)?;

context.lock().assert().success();

uv_snapshot!(context.filters(), context.export().arg("--package").arg("project"), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --package project
-e .
sortedcontainers==2.3.0 \
--hash=sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f \
--hash=sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1
# via project

----- stderr -----
warning: Declaring conflicts for packages (`package = ...`) is experimental and may change without warning. Pass `--preview-features package-conflicts` to disable this warning.
Resolved 4 packages in [TIME]
");

uv_snapshot!(context.filters(), context.export().arg("--format").arg("pylock.toml").arg("--package").arg("project"), @r#"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --format pylock.toml --package project
lock-version = "1.0"
created-by = "uv"
requires-python = ">=3.12"

[[packages]]
name = "project"
directory = { path = ".", editable = true }

[[packages]]
name = "sortedcontainers"
version = "2.3.0"
index = "https://pypi.org/simple"
sdist = { url = "https://files.pythonhosted.org/packages/14/10/6a9481890bae97da9edd6e737c9c3dec6aea3fc2fa53b0934037b35c89ea/sortedcontainers-2.3.0.tar.gz", upload-time = 2020-11-09T00:03:52Z, size = 30509, hashes = { sha256 = "59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1" } }
wheels = [{ url = "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", upload-time = 2020-11-09T00:03:50Z, size = 29479, hashes = { sha256 = "37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f" } }]

----- stderr -----
warning: Declaring conflicts for packages (`package = ...`) is experimental and may change without warning. Pass `--preview-features package-conflicts` to disable this warning.
Resolved 4 packages in [TIME]
"#);

Ok(())
}

Expand Down
Loading