diff --git a/crates/uv-resolver/src/lock/export/mod.rs b/crates/uv-resolver/src/lock/export/mod.rs index 74620ed90adac..23f48da9de935 100644 --- a/crates/uv-resolver/src/lock/export/mod.rs +++ b/crates/uv-resolver/src/lock/export/mod.rs @@ -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(); diff --git a/crates/uv/tests/it/export.rs b/crates/uv/tests/it/export.rs index 8bea3376ea726..597062935c04e 100644 --- a/crates/uv/tests/it/export.rs +++ b/crates/uv/tests/it/export.rs @@ -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 @@ -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: +#[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(()) }