Skip to content

Commit 286ffbe

Browse files
committed
Sort by name, then specifiers in uv add
1 parent 709e45f commit 286ffbe

File tree

2 files changed

+84
-6
lines changed

2 files changed

+84
-6
lines changed

crates/uv-workspace/src/pyproject_mut.rs

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,19 @@ pub fn add_dependency(
896896

897897
match to_replace.as_slice() {
898898
[] => {
899+
/// Split a requirement into the package name and its dependency specifiers.
900+
///
901+
/// E.g., given `flask>=1.0`, this function returns `("flask", ">=1.0")`. But given
902+
/// `Flask>=1.0`, this function returns `("Flask", ">=1.0")`.
903+
///
904+
/// Extras are retained, such that `flask[dotenv]>=1.0` returns `("flask[dotenv]", ">=1.0")`.
905+
fn split_specifiers(req: &str) -> (&str, &str) {
906+
req.find(|c: char| matches!(c, '>' | '<' | '=' | '~' | '!' | '@')).map_or_else(|| (req.trim(), ""), |pos| {
907+
let (name, specifiers) = req.split_at(pos);
908+
(name.trim(), specifiers.trim())
909+
})
910+
}
911+
899912
#[derive(Debug, Copy, Clone)]
900913
enum Sort {
901914
/// The list is sorted in a case-sensitive manner.
@@ -906,6 +919,7 @@ pub fn add_dependency(
906919
Unsorted,
907920
}
908921

922+
909923
// Determine if the dependency list is sorted prior to
910924
// adding the new dependency; the new dependency list
911925
// will be sorted only when the original list is sorted
@@ -920,14 +934,13 @@ pub fn add_dependency(
920934
.all(Value::is_str)
921935
.then(|| {
922936
if deps.iter().tuple_windows().all(|(a, b)| {
923-
a.as_str().map(str::to_lowercase) <= b.as_str().map(str::to_lowercase)
937+
a.as_str().map(str::to_lowercase).as_deref().map(split_specifiers)
938+
<= b.as_str().map(str::to_lowercase).as_deref().map(split_specifiers)
924939
}) {
925940
Some(Sort::CaseInsensitive)
926-
} else if deps
927-
.iter()
928-
.tuple_windows()
929-
.all(|(a, b)| a.as_str() <= b.as_str())
930-
{
941+
} else if deps.iter().tuple_windows().all(|(a, b)| {
942+
a.as_str().map(split_specifiers) <= b.as_str().map(split_specifiers)
943+
}) {
931944
Some(Sort::CaseSensitive)
932945
} else {
933946
None

crates/uv/tests/it/edit.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5842,6 +5842,71 @@ fn case_sensitive_sorted_dependencies() -> Result<()> {
58425842
Ok(())
58435843
}
58445844

5845+
/// Ensure that sorting is based on the name, rather than the combined name-and-specifiers.
5846+
#[test]
5847+
fn sorted_dependencies_name_specifiers() -> Result<()> {
5848+
let context = TestContext::new("3.12");
5849+
5850+
let pyproject_toml = context.temp_dir.child("pyproject.toml");
5851+
pyproject_toml.write_str(indoc! {r#"
5852+
[project]
5853+
name = "project"
5854+
version = "0.1.0"
5855+
description = "Add your description here"
5856+
requires-python = ">=3.12"
5857+
dependencies = [
5858+
"pylint>=3",
5859+
"pylint-module-boundaries>=1",
5860+
]
5861+
"#})?;
5862+
5863+
uv_snapshot!(context.filters(), context.add().args(["typing-extensions", "anyio"]), @r###"
5864+
success: true
5865+
exit_code: 0
5866+
----- stdout -----
5867+
5868+
----- stderr -----
5869+
Resolved 14 packages in [TIME]
5870+
Prepared 12 packages in [TIME]
5871+
Installed 12 packages in [TIME]
5872+
+ anyio==4.3.0
5873+
+ astroid==3.1.0
5874+
+ dill==0.3.8
5875+
+ idna==3.6
5876+
+ isort==5.13.2
5877+
+ mccabe==0.7.0
5878+
+ platformdirs==4.2.0
5879+
+ pylint==3.1.0
5880+
+ pylint-module-boundaries==1.3.1
5881+
+ sniffio==1.3.1
5882+
+ tomlkit==0.12.4
5883+
+ typing-extensions==4.10.0
5884+
"###);
5885+
5886+
let pyproject_toml = context.read("pyproject.toml");
5887+
5888+
insta::with_settings!({
5889+
filters => context.filters(),
5890+
}, {
5891+
assert_snapshot!(
5892+
pyproject_toml, @r###"
5893+
[project]
5894+
name = "project"
5895+
version = "0.1.0"
5896+
description = "Add your description here"
5897+
requires-python = ">=3.12"
5898+
dependencies = [
5899+
"anyio>=4.3.0",
5900+
"pylint>=3",
5901+
"pylint-module-boundaries>=1",
5902+
"typing-extensions>=4.10.0",
5903+
]
5904+
"###
5905+
);
5906+
});
5907+
Ok(())
5908+
}
5909+
58455910
/// Ensure that the custom ordering of the dependencies is preserved
58465911
/// after adding a package.
58475912
#[test]

0 commit comments

Comments
 (0)