Skip to content

Commit df570ee

Browse files
authored
Fix non-deterministic dependency constraint ordering in lock file (#10720)
1 parent 295a7a0 commit df570ee

File tree

3 files changed

+69
-5
lines changed

3 files changed

+69
-5
lines changed

src/poetry/packages/locker.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,10 @@ def _dump_package(
521521
dependencies: dict[str, list[Any]] = {}
522522
for dependency in sorted(
523523
package.requires,
524-
key=lambda d: d.name,
524+
key=lambda d: (
525+
d.name,
526+
str(d.marker) if not d.marker.is_any() else "",
527+
),
525528
):
526529
dependencies.setdefault(dependency.pretty_name, [])
527530

tests/installation/fixtures/with-conflicting-dependency-extras-transitive.test

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ groups = [ "main" ]
2929
markers = "extra == \"root-extra-one\" and extra != \"root-extra-two\" or extra != \"root-extra-one\" and extra == \"root-extra-two\""
3030

3131
[[package.dependencies.conflicting-dep]]
32-
version = "1.1.0"
32+
version = "1.2.0"
3333
optional = true
34-
markers = 'extra == "extra-one" and extra != "extra-two"'
34+
markers = 'extra != "extra-one" and extra == "extra-two"'
3535

3636
[[package.dependencies.conflicting-dep]]
37-
version = "1.2.0"
37+
version = "1.1.0"
3838
optional = true
39-
markers = 'extra != "extra-one" and extra == "extra-two"'
39+
markers = 'extra == "extra-one" and extra != "extra-two"'
4040

4141
[package.extras]
4242
extra-one = [ "conflicting-dep (==1.1.0)", "conflicting-dep (==1.2.0)" ]

tests/packages/test_locker.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1872,3 +1872,64 @@ def test_lockfile_keep_eol(
18721872
assert line.endswith("\r\n")
18731873
else:
18741874
assert not line.endswith("\r\n")
1875+
1876+
1877+
def test_lock_file_dependency_constraints_are_ordered_deterministically(
1878+
locker: Locker, root: ProjectPackage, transitive_info: TransitivePackageInfo
1879+
) -> None:
1880+
"""Dependency constraints for the same package should be sorted by
1881+
name and marker to ensure deterministic lock file output
1882+
regardless of the order they are added."""
1883+
package_a = get_package("A", "1.0.0")
1884+
# Add dependencies on B in non-sorted order (by marker and version)
1885+
package_a.add_dependency(
1886+
Factory.create_dependency(
1887+
"B",
1888+
{"version": ">=2.0", "markers": 'sys_platform == "win32"'},
1889+
)
1890+
)
1891+
package_a.add_dependency(
1892+
Factory.create_dependency(
1893+
"B",
1894+
{"version": ">=1.0", "markers": 'sys_platform == "linux"'},
1895+
)
1896+
)
1897+
1898+
locker.set_lock_data(root, {package_a: transitive_info})
1899+
1900+
expected = f"""\
1901+
# {GENERATED_COMMENT}
1902+
1903+
[[package]]
1904+
name = "A"
1905+
version = "1.0.0"
1906+
description = ""
1907+
optional = false
1908+
python-versions = "*"
1909+
groups = ["main"]
1910+
files = []
1911+
1912+
[package.dependencies]
1913+
B = [
1914+
{{version = ">=1.0", markers = "sys_platform == \\"linux\\""}},
1915+
{{version = ">=2.0", markers = "sys_platform == \\"win32\\""}},
1916+
]
1917+
1918+
[metadata]
1919+
lock-version = "2.1"
1920+
python-versions = "*"
1921+
content-hash = "115cf985d932e9bf5f540555bbdd75decbb62cac81e399375fc19f6277f8c1d8"
1922+
"""
1923+
1924+
with locker.lock.open(encoding="utf-8") as f:
1925+
content = f.read()
1926+
1927+
assert content == expected
1928+
1929+
# Run again to verify idempotency
1930+
locker.set_lock_data(root, {package_a: transitive_info})
1931+
1932+
with locker.lock.open(encoding="utf-8") as f:
1933+
content2 = f.read()
1934+
1935+
assert content2 == expected

0 commit comments

Comments
 (0)