Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a48b2cd
ENH: build the dev MNE wheel for the JupyterLite browser kernel
natinew77-creator Aug 4, 2026
abd1bda
DOC: add the changelog entry for the JupyterLite wheel build
natinew77-creator Aug 4, 2026
8caccea
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 4, 2026
49d7057
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 5, 2026
4b598a5
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 6, 2026
15cd2c8
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 6, 2026
c355342
MAINT: only relax matplotlib for the browser wheel
natinew77-creator Aug 7, 2026
ce98cef
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 7, 2026
86e1e21
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 7, 2026
24efce3
MAINT: move the browser build to Pyodide 314
natinew77-creator Aug 8, 2026
fb9a0b0
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 8, 2026
bd1b66a
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 11, 2026
b0e07f4
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 13, 2026
8c37209
DOC: bring the Pyodide override comment up to date
natinew77-creator Aug 14, 2026
10ae765
DOC: correct the scipy version Pyodide 314 ships
natinew77-creator Aug 14, 2026
a5b688d
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 16, 2026
cf09b9e
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 17, 2026
35a269f
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 18, 2026
0db81ed
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 18, 2026
343580a
MAINT: address review on the JupyterLite wheel build
natinew77-creator Aug 19, 2026
075b1ee
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 19, 2026
2a1c7fb
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,12 @@ jobs:
cp junit-results.xml doc/_build/test-results/test-doc/junit.xml;
cp coverage.xml doc/_build/test-results/test-doc/coverage.xml;
fi;
# Build the development MNE wheel that the JupyterLite browser kernel
# will install, once, before Sphinx runs. Building it here rather than
# from conf.py keeps it out of the per-invocation docs build.
- run:
name: Build MNE wheel for JupyterLite
command: python doc/sphinxext/build_lite_wheel.py
# Build docs
- run:
name: make html
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ mne/viz/_brain/tests/.ipynb_checkpoints

dist/
doc/_build/
doc/pypi/
doc/generated/
doc/auto_examples/
doc/auto_tutorials/
Expand Down
1 change: 1 addition & 0 deletions doc/changes/dev/14135.other.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a build script and CI step that produce the development MNE wheel the JupyterLite browser kernel installs, by `Natneal B`_.
120 changes: 120 additions & 0 deletions doc/sphinxext/build_lite_wheel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""Build the development MNE wheel for the JupyterLite browser kernel.

Run this once before building the docs, either in CI or locally::

python doc/sphinxext/build_lite_wheel.py

The wheel is written to ``doc/pypi``, where the jupyterlite-pyodide-kernel
PipliteAddon discovers, copies and indexes it (adding it to ``pipliteUrls`` in
``jupyter-lite.json``), so the browser kernel installs the current development
MNE rather than the older release from PyPI. See
https://jupyterlite.readthedocs.io/en/latest/howto/pyodide/wheels.html

Both functions are importable, so a docs build can reuse a wheel that is already
present rather than building one on every invocation::

from build_lite_wheel import build_wheel, find_wheels

wheels = find_wheels() or build_wheel()
"""

# Authors: The MNE-Python contributors.
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.

import json
import os
import shutil
import subprocess
import sys
import urllib.request
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[2]
PYPI_WHEELS_DIR = REPO_ROOT / "doc" / "pypi"


def find_wheels():
"""Return the MNE wheels already present in ``doc/pypi``.

Returns
-------
wheels : list of pathlib.Path
Paths of the MNE wheels found, empty if there are none.
"""
return sorted(PYPI_WHEELS_DIR.glob("mne-*.whl"))


def _latest_pypi_version():
"""Return the newest MNE version on PyPI, or None if it cannot be reached.

