Avoid infinite loop in `uv export` with conflicts (#12726)

## Summary

Closes https://github.com/astral-sh/uv/issues/12695.

Closes https://github.com/astral-sh/uv/issues/12719.
This commit is contained in:
Charlie Marsh 2025-04-07 15:10:58 -04:00 committed by GitHub
parent 1cca93c099
commit a0f5c7250b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 269 additions and 18 deletions

View File

@ -18,6 +18,7 @@
### Bug fixes
- Warn instead of error if CRC appears to be missing ([#12722](https://github.com/astral-sh/uv/pull/12722))
- Avoid infinite loop in `uv export` with conflicts ([#12726](https://github.com/astral-sh/uv/pull/12726))
### Rust API

View File

@ -483,11 +483,12 @@ fn conflict_marker_reachability<'lock>(
// Combine the inferred marker with the existing marker on the node.
match reachability.entry(child_edge.target()) {
Entry::Occupied(existing) => {
Entry::Occupied(mut existing) => {
// If the marker is a subset of the existing marker (A ⊆ B exactly if
// A B = A), updating the child wouldn't change child's marker.
parent_marker.or(*existing.get());
if parent_marker != *existing.get() {
existing.insert(parent_marker);
queue.push(child_edge.target());
}
}
@ -496,8 +497,6 @@ fn conflict_marker_reachability<'lock>(
queue.push(child_edge.target());
}
}
queue.push(child_edge.target());
}
}

View File

@ -2730,12 +2730,65 @@ fn complex_conflict_markers() -> Result<()> {
context.lock().assert().success();
uv_snapshot!(context.filters(), context.export(), @r###"
uv_snapshot!(context.filters(), context.export(), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR]
filelock==3.17.0 \
--hash=sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338 \
--hash=sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e
# via torch
fsspec==2024.12.0 \
--hash=sha256:670700c977ed2fb51e0d9f9253177ed20cbde4a3e5c0283cc5385b5870c8533f \
--hash=sha256:b520aed47ad9804237ff878b504267a3b0b441e97508bd6d2d8774e3db85cee2
# via torch
jinja2==3.1.5 \
--hash=sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb \
--hash=sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb
# via torch
markupsafe==3.0.2 \
--hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \
--hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \
--hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \
--hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \
--hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \
--hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \
--hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \
--hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \
--hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \
--hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \
--hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \
--hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \
--hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \
--hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \
--hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \
--hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \
--hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \
--hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \
--hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \
--hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \
--hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \
--hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \
--hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \
--hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \
--hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \
--hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \
--hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \
--hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \
--hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \
--hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \
--hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430
# via jinja2
mpmath==1.3.0 \
--hash=sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f \
--hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c
# via sympy
networkx==3.4.2 \
--hash=sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1 \
--hash=sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f
# via torch
nvidia-cublas-cu12==12.4.5.8 ; platform_machine == 'x86_64' and sys_platform == 'linux' \
--hash=sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3 \
--hash=sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b \
@ -2807,6 +2860,14 @@ fn complex_conflict_markers() -> Result<()> {
--hash=sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a \
--hash=sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3
# via torch
setuptools==75.8.0 \
--hash=sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6 \
--hash=sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3
# via torch
sympy==1.13.1 \
--hash=sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f \
--hash=sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8
# via torch
torch==2.6.0 \
--hash=sha256:2bb8987f3bb1ef2675897034402373ddfc8f5ef0e156e2d8cfc47cacafdda4a9 \
--hash=sha256:4874a73507a300a5d089ceaff616a569e7bb7c613c56f37f63ec3ffac65259cf \
@ -2821,30 +2882,34 @@ fn complex_conflict_markers() -> Result<()> {
--hash=sha256:8d9b215efc1c26fa7eefb9a157915c92d52e000d2bf83e5f69704047e63f125c \
--hash=sha256:e5dfa23ba84541d7c0a531dfce76d8bcd19159d50a4a8b14ad01e91734a5c1b0
# via torch
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
# via torch
----- stderr -----
Resolved 33 packages in [TIME]
"###);
");
uv_snapshot!(context.filters(), context.export().arg("--extra").arg("cpu"), @r###"
uv_snapshot!(context.filters(), context.export().arg("--extra").arg("cpu"), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR] --extra cpu
filelock==3.17.0 ; sys_platform == 'darwin' \
filelock==3.17.0 \
--hash=sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338 \
--hash=sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e
# via torch
fsspec==2024.12.0 ; sys_platform == 'darwin' \
fsspec==2024.12.0 \
--hash=sha256:670700c977ed2fb51e0d9f9253177ed20cbde4a3e5c0283cc5385b5870c8533f \
--hash=sha256:b520aed47ad9804237ff878b504267a3b0b441e97508bd6d2d8774e3db85cee2
# via torch
jinja2==3.1.5 ; sys_platform == 'darwin' \
jinja2==3.1.5 \
--hash=sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb \
--hash=sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb
# via torch
markupsafe==3.0.2 ; sys_platform == 'darwin' \
markupsafe==3.0.2 \
--hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \
--hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \
--hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \
@ -2877,15 +2942,15 @@ fn complex_conflict_markers() -> Result<()> {
--hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \
--hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430
# via jinja2
mpmath==1.3.0 ; sys_platform == 'darwin' \
mpmath==1.3.0 \
--hash=sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f \
--hash=sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c
# via sympy
networkx==3.4.2 ; sys_platform == 'darwin' \
networkx==3.4.2 \
--hash=sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1 \
--hash=sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f
# via torch
numpy==2.2.2 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin' \
numpy==2.2.2 \
--hash=sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0 \
--hash=sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2 \
--hash=sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4 \
@ -2918,7 +2983,7 @@ fn complex_conflict_markers() -> Result<()> {
--hash=sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff \
--hash=sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f
# via torchvision
pillow==11.1.0 ; (platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin' \
pillow==11.1.0 \
--hash=sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65 \
--hash=sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a \
--hash=sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352 \
@ -2951,11 +3016,11 @@ fn complex_conflict_markers() -> Result<()> {
--hash=sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab \
--hash=sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9
# via torchvision
setuptools==75.8.0 ; sys_platform == 'darwin' \
setuptools==75.8.0 \
--hash=sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6 \
--hash=sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3
# via torch
sympy==1.13.1 ; sys_platform == 'darwin' \
sympy==1.13.1 \
--hash=sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f \
--hash=sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8
# via torch
@ -2971,14 +3036,200 @@ fn complex_conflict_markers() -> Result<()> {
# via project
torchvision==0.21.0+cpu ; (platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')
# via project
typing-extensions==4.12.2 ; sys_platform == 'darwin' \
typing-extensions==4.12.2 \
--hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \
--hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8
# via torch
----- stderr -----
Resolved 33 packages in [TIME]
"###);
");
Ok(())
}
/// Export requirements in the presence of a cycle.
#[test]
fn cyclic_dependencies() -> Result<()> {
let context = TestContext::new("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 = [
"testtools==2.3.0",
"fixtures==3.0.0",
]
"#,
)?;
context.lock().assert().success();
uv_snapshot!(context.filters(), context.export(), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR]
argparse==1.4.0 \
--hash=sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4 \
--hash=sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314
# via unittest2
extras==1.0.0 \
--hash=sha256:132e36de10b9c91d5d4cc620160a476e0468a88f16c9431817a6729611a81b4e \
--hash=sha256:f689f08df47e2decf76aa6208c081306e7bd472630eb1ec8a875c67de2366e87
# via testtools
fixtures==3.0.0 \
--hash=sha256:2a551b0421101de112d9497fb5f6fd25e5019391c0fbec9bad591ecae981420d \
--hash=sha256:fcf0d60234f1544da717a9738325812de1f42c2fa085e2d9252d8fff5712b2ef
# via
# project
# testtools
linecache2==1.0.0 \
--hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c \
--hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef
# via traceback2
pbr==6.0.0 \
--hash=sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda \
--hash=sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9
# via
# fixtures
# testtools
python-mimeparse==1.6.0 \
--hash=sha256:76e4b03d700a641fd7761d3cd4fdbbdcd787eade1ebfac43f877016328334f78 \
--hash=sha256:a295f03ff20341491bfe4717a39cd0a8cc9afad619ba44b77e86b0ab8a2b8282
# via testtools
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via
# fixtures
# testtools
# unittest2
testtools==2.3.0 \
--hash=sha256:5827ec6cf8233e0f29f51025addd713ca010061204fdea77484a2934690a0559 \
--hash=sha256:a2be448869171b6e0f26d9544088b8b98439ec180ce272040236d570a40bcbed
# via
# fixtures
# project
traceback2==1.4.0 \
--hash=sha256:05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030 \
--hash=sha256:8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23
# via
# testtools
# unittest2
unittest2==1.1.0 \
--hash=sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8 \
--hash=sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579
# via testtools
----- stderr -----
Resolved 11 packages in [TIME]
");
Ok(())
}
/// Export requirements in the presence of a cycle, with conflicts enabled.
#[test]
fn cyclic_dependencies_conflict() -> Result<()> {
let context = TestContext::new("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 = [
"testtools==2.3.0",
"fixtures==3.0.0",
]
[project.optional-dependencies]
cpu = ["anyio==3.0.0"]
gpu = ["anyio==4.0.0"]
[tool.uv]
package = false
conflicts = [
[
{ extra = "cpu" },
{ extra = "gpu" },
],
]
"#,
)?;
context.lock().assert().success();
uv_snapshot!(context.filters(), context.export(), @r"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv export --cache-dir [CACHE_DIR]
argparse==1.4.0 \
--hash=sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4 \
--hash=sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314
# via unittest2
extras==1.0.0 \
--hash=sha256:132e36de10b9c91d5d4cc620160a476e0468a88f16c9431817a6729611a81b4e \
--hash=sha256:f689f08df47e2decf76aa6208c081306e7bd472630eb1ec8a875c67de2366e87
# via testtools
fixtures==3.0.0 \
--hash=sha256:2a551b0421101de112d9497fb5f6fd25e5019391c0fbec9bad591ecae981420d \
--hash=sha256:fcf0d60234f1544da717a9738325812de1f42c2fa085e2d9252d8fff5712b2ef
# via
# project
# testtools
linecache2==1.0.0 \
--hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c \
--hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef
# via traceback2
pbr==6.0.0 \
--hash=sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda \
--hash=sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9
# via
# fixtures
# testtools
python-mimeparse==1.6.0 \
--hash=sha256:76e4b03d700a641fd7761d3cd4fdbbdcd787eade1ebfac43f877016328334f78 \
--hash=sha256:a295f03ff20341491bfe4717a39cd0a8cc9afad619ba44b77e86b0ab8a2b8282
# via testtools
six==1.16.0 \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
# via
# fixtures
# testtools
# unittest2
testtools==2.3.0 \
--hash=sha256:5827ec6cf8233e0f29f51025addd713ca010061204fdea77484a2934690a0559 \
--hash=sha256:a2be448869171b6e0f26d9544088b8b98439ec180ce272040236d570a40bcbed
# via
# fixtures
# project
traceback2==1.4.0 \
--hash=sha256:05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030 \
--hash=sha256:8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23
# via
# testtools
# unittest2
unittest2==1.1.0 \
--hash=sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8 \
--hash=sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579
# via testtools
----- stderr -----
Resolved 15 packages in [TIME]
");
Ok(())
}