@@ -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