Returns
-------
version : str | None
The version string, or None if PyPI could not be queried.
"""
# Broad on purpose: this only ever runs while raising, so a network problem
# here must not replace the real error with a less useful one.
try:
url = "https://pypi.org/pypi/mne/json"
with urllib.request.urlopen(url, timeout=10) as response:
return json.load(response)["info"]["version"]
except Exception:
return None


def build_wheel():
"""Build the development MNE wheel into ``doc/pypi``.

Returns
-------
wheels : list of pathlib.Path
Paths of the MNE wheels that were built.
"""
# The version below is pinned, so each build writes the same filename and
# wheels do not pile up. Clearing first is about determinism instead: this
# directory is the piplite index, so it should hold the wheel this build
# produced and nothing else, including anything left by a manual pip wheel.
shutil.rmtree(PYPI_WHEELS_DIR, ignore_errors=True)
PYPI_WHEELS_DIR.mkdir(parents=True, exist_ok=True)

# The wheel is built from pyproject.toml as it stands: Pyodide 314 ships
# matplotlib 3.10.8, scipy 1.18.0 and numpy 2.4.3, all of which satisfy the
# minimums MNE declares, so none of them needs relaxing for the browser.
os.environ["SETUPTOOLS_SCM_PRETEND_VERSION"] = "9999.0.1"
# NB: build isolation is left ON (the default). MNE uses the hatchling build
# backend, so pip must create an isolated build env to install
# hatchling/hatch-vcs; --no-build-isolation fails with "Cannot import
# 'hatchling.build'" on CI, where those build deps are not in the base
# environment.
subprocess.run(
[
sys.executable,
"-m",
"pip",
"wheel",
REPO_ROOT,
"--no-deps",
"-w",
PYPI_WHEELS_DIR,
],
check=True,
)

# Fail loudly rather than silently letting the browser kernel fall back to
# the released MNE from PyPI.
wheels = find_wheels()
if not wheels:
Comment thread
natinew77-creator marked this conversation as resolved.
latest = _latest_pypi_version()
fallback = f"MNE {latest}" if latest else "the latest MNE release"
raise RuntimeError(
f"JupyterLite: no MNE wheel was built into {PYPI_WHEELS_DIR}; the "
f"browser kernel would fall back to {fallback} from PyPI. Check the "
"'pip wheel' output above."
)
return wheels


if __name__ == "__main__":
built = ", ".join(str(wheel) for wheel in build_wheel())
print(f"[JupyterLite] Built MNE wheel(s) for the browser kernel: {built}")
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ doc = [
"graphviz",
"intersphinx_registry >= 0.2405.27",
"ipython != 8.7.0", # also in "full-no-qt" and "test"
"jupyterlite-pyodide-kernel",
"jupyterlite-sphinx",
"memory_profiler >= 0.16",
"mne-bids",
"mne-connectivity",
Expand Down
14 changes: 14 additions & 0 deletions tools/circleci_uv_overrides.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,17 @@
# so uv does not drop those dependencies (the override takes precedence over the
# command line, including its extras).
-e .[full-pyside6]

# Floor for the browser kernel, so it stays on the Pyodide 314 line. Up to
# jupyterlite-sphinx 0.22.1 the jupyterlite-core cap was < 0.8, which held the
# kernel at Pyodide 0.29.3 and its matplotlib 3.8.4, one minor below the 3.9
# MNE declares. 0.23.0 raised that cap to < 0.9, so the resolution now reaches
# Pyodide 314 unaided and these two lines change nothing today. They stay as a
# guard: anything that pulls jupyterlite-sphinx back below 0.23 would otherwise
# drop the browser to the old Pyodide and quietly stop meeting MNE's matplotlib
# bound. Pyodide 314 ships matplotlib 3.10.8, scipy 1.18.0 and numpy 2.4.3, all
# of which satisfy MNE, so the wheel build needs no version patching at all.
# The core line is the one that does the work; the kernel line pins the intent
# in case something else ever constrains the kernel directly.
jupyterlite-core>=0.8.1
jupyterlite-pyodide-kernel>=0.8
Loading