Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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`_.
97 changes: 97 additions & 0 deletions doc/sphinxext/build_lite_wheel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""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 glob
import os
import shutil
import subprocess
import sys

REPO_ROOT = os.path.abspath(
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
)
PYPI_WHEELS_DIR = os.path.join(REPO_ROOT, "doc", "pypi")


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

Returns
-------
wheels : list of str
Paths of the MNE wheels found, empty if there are none.
"""
return glob.glob(os.path.join(PYPI_WHEELS_DIR, "mne-*.whl"))


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

Returns
-------
wheels : list of str
Paths of the MNE wheels that were built.
"""
# Clean first so stale wheels from previous runs do not accumulate and
# pollute the piplite all.json index.
shutil.rmtree(PYPI_WHEELS_DIR, ignore_errors=True)
os.makedirs(PYPI_WHEELS_DIR, exist_ok=True)

# The wheel is built from pyproject.toml as it stands: Pyodide 314 ships
# matplotlib 3.10.8, scipy 1.17.1 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 older released MNE from PyPI.
wheels = find_wheels()
if not wheels:
raise RuntimeError(
f"JupyterLite: no MNE wheel was built into {PYPI_WHEELS_DIR!r}; the "
"browser kernel would fall back to the released PyPI version. Check "
"the 'pip wheel' output above."
)
return wheels


if __name__ == "__main__":
print(f"[JupyterLite] Built MNE wheel(s) for the browser kernel: {build_wheel()}")
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
11 changes: 11 additions & 0 deletions tools/circleci_uv_overrides.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,14 @@
# so uv does not drop those dependencies (the override takes precedence over the
# command line, including its extras).
-e .[full-pyside6]

# jupyterlite-sphinx 0.22.1, its newest release, caps jupyterlite-core at < 0.8,
# which would hold the browser kernel at Pyodide 0.29.3 and its matplotlib 3.8.4,
# one minor below the 3.9 MNE declares. The cap is declared rather than real:
# 0.22.1 imports and builds fine against core 0.8.1. Overriding it puts the
# browser on Pyodide 314, whose matplotlib 3.10.8, scipy 1.17.1 and numpy 2.4.3
# all satisfy MNE, so the wheel build needs no version patching at all. Both
# lines are needed: overriding core alone also lifts the old kernel's own cap,
# which would leave the old kernel and its old Pyodide in place.
jupyterlite-core>=0.8.1
jupyterlite-pyodide-kernel>=0.8
Loading