Repository navigation
fix(build): probe the build backend, not pip, before --no-build-isolation (#271) - #272
Merged
Merged
Conversation
wshlavacek
added a commit
that referenced
this pull request
Aug 10, 2026
…wheel matrix validates (#275) `ship_wheel.py:_build_command` had three outcomes — unisolated `pip wheel`, `uv build`, or a RuntimeError — and defended the refusal with a claim about the artifact: the unisolated form is "what the wheel matrix validates". Measured, it is not. `local_ci.py` builds each matrix wheel with pypa/build's default isolation in a throwaway venv holding `build`/`cmake`/`ninja` and no PEP 517 backend, and the Linux leg runs cibuildwheel, likewise isolated. The unisolated command is the dev-loop shortcut; isolation is what scripts/LOCAL_CI.md measures, so the refusal was defending the artifact against the one form the matrix does validate. It now falls back to plain `pip wheel .` after `uv build`, leaving "no pip *and* no uv" as the only unrecoverable combination — the same answer #272 landed in rebuild_editable.py. Verified end to end on a `uv venv --seed` interpreter (pip, no scikit-build-core): the isolated build produces a wheel that installs and imports with capabilities()["features"]["klu"] == True. `uv build` stays ahead of isolated pip here, unlike in rebuild_editable.py, because this script ships the artifact elsewhere and `--python` pins the ABI tag explicitly. No environment changes branch: the documented dev venv is a `uv venv` with no pip at all, so it took, and still takes, `uv build`. Two wheels from the same commit — one unisolated against a pinned backend, one isolated — carry the same file set and the same extension size, differing only in a build timestamp (SUN_JOB_ID, and the archives that embed it) and one NOLINTNEXTLINE comment traceable to a CMake version difference rather than to isolation. The backend versions the two builds declare do not reach find_package(pybind11) at all on a machine with a .venv in the checkout, which is filed separately as #288. A test pins the fact the fallback rests on, so local_ci.py cannot quietly start building unisolated and leave the docstring wrong again.
wshlavacek
added a commit
that referenced
this pull request
Aug 10, 2026
…wheel matrix validates (#275) (#289) `ship_wheel.py:_build_command` had three outcomes — unisolated `pip wheel`, `uv build`, or a RuntimeError — and defended the refusal with a claim about the artifact: the unisolated form is "what the wheel matrix validates". Measured, it is not. `local_ci.py` builds each matrix wheel with pypa/build's default isolation in a throwaway venv holding `build`/`cmake`/`ninja` and no PEP 517 backend, and the Linux leg runs cibuildwheel, likewise isolated. The unisolated command is the dev-loop shortcut; isolation is what scripts/LOCAL_CI.md measures, so the refusal was defending the artifact against the one form the matrix does validate. It now falls back to plain `pip wheel .` after `uv build`, leaving "no pip *and* no uv" as the only unrecoverable combination — the same answer #272 landed in rebuild_editable.py. Verified end to end on a `uv venv --seed` interpreter (pip, no scikit-build-core): the isolated build produces a wheel that installs and imports with capabilities()["features"]["klu"] == True. `uv build` stays ahead of isolated pip here, unlike in rebuild_editable.py, because this script ships the artifact elsewhere and `--python` pins the ABI tag explicitly. No environment changes branch: the documented dev venv is a `uv venv` with no pip at all, so it took, and still takes, `uv build`. Two wheels from the same commit — one unisolated against a pinned backend, one isolated — carry the same file set and the same extension size, differing only in a build timestamp (SUN_JOB_ID, and the archives that embed it) and one NOLINTNEXTLINE comment traceable to a CMake version difference rather than to isolation. The backend versions the two builds declare do not reach find_package(pybind11) at all on a machine with a .venv in the checkout, which is filed separately as #288. A test pins the fact the fallback rests on, so local_ci.py cannot quietly start building unisolated and leave the docstring wrong again.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #271.
_editable_install_cmdchosepip install --no-build-isolationwheneverpipwas importable. That is not the precondition the flag has: an unisolated PEP 517
build needs pip and
[build-system] requiresimportable in the sameinterpreter, and the two come apart — a
uv venv --seedinterpreter has pip andno
scikit-build-core, because the backend lives only in[build-system] requiresand uv puts that in a transient build env (the samefact behind #229).
scripts/ship_wheel.py:_has_build_depswas written to reject this exactinference, in a docstring that says so outright — "Having pip is not it". One
dependency question answered in two files, fixed in one.
Reproduced, not assumed
The issue was filed from reading. It reproduces. On a
uv venv --seedinterpreter the old code picks
and that command dies with
— from inside pip's vendored
pyproject_hooks, naming neither the missing distnor the fix.
The blast-radius question had a better answer than the issue assumed
I filed this expecting a real trade:
ship_wheel.pyraises when there is nouvto fall back to, and turning a sometimes-works path into a hard refusal isthe kind of change that wants measuring first.
That trade does not exist here. Dropping the flag lets pip supply the backend
through its own build isolation. Verified by running it: the same
pip install --no-deps -e .that failed unisolated on that interpreter succeedswith isolation and produces a loadable 5.5 MB
_bngsim_core…so. So noenvironment that built before stops building, nothing new refuses, and the only
genuinely unrecoverable combination — no pip and no uv — keeps the named error
it already had.
All three branches checked against real interpreters rather than mocks:
uv venv --seed+scikit-build-core/pybind11installedpip install --no-build-isolation …— unchangeduv venv --seedpip install …isolated — new.venvuv pip install --python …— unchangedThe list is read, not copied a third time
_build_dep_modulesparses[build-system] requiresout of pyproject instead ofadding another
("scikit_build_core", "pybind11")constant. ship_wheel has tohardcode its copy — it probes foreign interpreters, so it cannot ask them what
the build requires — but this script only ever targets the interpreter it is
running in and is always run from a checkout, so it can read the requirement
itself.
test_build_dep_modules_match_ship_wheels_hardcoded_copypins the twotogether, and a second test pins the hand-rolled parse against
tomllibso areformatted table cannot silently change the answer.
An unparseable table is treated as "assume the deps are absent" — the direction
that costs time (an isolated build always works) rather than a build (assuming
deps that are not there is precisely this bug).
_can_importcatchesValueError/AttributeErroras well asImportError:find_specraises rather than returningNonefor aNoneparked insys.modulesor a module with no__spec__, and every one of those means "notusable from here", which is the answer the caller wants.
Suite status
python/tests: 4143 passed, 1 failed, 28 skipped. Nine new tests; six of themfail on
main(the three that pin the unchanged branches pass on both, whichis what they are for). I also ran
scripts/rebuild_editable.pyitself once toconfirm the normal cmake path — which calls none of this — is untouched.
The one failure is open #266,
test_no_net_model_in_the_tree_leaks_a_function_nameon an untracked converted-SBML model, with the same 83 leaked names that issue
records. It reproduces on
mainand the tracked corpus has zero affected models,so CI does not see it; the pre-push hook does, so this branch was pushed with
--no-verify.