| name | build-docs |
| description | Build the NEML2 Sphinx documentation locally. Use when the user asks to build, regenerate, preview, or serve the docs (e.g., "build the docs", "preview the documentation", "the doc build is failing", "run sphinx-build", "run sphinx-autobuild"). Walks through the `pip install` → `sphinx-build` (or `sphinx-autobuild` for live preview) pipeline using the shibuya theme + MyST-NB markdown/notebook backend, the syntax-catalog extension, and the front-matter recipe for tutorial pages that embed executable `{code-cell}` blocks. |
build-docs
End-to-end documentation build for NEML2. The doc pipeline is a single
sphinx-build invocation against doc/, driven by doc/conf.py.
Hard requirements before running
- The
neml2 Python package must be installed in the active env.
sphinx.ext.autodoc and the neml2_syntax extension (which runs
neml2-syntax --json at build start) both import neml2 and shell
out to its CLI. Editable installs work as long as the compiled
extension has been built.
- Sphinx + the docs extras must be installed:
pip install ".[dev]" -v → pulls in sphinx, shibuya,
myst-parser[linkify], myst-nb, sphinx-autobuild,
sphinx-copybutton.
Standard workflow
The wrapper script doc/scripts/build.sh captures the canonical
invocation -- prefer it over typing sphinx-build directly:
pip install ".[dev]" -v
doc/scripts/build.sh
xdg-open doc/_build/html/index.html
The wrapper runs sphinx-build -j auto -W --keep-going -b html doc doc/_build/html and pre-creates doc/_build/html/.jupyter_cache so
the parallel myst-nb workers don't race the directory mkdir
(otherwise one worker wins and the rest crash with FileExistsError).
-j auto roughly halves the cold-build wall-clock; the
neml2_syntax extension's neml2-syntax --json invocation runs once
at build start and is unaffected.
Common flags:
| Flag | Effect |
|---|
--clean | rm -rf the destination + jupyter cache before building. Use after a toctree change or when in doubt about stale state. |
--serve | After building, start python -m http.server on the destination bound to 127.0.0.1 (default port 8765). Foreground; Ctrl-C stops. Prints the SSH-tunnel command. |
--dest PATH | Output directory (default doc/_build/html). |
--port N | Override the serve port. |
-j, --jobs N | Override sphinx-build -j (default auto). |
--no-strict | Drop -W (treat warnings as errors). Useful when iterating on a known-warning page. |
-h, --help | Print usage. |
Add -E (rebuild environment) manually if some pages still show
stale sidebar groups after a structural change — sphinx's incremental
builder doesn't always invalidate every dependent page when the global
toc changes. The wrapper doesn't expose -E directly; just use
--clean for the same effect.
Live-preview mode
sphinx-autobuild doc doc/_build/html
Serves at http://127.0.0.1:8000/ and rebuilds + reloads on every
change under doc/. Tab keeps its scroll position across rebuilds.
doc/scripts/build.sh --serve is the one-shot equivalent (no
autoreload) suited for tunnelled remote review.
Layout
doc/
conf.py sphinx configuration (theme, extensions, JS wiring)
index.md top-level toctree
content/ narrative markdown (installation, tutorials, modules, system, …)
_ext/ local sphinx extensions
neml2_syntax.py autogenerates doc/generated/syntax/** from `neml2-syntax --json`
generated/ autogenerated output tree (gitignored)
syntax/ per-type, per-section, top-level index pages
asset/ logos, svgs (served as /_static/asset/...)
js/ custom JS components (served as /_static/components/...)
notebooks symlink to ../notebooks/ so MyST-NB sees the .ipynb files
_build/ output (gitignored)
The four custom JS files under doc/js/components/ are loaded on every
page via html_js_files in conf.py. They are inert until a <div>
with the matching class exists in the rendered HTML, so the cost of
loading them globally is zero on pages that don't use them.
Tutorial pages with executable {code-cell} blocks
myst-nb lets a hand-authored .md file behave like a notebook — code
cells run at build time, their outputs (stdout, return values, plots)
get captured inline. The page must opt in with two front-matter
blocks; missing either silently drops the cells from the output.
---
jupytext:
text_representation:
extension: .md
format_name: myst
format_version: 0.13
kernelspec:
display_name: Python 3
language: python
name: python3
mystnb:
execution_mode: cache
---
Then {code-cell} ipython3 directives are executed:
```{code-cell} ipython3
import neml2
model = neml2.load_model("input.i", "elasticity")
model
```
- The
jupytext.text_representation block is mandatory. Without
it, myst-nb logs Found an unexpected code-cell or raw-cell directive. Either this file was not converted to a notebook, because Jupytext header content was missing, … and the cells vanish from the
output. kernelspec alone is not enough.
- The cell language must be
ipython3 (not python). The other form
parses but myst-nb won't execute it.
execution_mode: cache reuses cached cell outputs when the source
hasn't changed (cache lives under _build/.jupyter_cache/), so
editing prose around a stable cell doesn't re-execute it. Use
execution_mode: force if you want a guaranteed re-run.
- The working directory for executed cells is the source file's
directory by default, so
load_model("input.i", …) finds an input.i
sibling without any path gymnastics.
- The global default in
conf.py is nb_execution_mode = "off" — only
pages with the per-file opt-in execute. This keeps the pre-baked
.ipynb notebooks under notebooks/ from re-executing on every
build.
Including verbatim files in narrative pages
For input files (.i), Python snippets, etc., use the built-in
{literalinclude} directive (paths resolve relative to the source
file):
```{literalinclude} input.i
:language: ini
:caption: input.i
```
Use ini (not python) for HIT input files — python lexer chokes
on !include directives and ${var} substitutions and emits Pygments
warnings.
Common failure modes
| Symptom | Likely cause | Fix |
|---|
ImportError running sphinx-build (autodoc cannot import neml2) | Package not installed, or installed in a different env | pip install ".[dev]" -v |
Could not import extension shibuya / myst_nb / … | Dev extras stale | pip install -U ".[dev]" |
mystnb.nbcell warning, {code-cell} blocks missing from rendered output | Front-matter jupytext.text_representation block missing or incomplete | Add the full front-matter shown above |
{code-cell} block renders but isn't executed | Cell language is python instead of ipython3, OR page lacks execution_mode opt-in in front-matter | Switch to ipython3 and add mystnb.execution_mode: cache |
| Sidebar still shows stale toctree on some pages after a structural change | Sphinx incremental cache stale | rm -rf doc/_build && sphinx-build … (or just pass -E) |
WARNING: more than one target found for 'myst' cross-reference X | A (label)= anchor and a doc filename both resolve to the same display text | Disambiguate with explicit doc form [text](path.md) |
Pygments warning: Lexing literal_block … as "python" resulted in an error | Code block contains shell/HIT/etc. syntax | Switch the fence language: ini for HIT, text for opaque output, bash for shell |
404 on /_static/components/<name>.js | Custom JS deleted/renamed without updating html_js_files in conf.py | Add/remove the path in conf.py:html_js_files |
| Theme looks plain (default alabaster) | shibuya not installed in the active env | pip install shibuya (already in [dev] extras) |
Race: foreground sphinx-build errors mid-syntax-catalog with No such file or directory: …generated/syntax/… | Autobuild server is regenerating the same files concurrently | Stop autobuild (TaskStop if running in background) before doing manual builds |
Reference
Doc-related conventions are documented inline in doc/conf.py. The
syntax-catalog generator lives in doc/_ext/neml2_syntax.py. The
per-tutorial pattern (main.md next to input.i + supporting files)
is established under doc/content/tutorials/models/.