Bootstrap --test-mode: resilient source builds with pre-built fallback
Author: Lalatendu Mohanty, Doug Hellmann
Created: 2026-06-01 (retrospective; feature merged 2025-12-19)
Status: Implemented
GitHub issue: #713
Pull request: #865
What
Serial fromager bootstrap --test-mode continues after failures instead of
aborting. On source-build failure, fromager downloads a pre-built wheel for
the same package so traversal can proceed; dependents are still discovered and
built. The run ends with test-mode-failures-<timestamp>.json and exit code 1
if any failure was recorded.
Primary outputs of a degraded run are graph.json, build-order.json, and the
failure report—not a full set of source-built wheels.
Why
Large source bootstraps often fail one package at a time. Without fallback,
dependents fail too, hiding which packages have real build problems. Operators
work around this by manually marking failures as pre_built and re-running
bootstrap repeatedly (#713).
Test mode automates that loop: continue traversal, substitute pre-built wheels on source failure, surface many build gaps in one run, and emit a structured failure list for automation.
Goals
Continue serial
bootstrapafter failures; fallback viaBootstrapRequirementResolver.resolve(..., pre_built=True).Classify failures:
resolution,bootstrap(fatal, error log) vshook,dependency_extraction(non-fatal, warning log). All are recorded; any failure yields exit code 1.Preserve graph traversal and record
source_url_typeinbuild-order.json("prebuilt"marks fallback wheels).
Non-goals
bootstrap-parallel(serial only); incompatible with--sdist-only.Replacing fail-fast default bootstrap or treating fallback wheels as the shipped product.
Recording source-build failures when fallback succeeds (#1166).
How
commands/bootstrap.py enables the flag; logic lives in Bootstrapper
(iterative-bootstrap.rst).
Architecture
Run flow — serial bootstrap with --test-mode never aborts the whole
command on a single package failure; it finishes the run and reports at the end.
Resolve top-level requirements → Bootstrap dependency tree → finalize → graph.json + build-order.json + failures JSON
On error — _handle_phase_error() in bootstrapper.py decides whether to
skip the package, substitute a pre-built wheel, or continue with a warning.
On phase error, branch by context:
Resolution → Record failure, skip package
Source build → Is a pre-built wheel available?
Yes → Use wheel, continue traversal
No → Record failure, skip package
Hook or dep extraction → Record warning, continue
Phase errors (_handle_phase_error()):
Context |
Behavior |
|---|---|
|
Record |
|
|
Post-bootstrap hooks |
Record |
Install dep extraction |
Record |
Settings pre_built packages skip the fallback path. Failure records include
package, version, exception type/message, and failure_type; see
FailureRecord in bootstrapper.py.
Artifacts: build-order.json source_url_type reflects runtime fallback;
graph node pre_built reflects settings only (not updated after fallback).
|
|
|
|---|---|---|
Purpose |
Source-build gap analysis |
All matching versions |
On build failure |
Pre-built fallback, keep traversing |
Remove version from graph |
Output |
|
Logs only |
Usage
fromager bootstrap --test-mode -r requirements.txt
Review test-mode-failures-*.json, build-order.json (source_url_type: "prebuilt"), and graph.json. Tests: tests/test_bootstrap_test_mode.py,
e2e/test_mode_*.sh.
Limitations
Source-build failure not recorded when fallback succeeds (#1166).
Audit fallback via
build-order.json, not graphpre_built.Serial bootstrap only.
Pre-built version may differ from requested source version (warning logged).
Key source files
commands/bootstrap.py, bootstrapper.py, bootstrap_requirement_resolver.py,
dependency_graph.py