Skip to content

Commit 4fa10a0

Browse files
authored
Merge pull request #654 from tiran/ignore-platform
feat: Add option to ignore wheel platform in resolver
2 parents 098b8fb + ee93650 commit 4fa10a0

File tree

9 files changed

+131
-6
lines changed

9 files changed

+131
-6
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ __pycache__/
55
/work-dir*/
66
/test-venv/
77
/venv*/
8+
/.venv/
89
/build/
910
**/*.egg-info/
1011
/src/fromager/version.py

src/fromager/packagesettings.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ class ResolverDist(pydantic.BaseModel):
119119
sdist_server_url: https://pypi.org/simple/
120120
include_sdists: True
121121
include_wheels: False
122+
ignore_platform: False
122123
"""
123124

124125
model_config = MODEL_CONFIG
@@ -132,6 +133,23 @@ class ResolverDist(pydantic.BaseModel):
132133
include_wheels: bool = False
133134
"""Use wheels to resolve? (default: no)"""
134135

136+
ignore_platform: bool = False
137+
"""Ignore the platform when resolving with wheels? (default: no)
138+
139+
This option ignores the platform field (OS, CPU arch) when resolving with
140+
*include_wheels* enabled.
141+
142+
.. versionadded:: 0.52
143+
"""
144+
145+
@pydantic.model_validator(mode="after")
146+
def validate_ignore_platform(self) -> typing.Self:
147+
if self.ignore_platform and not self.include_wheels:
148+
raise ValueError(
149+
"'ignore_platforms' has no effect without 'include_wheels'"
150+
)
151+
return self
152+
135153

136154
class DownloadSource(pydantic.BaseModel):
137155
"""Package download source
@@ -696,6 +714,11 @@ def resolver_include_sdists(self) -> bool:
696714
"""Include sdists when resolving package versions?"""
697715
return self._ps.resolver_dist.include_sdists
698716

717+
@property
718+
def resolver_ignore_platform(self) -> bool:
719+
"""Ignore the platform when resolving with wheels?"""
720+
return self._ps.resolver_dist.ignore_platform
721+
699722
def build_dir(self, sdist_root_dir: pathlib.Path) -> pathlib.Path:
700723
"""Build directory for package (e.g. subdirectory)"""
701724
build_dir = self._ps.build_dir

src/fromager/resolver.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,17 @@
4545

4646
PYTHON_VERSION = Version(python_version())
4747
DEBUG_RESOLVER = os.environ.get("DEBUG_RESOLVER", "")
48-
SUPPORTED_TAGS = set(sys_tags())
4948
PYPI_SERVER_URL = "https://pypi.org/simple"
5049
GITHUB_URL = "https://github.com"
5150

51+
# all supported tags
52+
SUPPORTED_TAGS: frozenset[Tag] = frozenset(sys_tags())
53+
# same, but ignore the platform for 'ignore_platform' flag
54+
IGNORE_PLATFORM: str = "ignore"
55+
SUPPORTED_TAGS_IGNORE_PLATFORM: frozenset[Tag] = frozenset(
56+
Tag(t.interpreter, t.abi, IGNORE_PLATFORM) for t in SUPPORTED_TAGS
57+
)
58+
5259

5360
def resolve(
5461
*,
@@ -58,6 +65,7 @@ def resolve(
5865
include_sdists: bool = True,
5966
include_wheels: bool = True,
6067
req_type: RequirementType | None = None,
68+
ignore_platform: bool = False,
6169
) -> tuple[str, Version]:
6270
# Create the (reusable) resolver.
6371
provider = overrides.find_and_invoke(
@@ -70,6 +78,7 @@ def resolve(
7078
include_wheels=include_wheels,
7179
sdist_server_url=sdist_server_url,
7280
req_type=req_type,
81+
ignore_platform=ignore_platform,
7382
)
7483
return resolve_from_provider(provider, req)
7584

@@ -81,6 +90,7 @@ def default_resolver_provider(
8190
include_sdists: bool,
8291
include_wheels: bool,
8392
req_type: RequirementType | None = None,
93+
ignore_platform: bool = False,
8494
) -> PyPIProvider | GenericProvider | GitHubTagProvider:
8595
"""Lookup resolver provider to resolve package versions"""
8696
return PyPIProvider(
@@ -89,6 +99,7 @@ def default_resolver_provider(
8999
sdist_server_url=sdist_server_url,
90100
constraints=ctx.constraints,
91101
req_type=req_type,
102+
ignore_platform=ignore_platform,
92103
)
93104

94105

@@ -144,6 +155,7 @@ def get_project_from_pypi(
144155
project: str,
145156
extras: typing.Iterable[str],
146157
sdist_server_url: str,
158+
ignore_platform: bool = False,
147159
) -> typing.Iterable[Candidate]:
148160
"""Return candidates created from the project name and extras."""
149161
found_candidates: set[str] = set()
@@ -210,6 +222,15 @@ def get_project_from_pypi(
210222
# FIXME: This doesn't take into account precedence of
211223
# the supported tags for best fit.
212224
matching_tags = SUPPORTED_TAGS.intersection(tags)
225+
if not matching_tags and ignore_platform:
226+
if DEBUG_RESOLVER:
227+
logger.debug(f"{project}: ignoring platform for {filename}")
228+
ignore_platform_tags: frozenset[Tag] = frozenset(
229+
Tag(t.interpreter, t.abi, IGNORE_PLATFORM) for t in tags
230+
)
231+
matching_tags = SUPPORTED_TAGS_IGNORE_PLATFORM.intersection(
232+
ignore_platform_tags
233+
)
213234
if not matching_tags:
214235
if DEBUG_RESOLVER:
215236
logger.debug(f"{project}: ignoring {filename} with tags {tags}")
@@ -272,13 +293,15 @@ def __init__(
272293
sdist_server_url: str = "https://pypi.org/simple/",
273294
constraints: Constraints | None = None,
274295
req_type: RequirementType | None = None,
296+
ignore_platform: bool = False,
275297
):
276298
super().__init__()
277299
self.include_sdists = include_sdists
278300
self.include_wheels = include_wheels
279301
self.sdist_server_url = sdist_server_url
280302
self.constraints = constraints or Constraints()
281303
self.req_type = req_type
304+
self.ignore_platform = ignore_platform
282305

283306
def identify(self, requirement_or_candidate: Requirement | Candidate) -> str:
284307
return canonicalize_name(requirement_or_candidate.name)
@@ -405,13 +428,15 @@ def __init__(
405428
sdist_server_url: str = "https://pypi.org/simple/",
406429
constraints: Constraints | None = None,
407430
req_type: RequirementType | None = None,
431+
ignore_platform: bool = False,
408432
):
409433
super().__init__(
410434
include_sdists=include_sdists,
411435
include_wheels=include_wheels,
412436
sdist_server_url=sdist_server_url,
413437
constraints=constraints,
414438
req_type=req_type,
439+
ignore_platform=ignore_platform,
415440
)
416441

417442
def get_cache(self) -> dict[str, list[Candidate]]:
@@ -456,7 +481,10 @@ def find_matches(
456481
# are added to the candidate at creation - we
457482
# treat candidates as immutable once created.
458483
for candidate in get_project_from_pypi(
459-
identifier, set(), self.sdist_server_url
484+
identifier,
485+
set(),
486+
self.sdist_server_url,
487+
self.ignore_platform,
460488
):
461489
if self.validate_candidate(
462490
identifier, requirements, incompatibilities, candidate

src/fromager/sources.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ def default_resolve_source(
165165
include_sdists=pbi.resolver_include_sdists,
166166
include_wheels=pbi.resolver_include_wheels,
167167
req_type=req_type,
168+
ignore_platform=pbi.resolver_ignore_platform,
168169
)
169170
return url, version
170171

src/fromager/wheels.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,8 @@ def resolve_prebuilt_wheel(
428428
include_sdists=False,
429429
include_wheels=True,
430430
req_type=req_type,
431+
# pre-built wheels must match platform
432+
ignore_platform=False,
431433
)
432434
except Exception:
433435
continue

tests/test_packagesettings.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
GitOptions,
1616
Package,
1717
PackageSettings,
18+
ResolverDist,
1819
SettingsFile,
1920
Variant,
2021
substitute_template,
@@ -67,8 +68,9 @@
6768
},
6869
"resolver_dist": {
6970
"include_sdists": True,
70-
"include_wheels": False,
71+
"include_wheels": True,
7172
"sdist_server_url": "https://sdist.test/egg",
73+
"ignore_platform": True,
7274
},
7375
"variants": {
7476
"cpu": {
@@ -119,6 +121,7 @@
119121
"sdist_server_url": None,
120122
"include_sdists": True,
121123
"include_wheels": False,
124+
"ignore_platform": False,
122125
},
123126
"variants": {},
124127
}
@@ -266,7 +269,8 @@ def test_pbi_test_pkg(testdata_context: context.WorkContext) -> None:
266269
== "test-pkg-1.0.2.tar.gz"
267270
)
268271
assert pbi.resolver_include_sdists is True
269-
assert pbi.resolver_include_wheels is False
272+
assert pbi.resolver_include_wheels is True
273+
assert pbi.resolver_ignore_platform is True
270274
assert (
271275
pbi.resolver_sdist_server_url("https://pypi.org/simple")
272276
== "https://sdist.test/egg"
@@ -630,3 +634,8 @@ def test_package_build_info_exclusive_build(testdata_context: context.WorkContex
630634

631635
custom_pbi = settings.package_build_info("exclusive-pkg")
632636
assert custom_pbi.exclusive_build is True
637+
638+
639+
def test_resolver_dist_validator():
640+
with pytest.raises(pydantic.ValidationError):
641+
ResolverDist(include_wheels=False, ignore_platform=True)

tests/test_resolver.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,64 @@ def test_provider_constraint_match():
267267
assert str(candidate.version) == "1.2.2"
268268

269269

270+
_ignore_platform_simple_response = """
271+
<!DOCTYPE html>
272+
<html>
273+
<head>
274+
<meta name="pypi:repository-version" content="1.1">
275+
<title>Links for fromager</title>
276+
</head>
277+
<body>
278+
<h1>Links for fromager</h1>
279+
<a href="https://files.pythonhosted.org/packages/7c/06/620610984c2794ef55c4257c77211b7a625431b380880c524c2f6bc264b1/fromager-0.51.0-cp311-abi3-manylinux_2_28_plan9.whl" >fromager-0.51.0-cp311-abi3-manylinux_2_28_plan9.whl</a>
280+
<br />
281+
<a href="https://files.pythonhosted.org/packages/7c/06/620610984c2794ef55c4257c77211b7a625431b380880c524c2f6bc264b1/fromager-0.51.0-cp3000-abi3-manylinux_2_28_plan9.whl" >fromager-0.51.0-cp3000-abi3-manylinux_2_28_plan9.whl</a>
282+
</body>
283+
</html>
284+
"""
285+
286+
287+
def test_provider_platform_mismatch():
288+
constraint = constraints.Constraints()
289+
with requests_mock.Mocker() as r:
290+
r.get(
291+
"https://pypi.org/simple/fromager/",
292+
text=_ignore_platform_simple_response,
293+
)
294+
295+
provider = resolver.PyPIProvider(include_wheels=True, constraints=constraint)
296+
reporter = resolvelib.BaseReporter()
297+
rslvr = resolvelib.Resolver(provider, reporter)
298+
299+
with pytest.raises(resolvelib.resolvers.exceptions.ResolverException):
300+
rslvr.resolve([Requirement("fromager")])
301+
302+
303+
def test_provider_ignore_platform():
304+
constraint = constraints.Constraints()
305+
with requests_mock.Mocker() as r:
306+
r.get(
307+
"https://pypi.org/simple/fromager/",
308+
text=_ignore_platform_simple_response,
309+
)
310+
311+
provider = resolver.PyPIProvider(
312+
include_wheels=True, constraints=constraint, ignore_platform=True
313+
)
314+
reporter = resolvelib.BaseReporter()
315+
rslvr = resolvelib.Resolver(provider, reporter)
316+
317+
result = rslvr.resolve([Requirement("fromager")])
318+
assert "fromager" in result.mapping
319+
320+
candidate = result.mapping["fromager"]
321+
assert (
322+
candidate.url
323+
== "https://files.pythonhosted.org/packages/7c/06/620610984c2794ef55c4257c77211b7a625431b380880c524c2f6bc264b1/fromager-0.51.0-cp311-abi3-manylinux_2_28_plan9.whl"
324+
)
325+
assert str(candidate.version) == "0.51.0"
326+
327+
270328
_github_fromager_repo_response = """
271329
{
272330
"id": 808690091,

tests/test_sources.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ def test_default_download_source_from_settings(
4040
req=req,
4141
sdist_server_url=sdist_server_url,
4242
include_sdists=True,
43-
include_wheels=False,
43+
include_wheels=True,
4444
req_type=None,
45+
ignore_platform=True,
4546
)
4647

4748
sources.default_download_source(
@@ -86,6 +87,7 @@ def test_default_download_source_with_predefined_resolve_dist(
8687
include_sdists=False,
8788
include_wheels=True,
8889
req_type=None,
90+
ignore_platform=False,
8991
)
9092

9193

tests/testdata/context/overrides/settings/test_pkg.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ project_override:
3333
resolver_dist:
3434
sdist_server_url: https://sdist.test/egg
3535
include_sdists: true
36-
include_wheels: false
36+
include_wheels: true
37+
ignore_platform: true
3738
variants:
3839
cpu:
3940
env:

0 commit comments

Comments
 (0)