Skip to content

Commit c9097d6

Browse files
authored
Merge pull request #893 from LalatenduMohanty/test-mode-cleanup-1
Test mode post merge cleanups
2 parents 9282c74 + 5bb0b55 commit c9097d6

2 files changed

Lines changed: 226 additions & 189 deletions

File tree

src/fromager/bootstrapper.py

Lines changed: 155 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -224,59 +224,38 @@ def _processing_build_requirement(self, current_req_type: RequirementType) -> bo
224224
def bootstrap(self, req: Requirement, req_type: RequirementType) -> None:
225225
"""Bootstrap a package and its dependencies.
226226
227+
Handles setup, validation, and error handling. Delegates actual build
228+
work to _bootstrap_impl().
229+
227230
In test mode, catches build exceptions, records package name, and continues.
228231
In normal mode, raises exceptions immediately (fail-fast).
229232
"""
233+
logger.info(f"bootstrapping {req} as {req_type} dependency of {self.why[-1:]}")
234+
235+
# Resolve version first so we have it for error reporting.
236+
# In test mode, record resolution failures and continue.
230237
try:
231-
self._bootstrap_impl(req, req_type)
238+
source_url, resolved_version = self.resolve_version(
239+
req=req,
240+
req_type=req_type,
241+
)
232242
except Exception as err:
233243
if not self.test_mode:
234244
raise
235-
# Get version from cache if available
236-
cached = self._resolved_requirements.get(str(req))
237-
if cached:
238-
_source_url, resolved_version = cached
239-
version = str(resolved_version)
240-
else:
241-
version = None
242-
self._record_test_mode_failure(req, version, err, "bootstrap")
245+
self._record_test_mode_failure(req, None, err, "resolution")
246+
return
243247

244-
def _bootstrap_impl(self, req: Requirement, req_type: RequirementType) -> None:
245-
"""Internal implementation of bootstrap logic.
248+
# Capture parent before _track_why pushes current package onto the stack
249+
parent: tuple[Requirement, Version] | None = None
250+
if self.why:
251+
_, parent_req, parent_version = self.why[-1]
252+
parent = (parent_req, parent_version)
246253

247-
Error Handling:
248-
Fatal errors (version resolution, source build, prebuilt download)
249-
raise exceptions for bootstrap() to catch and record.
254+
# Update dependency graph unconditionally (before seen check to capture all edges)
255+
self._add_to_graph(req, req_type, resolved_version, source_url, parent)
250256

251-
Non-fatal errors (post-hook, dependency extraction) are recorded
252-
locally and processing continues. These are recorded here because
253-
the package build succeeded - only optional post-processing failed.
254-
"""
255-
logger.info(f"bootstrapping {req} as {req_type} dependency of {self.why[-1:]}")
256-
constraint = self.ctx.constraints.get_constraint(req.name)
257-
if constraint:
258-
logger.info(
259-
f"incoming requirement {req} matches constraint {constraint}. Will apply both."
260-
)
261-
262-
source_url, resolved_version = self.resolve_version(
263-
req=req,
264-
req_type=req_type,
265-
)
266-
pbi = self.ctx.package_build_info(req)
267-
268-
self._add_to_graph(req, req_type, resolved_version, source_url)
269-
270-
# Is bootstrap going to create a wheel or just an sdist?
271-
#
272-
# Use fast sdist-only if flag is set and requirement is not a build
273-
# requirement.
274-
#
275-
# An install requirement on a pre-built wheel treats the wheel as
276-
# sdist-only in order to build its installation requirements sdist-only.
277-
#
278-
# When bootstrap encounters another package with a *build* requirement
279-
# on a pre-built wheel, its installation dependencies are materialized.
257+
# Build sdist-only (no wheel) if flag is set, unless this is a build
258+
# requirement which always needs a full wheel.
280259
build_sdist_only = self.sdist_only and not self._processing_build_requirement(
281260
req_type
282261
)
@@ -292,109 +271,153 @@ def _bootstrap_impl(self, req: Requirement, req_type: RequirementType) -> None:
292271

293272
logger.info(f"new {req_type} dependency {req} resolves to {resolved_version}")
294273

