build: ship a genuine pure-Python wheel for Pyodide/JupyterLite - #632
Merged
Conversation
CMakeLists.txt: RTB_BUILD_EXTENSIONS option (default ON) wraps both
nanobind_add_module calls -- set -DRTB_BUILD_EXTENSIONS=OFF to skip them.
Both _fknm_c and _frne_c already have complete, tested pure-Python
fallbacks (fknm.py/frne.py, tests/test_fknm_fallback.py), so this is a
real working configuration.
pyproject.toml: RTB_PURE_WHEEL=1 env var triggers a scikit-build-core
override (wheel.platlib=false) that forces the wheel tag to py3-none-any,
confirmed by reading scikit-build-core's own wheel_tag.py. Combined with
the CMake option above (via cmake.define), this replaces cross-compiling
a wasm32 binary via cibuildwheel's Pyodide platform -- which coupled the
wheel to one specific Pyodide/Emscripten ABI version and to Pyodide's
JSPI-default-on cutover (Safari/Firefox lack JSPI) -- with an ordinary
wheel built on a normal runner. Verified locally: RTB_PURE_WHEEL=1 build
produces roboticstoolbox_python-1.4.0-py3-none-any.whl with zero compiled
targets ("ninja: no work to do"), installs and runs correctly (fkine,
jacob0, ikine_LM all verified against a fresh venv), and a normal build
without the env var is unaffected (still produces a compiled cp312 wheel).
release.yml's build_pyodide job no longer uses cibuildwheel's Pyodide platform (no Emscripten SDK, no cross-compilation) -- just a plain `python -m build --wheel` with RTB_PURE_WHEEL=1 on a normal ubuntu-latest runner. Since the resulting py3-none-any wheel has no ABI to be rejected over, upload_pypi's "exclude pyodide wheel" step and the whole upload_pyodide_asset job (GitHub-Release-asset workaround) are gone -- it just uploads to PyPI like every other wheel. ci.yml's docs-build job builds this wheel fresh from the checked-out source on every push instead of fetching one from the latest GitHub Release -- feasible now because there's no compilation involved, so it's fast. This also fixes an existing staleness gap: "Try it Now" now tracks bleeding-edge main instead of lagging behind the last release. spatialgeometry's own wasm wheel is untouched here (still fetched via cross-repo `gh release download`, still cp312-pyodide-tagged) -- SG hasn't adopted the same fix yet (tracked: jhavl/spatialgeometry#46). Updated that step's comment to reflect the new asymmetry accurately. docs/source/intro.rst's "available as a GitHub release resource" line was describing the now-removed upload_pyodide_asset mechanism; updated.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #632 +/- ##
=====================================
Coverage 0.00% 0.00%
=====================================
Files 143 143
Lines 14037 14035 -2
=====================================
+ Misses 14037 14035 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… path Cherry-picked from the stale, never-merged feat/pyodide-pure-wheel-v2 branch (pushed 2026-07-23, 109 commits behind main as of this cherry-pick -- too stale to merge as-is, but these two findings are real and still live on main). That branch ran the full test suite inside a real Pyodide environment with zero compiled extensions present; both bugs reproduce identically with just the C extension absent, no real Pyodide required. tests/test_ETS.py::test_insert: genuine off-by-one -- a 7-joint ETS (jindex 0-6) tested with a 6-element q. The C extension has no bounds checking, so q[6] silently read past the array end (undefined behaviour, happened not to crash); the pure-Python fallback correctly raises IndexError. Verified fail (IndexError against the pure wheel) -> fix (q now has 7 elements) -> pass (both the pure-Python and compiled paths). URDFRobot.py's _load_rd_module: the sys.platform == "emscripten" guard only checked inside `except Exception`, not `except ImportError`. GitPython's subprocess-spawn failure under Pyodide surfaces as a plain ImportError, which the candidates loop's `except ImportError: continue` treated as "try the next name" -- after exhausting every candidate this fell through to a misleading "model renamed"/"not found" error instead of the correct, actionable one. Fixed by checking the platform up front, before the loop, since the outcome doesn't depend on which candidate name is tried. Verified fail (misleading rename error) -> fix -> pass (correct "browser sandbox" error), both by loading the pre/post-fix file content directly (bypassing an unrelated editable-install path collision) and via a proper pytest run against a real editable install of this worktree. New regression test: tests/test_URDFRobot.py, same patch.object(sys, "platform", "emscripten") pattern already used in tests/test_collision.py. Two other findings from that branch (test_ET.py's ungated C-only assertions, a WASM-numpy-specific float tolerance in test_trajectory.py) were not ported -- not live bugs, and the second can't be reproduced without a real Pyodide/WASM numpy build. See #633 for the fuller discussion, including why the branch's real-Pyodide CI job itself wasn't ported (three of its four findings were actually pure-Python-path coverage gaps, not WASM-specific -- catchable much more cheaply now that build_pyodide produces a real installable pure-Python wheel).
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.
Summary
Closes #627.
Replaces the cross-compiled wasm32 wheel (built via cibuildwheel's Pyodide platform, forced to cp312/Pyodide 0.27.x to stay JSPI-safe for Safari — see PR #626) with a genuine
py3-none-anywheel built on a normal runner.CMakeLists.txt: newRTB_BUILD_EXTENSIONSoption (defaultON) wraps bothnanobind_add_modulecalls. Both_fknm_cand_frne_calready have complete, tested pure-Python fallbacks (fknm.py/frne.py,tests/test_fknm_fallback.py).pyproject.toml:RTB_PURE_WHEEL=1triggers a scikit-build-core override (wheel.platlib = false) that forces the wheel tag topy3-none-any— confirmed directly from scikit-build-core's ownwheel_tag.py(root_is_purelib→plats=["any"], abi="none", pyvers=["py3"]).release.yml:build_pyodidejob is now a plainpython -m build --wheel, no Emscripten SDK, no cibuildwheel. Since the wheel has no ABI to be tag-rejected over,upload_pypi's wheel-exclusion step and the wholeupload_pyodide_assetjob (GitHub-Release-asset workaround) are gone — it uploads to PyPI like every other wheel.ci.yml:docs-buildnow builds this wheel fresh on every push (fast, no compilation) instead of fetching the latest GitHub Release's wasm asset — fixes a staleness gap as a side effect ("Try it Now" now tracksmain, not the last release).docs/source/intro.rst: updated the now-inaccurate "available as a GitHub release resource" line.spatialgeometry's own wheel is untouched here (still fetched cross-repo, still
cp312-pyodide_wasm32-tagged) — SG hasn't adopted the same fix yet (jhavl/spatialgeometry#46 tracks it as a natural follow-up once this is proven out here).Test plan
RTB_PURE_WHEEL=1: producesroboticstoolbox_python-1.4.0-py3-none-any.whl, "ninja: no work to do" confirms zero compiled targets._C_AVAILABLEisFalsefor bothfknm/frne;robot.fkine()androbot.jacob0()(pure-Python fallback paths) work correctly against a real Panda model;robot.ik_LM()correctly raises pointing atikine_LM;robot.ikine_LM()(pure-Python IK) succeeds.RTB_PURE_WHEELset is unaffected: still produces a compiledcp312-cp312-macosx_...wheel.docs-buildjob will build the wheel and run a realjupyter lite buildagainst it — watching for that to go green.