Skip to content

Commit 9b34e65

Browse files
authored
Merge pull request #775 from lbrabec/fix_655
fix: preserve multiple requirements with markers in project_override
2 parents e88a7c2 + edee479 commit 9b34e65

File tree

2 files changed

+88
-5
lines changed

2 files changed

+88
-5
lines changed

src/fromager/pyproject.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import itertools
56
import logging
67
import pathlib
78
import typing
@@ -94,22 +95,28 @@ def _default_build_system(self, doc: tomlkit.TOMLDocument) -> TomlDict:
9495
def _update_build_requires(self, build_system: TomlDict) -> None:
9596
old_requires = build_system[BUILD_REQUIRES]
9697
# always include setuptools
97-
req_map: dict[NormalizedName, Requirement] = {
98-
canonicalize_name("setuptools"): Requirement("setuptools"),
98+
req_map: dict[NormalizedName, list[Requirement]] = {
99+
canonicalize_name("setuptools"): [Requirement("setuptools")],
99100
}
100101
# parse original build reqirements (if available)
101102
for reqstr in old_requires:
102103
req = Requirement(reqstr)
103-
req_map[canonicalize_name(req.name)] = req
104+
req_map.setdefault(canonicalize_name(req.name), []).append(req)
104105
# remove unwanted requirements
105106
for name in self.remove_requirements:
106107
req_map.pop(canonicalize_name(name), None)
107108
# add / update requirements
109+
update_req_map: dict[NormalizedName, list[Requirement]] = {}
108110
for reqstr in self.update_requirements:
109111
req = Requirement(reqstr)
110-
req_map[canonicalize_name(req.name)] = req
112+
update_req_map.setdefault(canonicalize_name(req.name), []).append(req)
113+
req_map.update(update_req_map)
111114

112-
new_requires = sorted(str(req) for req in req_map.values())
115+
new_requires = sorted(
116+
itertools.chain.from_iterable(
117+
[str(req) for req in reqs] for reqs in req_map.values()
118+
)
119+
)
113120
if set(new_requires) != set(old_requires):
114121
# ignore order of items
115122
build_system[BUILD_REQUIRES] = new_requires

tests/test_pyproject.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,79 @@ def test_pyproject_fix(
101101
"torch",
102102
],
103103
}
104+
105+
106+
PYPROJECT_MULTIPLE_REQUIRES = """
107+
[build-system]
108+
requires = [
109+
"numpy<2.0; python_version<'3.9'",
110+
"numpy==2.0.2; python_version>='3.9' and python_version<'3.13'",
111+
"numpy==2.1.3; python_version=='3.13'",
112+
"packaging",
113+
"pip",
114+
"scikit-build>=0.14.0",
115+
"setuptools==59.2.0; python_version<'3.12'",
116+
"setuptools<70.0.0; python_version>='3.12'",
117+
]
118+
"""
119+
120+
121+
def test_pyproject_preserve_multiple_requires(tmp_path: pathlib.Path) -> None:
122+
tmp_path.joinpath("pyproject.toml").write_text(PYPROJECT_MULTIPLE_REQUIRES)
123+
req = Requirement("testproject==1.0.0")
124+
fixer = pyproject.PyprojectFix(
125+
req,
126+
build_dir=tmp_path,
127+
update_build_requires=["setuptools"],
128+
remove_build_requires=[canonicalize_name("cmake")],
129+
)
130+
fixer.run()
131+
with tmp_path.joinpath("pyproject.toml").open(encoding="utf-8") as f:
132+
doc = tomlkit.load(f)
133+
assert isinstance(doc["build-system"], typing.Container)
134+
# PyprojectFix parses requirements using packaging.requirements.Requirement and then casts
135+
# to str, this may change white spaces in markers, let's do it here as well
136+
assert dict(doc["build-system"].items())["requires"] == [
137+
str(Requirement(req))
138+
for req in [
139+
"numpy<2.0; python_version<'3.9'",
140+
"numpy==2.0.2; python_version>='3.9' and python_version<'3.13'",
141+
"numpy==2.1.3; python_version=='3.13'",
142+
"packaging",
143+
"pip",
144+
"scikit-build>=0.14.0",
145+
"setuptools",
146+
]
147+
]
148+
149+
150+
def test_pyproject_override_multiple_requires(tmp_path: pathlib.Path) -> None:
151+
tmp_path.joinpath("pyproject.toml").write_text(PYPROJECT_MULTIPLE_REQUIRES)
152+
req = Requirement("testproject==1.0.0")
153+
fixer = pyproject.PyprojectFix(
154+
req,
155+
build_dir=tmp_path,
156+
update_build_requires=[
157+
"setuptools",
158+
"numpy<3.0.0; python_version=='3.12'",
159+
"numpy==3.0.0",
160+
],
161+
remove_build_requires=[canonicalize_name("cmake")],
162+
)
163+
fixer.run()
164+
with tmp_path.joinpath("pyproject.toml").open(encoding="utf-8") as f:
165+
doc = tomlkit.load(f)
166+
assert isinstance(doc["build-system"], typing.Container)
167+
# PyprojectFix parses requirements using packaging.requirements.Requirement and then casts
168+
# to str, this may change white spaces in markers, let's do it here as well
169+
assert dict(doc["build-system"].items())["requires"] == [
170+
str(Requirement(req))
171+
for req in [
172+
"numpy<3.0.0; python_version=='3.12'",
173+
"numpy==3.0.0",
174+
"packaging",
175+
"pip",
176+
"scikit-build>=0.14.0",
177+
"setuptools",
178+
]
179+
]

0 commit comments

Comments
 (0)