Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 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
b943394
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 22, 2026
cadbbba
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 23, 2026
386c326
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 23, 2026
0af87a0
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 24, 2026
3fb9e80
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 24, 2026
1cd0d20
MAINT: address review on the JupyterLite wheel build
natinew77-creator Aug 24, 2026
8926cd4
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 24, 2026
9aca5cc
MAINT: build the JupyterLite wheel from a make target
natinew77-creator Aug 25, 2026
718cf49
Merge branch 'main' into lite-wheel-build
natinew77-creator Aug 25, 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
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
17 changes: 10 additions & 7 deletions doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ MPROF = SG_STAMP_STARTS=true mprof run -E --python sphinx
# Internal variables.
ALLSPHINXOPTS = -d _build/doctrees $(SPHINXOPTS) .

.PHONY: help clean html html-noplot html-pattern linkcheck linkcheck-grep doctest
.PHONY: help clean html html-noplot html-pattern jupyterlite_wheel linkcheck linkcheck-grep doctest

# make with no arguments will build the first target by default, i.e., build standalone HTML files
first_target: html-noplot
Expand All @@ -28,29 +28,32 @@ help:
@echo " view to view the built HTML"

clean:
-rm -rf _build sg_execution_times.rst auto_examples auto_tutorials generated *.stc *.fif *.nii.gz
-rm -rf _build pypi sg_execution_times.rst auto_examples auto_tutorials generated *.stc *.fif *.nii.gz

html:
jupyterlite_wheel:
@python sphinxext/build_lite_wheel.py

html: jupyterlite_wheel
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html
@echo
@echo "Build finished. The HTML pages are in _build/html."

html-memory:
html-memory: jupyterlite_wheel
$(MPROF) -b html $(ALLSPHINXOPTS) _build/html
@echo
@echo "Build finished. The HTML pages are in _build/html."

html-pattern:
html-pattern: jupyterlite_wheel
$(SPHINXBUILD) -D sphinx_gallery_conf.filename_pattern=$(PATTERN) -D sphinx_gallery_conf.run_stale_examples=True -b html $(ALLSPHINXOPTS) _build/html
@echo
@echo "Build finished. The HTML pages are in _build/html"

html-pattern-memory:
html-pattern-memory: jupyterlite_wheel
$(MPROF) -D sphinx_gallery_conf.filename_pattern=$(PATTERN) -D sphinx_gallery_conf.run_stale_examples=True -b html $(ALLSPHINXOPTS) _build/html
@echo
@echo "Build finished. The HTML pages are in _build/html"

html-noplot:
html-noplot: jupyterlite_wheel
$(SPHINXBUILD) -D plot_gallery=0 -b html $(ALLSPHINXOPTS) _build/html
@echo
@echo "Build finished. The HTML pages are in _build/html."
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 ``make`` target that produce the MNE wheel the JupyterLite browser kernel installs, by `Natneal B`_.
126 changes: 126 additions & 0 deletions doc/sphinxext/build_lite_wheel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
"""Build the 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 MNE the surrounding
pages are built from rather than the last release on PyPI. That is the
development version on ``main`` and that release's code on a ``maint/*``
branch, since the docs build from whichever branch it is running on. 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 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 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 MNE wheel for the browser kernel into ``doc/pypi``.

Returns
-------
wheels : list of Path
Paths of the MNE wheels that were built.
"""
# This directory is the piplite index, so it should hold the wheel this
# build produced and nothing else, including anything left behind by an
# earlier build or a manual pip wheel. The version is left to hatch-vcs:
# piplite serves this index exclusively rather than merging it with PyPI
# (_query_package returns as soon as the package is found here), so a
# development version has nothing to lose a resolution against.
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.
# 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__":
# Reuse a wheel that is already there, so repeat `make html` runs do not
# rebuild it. Remove doc/pypi (or `make clean`) to force a fresh one.
existing = find_wheels()
wheels = ", ".join(str(wheel) for wheel in (existing or build_wheel()))
verb = "Reusing" if existing else "Built"
print(f"[JupyterLite] {verb} MNE wheel(s) for the browser kernel: {wheels}")
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ doc = [
"graphviz",
"intersphinx_registry >= 0.2405.27",
"ipython != 8.7.0", # also in "full-no-qt" and "test"
# 0.8 is the Pyodide 314 line, whose matplotlib, scipy and numpy all satisfy
# MNE, and 0.23 raised jupyterlite-sphinx's jupyterlite-core cap to < 0.9 so
# the two resolve together. Below either, the browser drops to Pyodide 0.29
# and its matplotlib 3.8.4, a minor under the 3.9 MNE declares.
"jupyterlite-pyodide-kernel >= 0.8",
"jupyterlite-sphinx >= 0.23",
"memory_profiler >= 0.16",
"mne-bids",
"mne-connectivity",
Expand Down
Loading