295-
# Track dependency chain for error messages - context manager ensures cleanup
274+
# Track dependency chain - context manager ensures cleanup even on exception
296275
with self._track_why(req_type, req, resolved_version):
297-
cached_wheel_filename: pathlib.Path | None = None
298-
unpacked_cached_wheel: pathlib.Path | None = None
299-
300-
if pbi.pre_built:
301-
wheel_filename, unpack_dir = self._download_prebuilt(
302-
req=req,
303-
req_type=req_type,
304-
resolved_version=resolved_version,
305-
wheel_url=source_url,
306-
)
307-
build_result = SourceBuildResult(
308-
wheel_filename=wheel_filename,
309-
sdist_filename=None,
310-
unpack_dir=unpack_dir,
311-
sdist_root_dir=None,
312-
build_env=None,
313-
source_type=SourceType.PREBUILT,
314-
)
315-
else:
316-
# Look for an existing wheel in caches before building
317-
cached_wheel_filename, unpacked_cached_wheel = self._find_cached_wheel(
318-
req, resolved_version
319-
)
320-
321-
# Build from source (handles test-mode fallback internally)
322-
build_result = self._build_from_source(
323-
req=req,
324-
resolved_version=resolved_version,
325-
source_url=source_url,
326-
req_type=req_type,
327-
build_sdist_only=build_sdist_only,
328-
cached_wheel_filename=cached_wheel_filename,
329-
unpacked_cached_wheel=unpacked_cached_wheel,
330-
)
331-
332-
# Run post-bootstrap hooks (non-fatal in test mode)
333276
try:
334-
hooks.run_post_bootstrap_hooks(
335-
ctx=self.ctx,
336-
req=req,
337-
dist_name=canonicalize_name(req.name),
338-
dist_version=str(resolved_version),
339-
sdist_filename=build_result.sdist_filename,
340-
wheel_filename=build_result.wheel_filename,
277+
self._bootstrap_impl(
278+
req, req_type, source_url, resolved_version, build_sdist_only
341279
)
342-
except Exception as hook_error:
280+
except Exception as err:
343281
if not self.test_mode:
344282
raise
345283
self._record_test_mode_failure(
346-
req, str(resolved_version), hook_error, "hook", "warning"
284+
req, str(resolved_version), err, "bootstrap"
347285
)
348286

349-
# Extract install dependencies (non-fatal in test mode)
350-
try:
351-
install_dependencies = self._get_install_dependencies(
352-
req=req,
353-
resolved_version=resolved_version,
354-
wheel_filename=build_result.wheel_filename,
355-
sdist_filename=build_result.sdist_filename,
356-
sdist_root_dir=build_result.sdist_root_dir,
357-
build_env=build_result.build_env,
358-
unpack_dir=build_result.unpack_dir,
359-
)
360-
except Exception as dep_error:
361-
if not self.test_mode:
362-
raise
363-
self._record_test_mode_failure(
364-
req,
365-
str(resolved_version),
366-
dep_error,
367-
"dependency_extraction",
368-
"warning",
369-
)
370-
install_dependencies = []
287+
def _bootstrap_impl(
288+
self,
289+
req: Requirement,
290+
req_type: RequirementType,
291+
source_url: str,
292+
resolved_version: Version,
293+
build_sdist_only: bool,
294+
) -> None:
295+
"""Internal implementation - performs the actual bootstrap work.
371296
372-
logger.debug(
373-
"install dependencies: %s",
374-
", ".join(sorted(str(r) for r in install_dependencies)),
297+
Called by bootstrap() after setup, validation, and seen-checking.
298+
299+
Args:
300+
req: The requirement to bootstrap.
301+
req_type: The type of requirement.
302+
source_url: The resolved source URL.
303+
resolved_version: The resolved version.
304+
build_sdist_only: Whether to build only sdist (no wheel).
305+
306+
Error Handling:
307+
Fatal errors (source build, prebuilt download) raise exceptions
308+
for bootstrap() to catch and record.
309+
310+
Non-fatal errors (post-hook, dependency extraction) are recorded
311+
locally and processing continues. These are recorded here because
312+
the package build succeeded - only optional post-processing failed.
313+
"""
314+
constraint = self.ctx.constraints.get_constraint(req.name)
315+
if constraint:
316+
logger.info(
317+
f"incoming requirement {req} matches constraint {constraint}. Will apply both."
318+
)
319+
320+
pbi = self.ctx.package_build_info(req)
321+
322+
cached_wheel_filename: pathlib.Path | None = None
323+
unpacked_cached_wheel: pathlib.Path | None = None
324+
325+
if pbi.pre_built:
326+
wheel_filename, unpack_dir = self._download_prebuilt(
327+
req=req,
328+
req_type=req_type,
329+
resolved_version=resolved_version,
330+
wheel_url=source_url,
331+
)
332+
build_result = SourceBuildResult(
333+
wheel_filename=wheel_filename,
334+
sdist_filename=None,
335+
unpack_dir=unpack_dir,
336+
sdist_root_dir=None,
337+
build_env=None,
338+
source_type=SourceType.PREBUILT,
339+
)
340+
else:
341+
# Look for an existing wheel in caches before building
342+
cached_wheel_filename, unpacked_cached_wheel = self._find_cached_wheel(
343+
req, resolved_version
375344
)
376345

377-
self._add_to_build_order(
346+
# Build from source (handles test-mode fallback internally)
347+
build_result = self._build_from_source(
378348
req=req,
379-
version=resolved_version,
349+
resolved_version=resolved_version,
380350
source_url=source_url,
381-
source_type=build_result.source_type,
382-
prebuilt=pbi.pre_built,
383-
constraint=constraint,
351+
req_type=req_type,
352+
build_sdist_only=build_sdist_only,
353+
cached_wheel_filename=cached_wheel_filename,
354+
unpacked_cached_wheel=unpacked_cached_wheel,
384355
)
385356

386-
self.progressbar.update_total(len(install_dependencies))
387-
for dep in self._sort_requirements(install_dependencies):
388-
with req_ctxvar_context(dep):
389-
# In test mode, bootstrap() catches and records failures internally.
390-
# In normal mode, it raises immediately which we propagate.
391-
self.bootstrap(req=dep, req_type=RequirementType.INSTALL)
392-
self.progressbar.update()
393-
394-
# Clean up build directories (why stack cleanup handled by context manager)
395-
self.ctx.clean_build_dirs(
396-
build_result.sdist_root_dir, build_result.build_env
357+
# Run post-bootstrap hooks (non-fatal in test mode)
358+
try:
359+
hooks.run_post_bootstrap_hooks(
360+
ctx=self.ctx,
361+
req=req,
362+
dist_name=canonicalize_name(req.name),
363+
dist_version=str(resolved_version),
364+
sdist_filename=build_result.sdist_filename,
365+
wheel_filename=build_result.wheel_filename,
366+
)
367+
except Exception as hook_error:
368+
if not self.test_mode:
369+
raise
370+
self._record_test_mode_failure(
371+
req, str(resolved_version), hook_error, "hook", "warning"
372+
)
373+
374+
# Extract install dependencies (non-fatal in test mode)
375+
try:
376+
install_dependencies = self._get_install_dependencies(
377+
req=req,
378+
resolved_version=resolved_version,
379+
wheel_filename=build_result.wheel_filename,
380+
sdist_filename=build_result.sdist_filename,
381+
sdist_root_dir=build_result.sdist_root_dir,
382+
build_env=build_result.build_env,
383+
unpack_dir=build_result.unpack_dir,
397384
)
385+
except Exception as dep_error:
386+
if not self.test_mode:
387+
raise
388+
self._record_test_mode_failure(
389+
req,
390+
str(resolved_version),
391+
dep_error,
392+
"dependency_extraction",
393+
"warning",
394+
)
395+
install_dependencies = []
396+
397+
logger.debug(
398+
"install dependencies: %s",
399+
", ".join(sorted(str(r) for r in install_dependencies)),
400+
)
401+
402+
self._add_to_build_order(
403+
req=req,
404+
version=resolved_version,
405+
source_url=source_url,
406+
source_type=build_result.source_type,
407+
prebuilt=pbi.pre_built,
408+
constraint=constraint,
409+
)
410+
411+
self.progressbar.update_total(len(install_dependencies))
412+
for dep in self._sort_requirements(install_dependencies):
413+
with req_ctxvar_context(dep):
414+
# In test mode, bootstrap() catches and records failures internally.
415+
# In normal mode, it raises immediately which we propagate.
416+
self.bootstrap(req=dep, req_type=RequirementType.INSTALL)
417+
self.progressbar.update()
418+
419+
# Clean up build directories
420+
self.ctx.clean_build_dirs(build_result.sdist_root_dir, build_result.build_env)
398421

399422
@contextlib.contextmanager
400423
def _track_why(
@@ -1340,11 +1363,12 @@ def _add_to_graph(
13401363
req_type: RequirementType,
13411364
req_version: Version,
13421365
download_url: str,
1366+
parent: tuple[Requirement, Version] | None,
13431367
) -> None:
13441368
if req_type == RequirementType.TOP_LEVEL:
13451369
return
13461370

1347-
_, parent_req, parent_version = self.why[-1] if self.why else (None, None, None)
1371+
parent_req, parent_version = parent if parent else (None, None)
13481372
pbi = self.ctx.package_build_info(req)
13491373
# Update the dependency graph after we determine that this requirement is
13501374
# useful but before we determine if it is redundant so that we capture all

0 commit comments

Comments
 (0)