Skip to content

Commit 74ac255

Browse files
committed
Avoid infinite loop in uv export with conflicts
1 parent 2b62f73 commit 74ac255

File tree

2 files changed

+194
-2
lines changed

2 files changed

+194
-2
lines changed

crates/uv-resolver/src/lock/requirements_txt.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,17 @@ impl<'lock> RequirementsTxtExport<'lock> {
284284
}
285285
}
286286

287+
println!("1");
288+
287289
// Determine the reachability of each node in the graph.
288290
let mut reachability = if let Some(conflicts) = conflicts.as_ref() {
289291
conflict_marker_reachability(&graph, &[], conflicts)
290292
} else {
291293
marker_reachability(&graph, &[])
292294
};
293295

296+
println!("2");
297+
294298
// Collect all packages.
295299
let mut nodes = graph
296300
.node_references()
@@ -496,8 +500,6 @@ fn conflict_marker_reachability<'lock>(
496500
queue.push(child_edge.target());
497501
}
498502
}
499-
500-
queue.push(child_edge.target());
501503
}
502504
}
503505

crates/uv/tests/it/export.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2982,3 +2982,193 @@ fn complex_conflict_markers() -> Result<()> {
29822982

29832983
Ok(())
29842984
}
2985+
2986+
/// Export requirements in the presence of a cycle.
2987+
#[test]
2988+
fn cyclic_dependencies() -> Result<()> {
2989+
let context = TestContext::new("3.12");
2990+
2991+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
2992+
pyproject_toml.write_str(
2993+
r#"
2994+
[project]
2995+
name = "project"
2996+
version = "0.1.0"
2997+
requires-python = ">=3.12"
2998+
dependencies = [
2999+
"testtools==2.3.0",
3000+
"fixtures==3.0.0",
3001+
]
3002+
"#,
3003+
)?;
3004+
3005+
context.lock().assert().success();
3006+
3007+
uv_snapshot!(context.filters(), context.export(), @r"
3008+
success: true
3009+
exit_code: 0
3010+
----- stdout -----
3011+
1
3012+
2
3013+
# This file was autogenerated by uv via the following command:
3014+
# uv export --cache-dir [CACHE_DIR]
3015+
argparse==1.4.0 \
3016+
--hash=sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4 \
3017+
--hash=sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314
3018+
# via unittest2
3019+
extras==1.0.0 \
3020+
--hash=sha256:132e36de10b9c91d5d4cc620160a476e0468a88f16c9431817a6729611a81b4e \
3021+
--hash=sha256:f689f08df47e2decf76aa6208c081306e7bd472630eb1ec8a875c67de2366e87
3022+
# via testtools
3023+
fixtures==3.0.0 \
3024+
--hash=sha256:2a551b0421101de112d9497fb5f6fd25e5019391c0fbec9bad591ecae981420d \
3025+
--hash=sha256:fcf0d60234f1544da717a9738325812de1f42c2fa085e2d9252d8fff5712b2ef
3026+
# via
3027+
# project
3028+
# testtools
3029+
linecache2==1.0.0 \
3030+
--hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c \
3031+
--hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef
3032+
# via traceback2
3033+
pbr==6.0.0 \
3034+
--hash=sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda \
3035+
--hash=sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9
3036+
# via
3037+
# fixtures
3038+
# testtools
3039+
python-mimeparse==1.6.0 \
3040+
--hash=sha256:76e4b03d700a641fd7761d3cd4fdbbdcd787eade1ebfac43f877016328334f78 \
3041+
--hash=sha256:a295f03ff20341491bfe4717a39cd0a8cc9afad619ba44b77e86b0ab8a2b8282
3042+
# via testtools
3043+
six==1.16.0 \
3044+
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
3045+
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
3046+
# via
3047+
# fixtures
3048+
# testtools
3049+
# unittest2
3050+
testtools==2.3.0 \
3051+
--hash=sha256:5827ec6cf8233e0f29f51025addd713ca010061204fdea77484a2934690a0559 \
3052+
--hash=sha256:a2be448869171b6e0f26d9544088b8b98439ec180ce272040236d570a40bcbed
3053+
# via
3054+
# fixtures
3055+
# project
3056+
traceback2==1.4.0 \
3057+
--hash=sha256:05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030 \
3058+
--hash=sha256:8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23
3059+
# via
3060+
# testtools
3061+
# unittest2
3062+
unittest2==1.1.0 \
3063+
--hash=sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8 \
3064+
--hash=sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579
3065+
# via testtools
3066+
3067+
----- stderr -----
3068+
Resolved 11 packages in [TIME]
3069+
");
3070+
3071+
Ok(())
3072+
}
3073+
3074+
/// Export requirements in the presence of a cycle, with conflicts enabled.
3075+
#[test]
3076+
fn cyclic_dependencies_conflict() -> Result<()> {
3077+
let context = TestContext::new("3.12");
3078+
3079+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
3080+
pyproject_toml.write_str(
3081+
r#"
3082+
[project]
3083+
name = "project"
3084+
version = "0.1.0"
3085+
requires-python = ">=3.12"
3086+
dependencies = [
3087+
"testtools==2.3.0",
3088+
"fixtures==3.0.0",
3089+
]
3090+
3091+
[project.optional-dependencies]
3092+
cpu = ["anyio==3.0.0"]
3093+
gpu = ["anyio==4.0.0"]
3094+
3095+
[tool.uv]
3096+
package = false
3097+
conflicts = [
3098+
[
3099+
{ extra = "cpu" },
3100+
{ extra = "gpu" },
3101+
],
3102+
]
3103+
3104+
"#,
3105+
)?;
3106+
3107+
context.lock().assert().success();
3108+
3109+
uv_snapshot!(context.filters(), context.export(), @r"
3110+
success: true
3111+
exit_code: 0
3112+
----- stdout -----
3113+
1
3114+
2
3115+
# This file was autogenerated by uv via the following command:
3116+
# uv export --cache-dir [CACHE_DIR]
3117+
argparse==1.4.0 \
3118+
--hash=sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4 \
3119+
--hash=sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314
3120+
# via unittest2
3121+
extras==1.0.0 \
3122+
--hash=sha256:132e36de10b9c91d5d4cc620160a476e0468a88f16c9431817a6729611a81b4e \
3123+
--hash=sha256:f689f08df47e2decf76aa6208c081306e7bd472630eb1ec8a875c67de2366e87
3124+
# via testtools
3125+
fixtures==3.0.0 \
3126+
--hash=sha256:2a551b0421101de112d9497fb5f6fd25e5019391c0fbec9bad591ecae981420d \
3127+
--hash=sha256:fcf0d60234f1544da717a9738325812de1f42c2fa085e2d9252d8fff5712b2ef
3128+
# via
3129+
# project
3130+
# testtools
3131+
linecache2==1.0.0 \
3132+
--hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c \
3133+
--hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef
3134+
# via traceback2
3135+
pbr==6.0.0 \
3136+
--hash=sha256:4a7317d5e3b17a3dccb6a8cfe67dab65b20551404c52c8ed41279fa4f0cb4cda \
3137+
--hash=sha256:d1377122a5a00e2f940ee482999518efe16d745d423a670c27773dfbc3c9a7d9
3138+
# via
3139+
# fixtures
3140+
# testtools
3141+
python-mimeparse==1.6.0 \
3142+
--hash=sha256:76e4b03d700a641fd7761d3cd4fdbbdcd787eade1ebfac43f877016328334f78 \
3143+
--hash=sha256:a295f03ff20341491bfe4717a39cd0a8cc9afad619ba44b77e86b0ab8a2b8282
3144+
# via testtools
3145+
six==1.16.0 \
3146+
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
3147+
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
3148+
# via
3149+
# fixtures
3150+
# testtools
3151+
# unittest2
3152+
testtools==2.3.0 \
3153+
--hash=sha256:5827ec6cf8233e0f29f51025addd713ca010061204fdea77484a2934690a0559 \
3154+
--hash=sha256:a2be448869171b6e0f26d9544088b8b98439ec180ce272040236d570a40bcbed
3155+
# via
3156+
# fixtures
3157+
# project
3158+
traceback2==1.4.0 \
3159+
--hash=sha256:05acc67a09980c2ecfedd3423f7ae0104839eccb55fc645773e1caa0951c3030 \
3160+
--hash=sha256:8253cebec4b19094d67cc5ed5af99bf1dba1285292226e98a31929f87a5d6b23
3161+
# via
3162+
# testtools
3163+
# unittest2
3164+
unittest2==1.1.0 \
3165+
--hash=sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8 \
3166+
--hash=sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579
3167+
# via testtools
3168+
3169+
----- stderr -----
3170+
Resolved 15 packages in [TIME]
3171+
");
3172+
3173+
Ok(())
3174+
}

0 commit comments

Comments
 (0)