Skip to content

Commit e60f4ba

Browse files
authored
Merge pull request #949 from rd4398/single-resolve-mehtod
(refactor): RequirementsResolver should have only one method resolve()
2 parents c509b13 + 6ff2394 commit e60f4ba

3 files changed

Lines changed: 248 additions & 80 deletions

File tree

src/fromager/bootstrapper.py

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -183,40 +183,34 @@ def resolve_version(
183183
Delegates PyPI/graph resolution to RequirementResolver.
184184
"""
185185
if req.url:
186-
# Git URLs must be resolved in Bootstrapper (orchestration concern)
187186
if req_type != RequirementType.TOP_LEVEL:
188187
raise ValueError(
189188
f"{req} includes a URL, but is not a top-level dependency"
190189
)
191190

192191
# Check cache first to avoid re-resolving
193-
cached_result = self._resolver.get_cached_resolution(req)
192+
# Git URLs are always source (not prebuilt)
193+
cached_result = self._resolver.get_cached_resolution(req, pre_built=False)
194194
if cached_result is not None:
195195
logger.debug(f"resolved {req} from cache")
196196
return cached_result
197197

198198
logger.info("resolving source via URL, ignoring any plugins")
199199
source_url, resolved_version = self._resolve_version_from_git_url(req=req)
200-
# Cache the git URL resolution
201-
self._resolver.cache_resolution(req, (source_url, resolved_version))
200+
# Cache the git URL resolution (always source, not prebuilt)
201+
self._resolver.cache_resolution(
202+
req, pre_built=False, result=(source_url, resolved_version)
203+
)
202204
return source_url, resolved_version
203205

204-
# Delegate to RequirementResolver (handles caching internally)
206+
# Delegate to RequirementResolver
205207
parent_req = self.why[-1][1] if self.why else None
206-
pbi = self.ctx.package_build_info(req)
207208

208-
if pbi.pre_built:
209-
return self._resolver.resolve_prebuilt(
210-
req=req,
211-
req_type=req_type,
212-
parent_req=parent_req,
213-
)
214-
else:
215-
return self._resolver.resolve_source(
216-
req=req,
217-
req_type=req_type,
218-
parent_req=parent_req,
219-
)
209+
return self._resolver.resolve(
210+
req=req,
211+
req_type=req_type,
212+
parent_req=parent_req,
213+
)
220214

221215
def _processing_build_requirement(self, current_req_type: RequirementType) -> bool:
222216
"""Are we currently processing a build requirement?
@@ -927,10 +921,11 @@ def _handle_test_mode_failure(
927921

928922
try:
929923
parent_req = self.why[-1][1] if self.why else None
930-
wheel_url, fallback_version = self._resolver.resolve_prebuilt(
924+
wheel_url, fallback_version = self._resolver.resolve(
931925
req=req,
932926
req_type=req_type,
933927
parent_req=parent_req,
928+
pre_built=True, # Force prebuilt for test mode fallback
934929
)
935930

936931
if fallback_version != resolved_version:

src/fromager/requirement_resolver.py

Lines changed: 61 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -50,139 +50,140 @@ def __init__(
5050
self.ctx = ctx
5151
self.prev_graph = prev_graph
5252
# Session-level resolution cache to avoid re-resolving same requirements
53-
self._resolved_requirements: dict[str, tuple[str, Version]] = {}
53+
# Key: (requirement_string, pre_built) to distinguish source vs prebuilt
54+
self._resolved_requirements: dict[tuple[str, bool], tuple[str, Version]] = {}
5455

55-
def resolve_source(
56+
def resolve(
5657
self,
5758
req: Requirement,
5859
req_type: RequirementType,
5960
parent_req: Requirement | None = None,
61+
pre_built: bool | None = None,
6062
) -> tuple[str, Version]:
61-
"""Resolve source package (sdist).
63+
"""Resolve package requirement.
6264
6365
Tries resolution strategies in order:
6466
1. Session cache (if previously resolved)
6567
2. Previous dependency graph
66-
3. PyPI source resolution
68+
3. PyPI resolution (source or prebuilt based on package build info)
6769
6870
Args:
69-
req: Package requirement (must NOT have URL)
71+
req: Package requirement
7072
req_type: Type of requirement
7173
parent_req: Parent requirement from dependency chain
74+
pre_built: Optional override to force prebuilt (True) or source (False).
75+
If None (default), uses package build info to determine.
7276
7377
Returns:
74-
Tuple of (source_url, resolved_version)
78+
Tuple of (url, resolved_version)
7579
7680
Raises:
77-
ValueError: If req contains a URL (must use Bootstrapper for git URLs)
81+
ValueError: If req contains a git URL and pre_built is False
82+
(git URL source resolution must be handled by Bootstrapper)
7883
"""
79-
if req.url:
84+
# Determine pre_built if not specified (needed for cache key and URL guard)
85+
if pre_built is None:
86+
pbi = self.ctx.package_build_info(req)
87+
pre_built = pbi.pre_built
88+
89+
# Git URL source resolution must be handled by Bootstrapper.
90+
# But git URL prebuilt resolution is allowed - we look for wheels on PyPI
91+
# (test mode fallback uses this path).
92+
if req.url and not pre_built:
8093
raise ValueError(
8194
f"Git URL requirements must be handled by Bootstrapper: {req}"
8295
)
8396

84-
# Check session cache first
85-
cached_result = self.get_cached_resolution(req)
97+
# Check session cache (keyed by requirement + pre_built)
98+
cached_result = self.get_cached_resolution(req, pre_built)
8699
if cached_result is not None:
87100
logger.debug(f"resolved {req} from cache")
88101
return cached_result
89102

90-
# Try graph
91-
cached_resolution = self._resolve_from_graph(
92-
req=req,
93-
req_type=req_type,
94-
pre_built=False,
95-
parent_req=parent_req,
96-
)
97-
if cached_resolution:
98-
source_url, resolved_version = cached_resolution
99-
logger.debug(f"resolved from previous bootstrap to {resolved_version}")
100-
else:
101-
# Fallback to PyPI
102-
source_url, resolved_version = sources.resolve_source(
103-
ctx=self.ctx,
104-
req=req,
105-
sdist_server_url=resolver.PYPI_SERVER_URL,
106-
req_type=req_type,
107-
)
103+
# Resolve using strategies
104+
url, resolved_version = self._resolve(req, req_type, parent_req, pre_built)
108105

109106
# Cache the result
110-
result = (source_url, resolved_version)
111-
self.cache_resolution(req, result)
112-
return source_url, resolved_version
107+
result = (url, resolved_version)
108+
self.cache_resolution(req, pre_built, result)
109+
return url, resolved_version
113110

114-
def resolve_prebuilt(
111+
def _resolve(
115112
self,
116113
req: Requirement,
117114
req_type: RequirementType,
118-
parent_req: Requirement | None = None,
115+
parent_req: Requirement | None,
116+
pre_built: bool,
119117
) -> tuple[str, Version]:
120-
"""Resolve pre-built package (wheels only).
118+
"""Internal resolution logic without caching.
121119
122120
Tries resolution strategies in order:
123-
1. Session cache (if previously resolved)
124-
2. Previous dependency graph
125-
3. PyPI wheel resolution
121+
1. Previous dependency graph
122+
2. PyPI resolution (source or prebuilt)
126123
127124
Args:
128125
req: Package requirement
129126
req_type: Type of requirement
130127
parent_req: Parent requirement from dependency chain
128+
pre_built: Whether to resolve prebuilt (True) or source (False)
131129
132130
Returns:
133-
Tuple of (source_url, resolved_version)
134-
135-
Raises:
136-
ValueError: If unable to resolve
131+
Tuple of (url, resolved_version)
137132
"""
138-
# Check session cache first
139-
cached_result = self.get_cached_resolution(req)
140-
if cached_result is not None:
141-
logger.debug(f"resolved {req} from cache")
142-
return cached_result
143-
144133
# Try graph
145134
cached_resolution = self._resolve_from_graph(
146135
req=req,
147136
req_type=req_type,
148-
pre_built=True,
137+
pre_built=pre_built,
149138
parent_req=parent_req,
150139
)
151140

152141
if cached_resolution and not req.url:
153-
wheel_url, resolved_version = cached_resolution
142+
url, resolved_version = cached_resolution
154143
logger.debug(f"resolved from previous bootstrap to {resolved_version}")
155-
else:
156-
# Fallback to PyPI prebuilt resolution
144+
return url, resolved_version
145+
146+
# Fallback to PyPI
147+
if pre_built:
148+
# Resolve prebuilt wheel
157149
servers = wheels.get_wheel_server_urls(
158150
self.ctx, req, cache_wheel_server_url=resolver.PYPI_SERVER_URL
159151
)
160-
wheel_url, resolved_version = wheels.resolve_prebuilt_wheel(
152+
url, resolved_version = wheels.resolve_prebuilt_wheel(
161153
ctx=self.ctx, req=req, wheel_server_urls=servers, req_type=req_type
162154
)
155+
else:
156+
# Resolve source (sdist)
157+
url, resolved_version = sources.resolve_source(
158+
ctx=self.ctx,
159+
req=req,
160+
sdist_server_url=resolver.PYPI_SERVER_URL,
161+
req_type=req_type,
162+
)
163163

164-
# Cache the result
165-
result = (wheel_url, resolved_version)
166-
self.cache_resolution(req, result)
167-
return wheel_url, resolved_version
164+
return url, resolved_version
168165

169166
def get_cached_resolution(
170167
self,
171168
req: Requirement,
169+
pre_built: bool,
172170
) -> tuple[str, Version] | None:
173171
"""Get a cached resolution result if it exists.
174172
175173
Args:
176174
req: Package requirement to look up in cache
175+
pre_built: Whether looking for prebuilt or source resolution
177176
178177
Returns:
179178
Tuple of (source_url, resolved_version) if cached, None otherwise
180179
"""
181-
return self._resolved_requirements.get(str(req))
180+
cache_key = (str(req), pre_built)
181+
return self._resolved_requirements.get(cache_key)
182182

183183
def cache_resolution(
184184
self,
185185
req: Requirement,
186+
pre_built: bool,
186187
result: tuple[str, Version],
187188
) -> None:
188189
"""Cache a resolution result.
@@ -192,9 +193,11 @@ def cache_resolution(
192193
193194
Args:
194195
req: Package requirement to cache
196+
pre_built: Whether this is a prebuilt or source resolution
195197
result: Tuple of (source_url, resolved_version)
196198
"""
197-
self._resolved_requirements[str(req)] = result
199+
cache_key = (str(req), pre_built)
200+
self._resolved_requirements[cache_key] = result
198201

199202
def _resolve_from_graph(
200203
self,

0 commit comments

Comments
 (0)