diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..1712953 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,68 @@ +name: docs + +# Build the main Clawpack documentation (doc/doc) and fail on any NEW +# reStructuredText / docstring warning relative to the committed baseline +# (doc/doc/tools/doc_warnings_baseline.txt). This only parses/renders the +# docs (the `dummy` builder writes no HTML) and does not deploy. +# +# NOTE on the baseline: the set of warnings depends on the build environment +# (which clawpack packages are importable, which optional deps are mocked). +# The committed baseline must therefore be regenerated in THIS environment, +# not on a developer's full source checkout. Run the workflow manually +# (workflow_dispatch) to produce an updated baseline artifact, then commit it. +# Until that is done, treat this check as informational in branch protection. + +on: + pull_request: + branches: [dev, v5.14.x] + push: + branches: [dev, v5.14.x] + workflow_dispatch: + inputs: + update_baseline: + description: 'Regenerate the warning baseline and upload it as an artifact' + type: boolean + default: false + +jobs: + checkwarnings: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # gfortran is needed to build the clawpack Fortran extensions on install. + - name: Install system build dependencies + run: sudo apt-get update && sudo apt-get install -y gfortran + + - name: Install the documentation toolchain + run: pip install -r doc/tools/requirements-docs.txt + + # autodoc imports the clawpack subpackages; petclaw/petsc4py is optional + # and mocked in conf.py, so it is intentionally NOT installed here. If a + # different subpackage fails to import, add it to autodoc_mock_imports in + # doc/conf.py rather than installing heavy/optional deps. + - name: Install clawpack (for autodoc imports) + run: pip install clawpack + + - name: Check for new documentation warnings + if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.update_baseline) }} + working-directory: doc + run: make checkwarnings + + # Manual path: regenerate the baseline in the CI environment and upload it + # so a maintainer can commit the environment-consistent version. + - name: Regenerate baseline + if: ${{ github.event_name == 'workflow_dispatch' && inputs.update_baseline }} + working-directory: doc + run: make checkwarnings-update + + - name: Upload regenerated baseline + if: ${{ github.event_name == 'workflow_dispatch' && inputs.update_baseline }} + uses: actions/upload-artifact@v4 + with: + name: doc_warnings_baseline + path: doc/tools/doc_warnings_baseline.txt diff --git a/doc/Makefile b/doc/Makefile index 34eef4c..809c048 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -19,7 +19,7 @@ else LAYOUT = _themes/flask_local/layout.html endif -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest checkwarnings checkwarnings-update checkwarnings-strict help: @echo "Please use \`make ' where is one of" @@ -39,6 +39,9 @@ help: @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " checkwarnings to fail on any NEW reST/docstring warning (vs the baseline)" + @echo " checkwarnings-update to regenerate the warning baseline (tools/doc_warnings_baseline.txt)" + @echo " checkwarnings-strict to fail on ANY reST/docstring warning, ignoring the baseline" clean: -rm -rf $(BUILDDIR)/* @@ -140,3 +143,12 @@ doctest: versions: sphinx-multiversion . _build/html +checkwarnings: + python tools/check_doc_warnings.py + +checkwarnings-update: + python tools/check_doc_warnings.py --update + +checkwarnings-strict: + python tools/check_doc_warnings.py --strict + diff --git a/doc/conf.py b/doc/conf.py index ba3a364..83f0094 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -16,6 +16,22 @@ import sys, os +# Some optional clawpack subpackages (petclaw, forestclaw) call +# logging.config.fileConfig() at import time with the default +# disable_existing_loggers=True. During the docs build autodoc/pycode import +# these modules *after* Sphinx has installed its warning logger, so that call +# would disable it and silently swallow every subsequent reST/docstring +# warning (they still get embedded in the HTML, but never reported). Force +# disable_existing_loggers=False for any fileConfig call so importing these +# packages can no longer muzzle Sphinx's warnings. +# (The underlying bug is those packages' __init__.py; see the docs CI notes.) +import logging.config as _logging_config +_orig_fileConfig = _logging_config.fileConfig +def _safe_fileConfig(*args, **kwargs): + kwargs['disable_existing_loggers'] = False + return _orig_fileConfig(*args, **kwargs) +_logging_config.fileConfig = _safe_fileConfig + # If your extensions are in another directory, add it here. If the directory # is relative to the documentation root, use os.path.abspath to make it # absolute, like shown here. @@ -47,6 +63,13 @@ 'srclinks'] +# autodoc imports the documented modules at build time. petclaw/petsc4py is +# optional, heavy, and currently untested in the pip-only doc-build environment +# (including CI), so mock it to keep autodoc imports from failing. Add further +# entries here if other optional/compiled modules fail to import. +autodoc_mock_imports = ['petsc4py', 'clawpack.petclaw'] + + mathjax_path = 'https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML' @@ -261,7 +284,11 @@ #latex_use_modindex = True -keep_warnings = True +# Do not embed docutils warnings as "System Message" nodes in the rendered +# HTML. Warnings are still written to stderr / the sphinx warning log, where +# they are caught by ``make checkwarnings`` (see tools/check_doc_warnings.py) +# and the docs CI workflow, instead of being silently baked into the pages. +keep_warnings = False inheritance_graph_attrs = dict(rankdir="TB", fontsize=12,splines='"true"',penwidth=100) diff --git a/doc/howto_doc.rst b/doc/howto_doc.rst index dbe6ba6..6bf9ff2 100644 --- a/doc/howto_doc.rst +++ b/doc/howto_doc.rst @@ -105,6 +105,68 @@ Note that we suggest using `_build1` when building a single version so this can be quickly rebuilt when writing and editing documentation. +.. _howto_doc_warnings: + +Checking for documentation warnings +----------------------------------- + +Sphinx does not fail the build on reStructuredText or docstring problems +(a missing blank line before a list, a bad cross reference, an autodoc import +issue, and so on). Historically these warnings were also *embedded* into the +rendered HTML as "System Message" boxes (via `keep_warnings = True` in +`conf.py`) while not being obvious on the command line, so they could slip +onto the website unnoticed. `keep_warnings` is now `False`, and the +following `make` targets let you catch warnings before they are merged. + +To fail on any warning that is **new** relative to a committed baseline +(`tools/doc_warnings_baseline.txt`):: + + cd $CLAW/doc/doc + make checkwarnings + +This does a full re-parse using the lightweight `dummy` builder (no HTML is +written) and compares the result against the baseline, which records the +warnings that already existed when the check was introduced. Only newly +introduced warnings cause a non-zero exit, so you can fix the backlog +gradually without the check going red on unrelated pages. + +If you intentionally add or remove warnings (e.g. after fixing a batch of +them), regenerate and commit the baseline:: + + make checkwarnings-update + +To ignore the baseline entirely and report **every** remaining warning -- +the goal once the backlog has been driven to zero -- use:: + + make checkwarnings-strict + +The same check runs in CI (`.github/workflows/docs.yml`) on pull requests to +`dev` and the current release branch. Because `autodoc` imports the clawpack +packages, CI installs them with `pip`; the optional parallel package +`petclaw` (and `petsc4py`) is not installed but is instead listed in +`autodoc_mock_imports` in `conf.py`. + +.. note:: + + The exact set of warnings depends on which packages are importable, so the + baseline is environment dependent. Regenerate it in the same environment + the CI workflow uses (see `tools/requirements-docs.txt`); the workflow can + be run manually to produce an updated baseline as an artifact. + +**Possible future enhancements:** + +- Extend the same warning check to the separate `gallery` Sphinx project + (`$CLAW/doc/gallery`), which first requires running the examples that + generate its figures. +- Turn off `keep_warnings` in `gallery/conf.py` and `doc/pyclaw/conf.py` + (used only for standalone pyclaw builds) for consistency. +- Once the baseline is empty, switch CI to `make checkwarnings-strict` and + optionally enable nitpicky (`-n`) cross-reference checking. +- Reconcile the build/deploy directory mismatch: `make html` writes to + `_build1/html`, while deployment (below) rsyncs from `_build/html`, the + `make versions` output. + + To generate docs including previous versions -------------------------------------------- diff --git a/doc/tools/check_doc_warnings.py b/doc/tools/check_doc_warnings.py new file mode 100644 index 0000000..76cdd8f --- /dev/null +++ b/doc/tools/check_doc_warnings.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python +# encoding: utf-8 +r""" +Catch reStructuredText / docstring warnings in the Clawpack documentation. + +Sphinx does not fail the build on docutils warnings (missing blank lines, +bad cross references, autodoc problems, ...). With ``keep_warnings = False`` +in ``conf.py`` these warnings are no longer embedded in the rendered HTML, so +this script provides a way to surface and gate on them. + +It performs a *forced full re-parse* of the main documentation (the ``dummy`` +builder, so no HTML is written) capturing every warning, normalises each one +into a stable, machine-independent signature, and compares the result against a +committed baseline: + + tools/doc_warnings_baseline.txt + +Exit status / modes +------------------- +default Fail (exit 1) if any warning appears that is NOT in the baseline. + Resolved baseline entries are reported but do not fail the run. +--update Rewrite the baseline from the current run instead of comparing. + Use this to seed the baseline, or to shrink it after fixing (or + intentionally adding) warnings, then commit the result. +--strict Ignore the baseline entirely and fail if there are ANY warnings. + This is the end goal once the baseline has been driven to empty. + +Because autodoc imports the clawpack packages, the set of warnings depends on +the build environment. Regenerate the baseline (``--update``) in the same +environment the CI workflow uses so the signatures agree. +""" + +from __future__ import annotations + +import argparse +import os +import re +import subprocess +import sys +import tempfile + + +# tools/ -> doc/doc (source dir) -> .../clawpack (the $CLAW root) +TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) +SRC_DIR = os.path.dirname(TOOLS_DIR) +CLAW_ROOT = os.path.abspath(os.path.join(SRC_DIR, os.pardir, os.pardir)) +BASELINE = os.path.join(TOOLS_DIR, 'doc_warnings_baseline.txt') + +# A sphinx warning line looks like one of: +# /abs/path/foo.rst:123: WARNING: message +# /abs/path/mod.py:docstring of pkg.mod.Cls:7: ERROR: message +# WARNING: message (no location) +_WARNING_RE = re.compile( + r'^(?P.*?)(?:: )?(?PWARNING|ERROR|SEVERE|CRITICAL): ' + r'(?P.*)$' +) + + +def _relativize(path: str) -> str: + """Return *path* relative to the $CLAW root when possible, else unchanged.""" + if not path: + return path + abspath = path if os.path.isabs(path) else os.path.join(SRC_DIR, path) + try: + rel = os.path.relpath(abspath, CLAW_ROOT) + except ValueError: # different drive on Windows + return path + # Only rewrite paths that actually live under the $CLAW root. + return rel if not rel.startswith(os.pardir) else path + + +def _normalize_location(loc: str) -> str: + """Drop absolute prefixes and volatile line numbers from a warning's location. + + ``/abs/mod.py:docstring of pkg.Cls:7`` -> ``geoclaw/.../mod.py:docstring of pkg.Cls`` + ``/abs/foo.rst:123`` -> ``doc/doc/foo.rst`` + """ + if not loc: + return '' + parts = loc.split(':') + filepart = _relativize(parts[0]) + # Keep descriptive middle components (e.g. "docstring of ..."), drop pure + # line numbers so unrelated edits that shift lines don't churn the baseline. + rest = [p for p in parts[1:] if not p.strip().isdigit()] + return ':'.join([filepart] + rest) + + +def _signature(match: 're.Match[str]') -> str: + loc = _normalize_location(match.group('loc').strip()) + level = match.group('level') + msg = ' '.join(match.group('msg').split()) + if loc: + return f'{loc}: {level}: {msg}' + return f'{level}: {msg}' + + +def collect_warnings() -> set[str]: + """Run a dummy sphinx build and return the set of normalized warning signatures.""" + tmp = tempfile.mkdtemp(prefix='doc_warncheck_') + warnfile = os.path.join(tmp, 'warnings.txt') + doctrees = os.path.join(tmp, 'doctrees') + outdir = os.path.join(tmp, 'out') + cmd = [ + sys.executable, '-m', 'sphinx', + '-b', 'dummy', # parse only; write no output + '-E', # ignore cached environment: re-read every source + '-q', # quiet: only warnings/errors on the console + '-w', warnfile, # also capture warnings to a file + '-d', doctrees, + '.', outdir, + ] + # Run from the source dir so conf.py's relative paths match `make html`. + proc = subprocess.run(cmd, cwd=SRC_DIR, capture_output=True, text=True) + + signatures: set[str] = set() + if os.path.exists(warnfile): + with open(warnfile, encoding='utf-8', errors='replace') as fh: + for line in fh: + line = line.rstrip('\n') + m = _WARNING_RE.match(line) + if m: + signatures.add(_signature(m)) + + # A crash (bad conf.py, import failure that aborts the build) exits non-zero + # and may leave no warning file: surface it rather than reporting "clean". + if proc.returncode != 0 and not signatures: + sys.stderr.write( + "sphinx-build failed before producing warnings " + f"(exit {proc.returncode}):\n" + ) + sys.stderr.write(proc.stdout) + sys.stderr.write(proc.stderr) + sys.exit(2) + + return signatures + + +def load_baseline() -> set[str]: + if not os.path.exists(BASELINE): + return set() + with open(BASELINE, encoding='utf-8') as fh: + return { + line.rstrip('\n') + for line in fh + if line.strip() and not line.startswith('#') + } + + +def write_baseline(signatures: set[str]) -> None: + header = ( + "# Baseline of pre-existing Clawpack documentation warnings.\n" + "# Generated by tools/check_doc_warnings.py --update.\n" + "# `make checkwarnings` fails only on warnings NOT listed here.\n" + "# Regenerate in the same environment the CI workflow uses.\n" + ) + with open(BASELINE, 'w', encoding='utf-8') as fh: + fh.write(header) + for sig in sorted(signatures): + fh.write(sig + '\n') + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--update', action='store_true', + help='rewrite the baseline from this run instead of comparing') + parser.add_argument('--strict', action='store_true', + help='ignore the baseline and fail on ANY warning') + args = parser.parse_args(argv) + + current = collect_warnings() + + if args.update: + write_baseline(current) + print(f"Wrote {len(current)} warning(s) to {os.path.relpath(BASELINE, CLAW_ROOT)}") + return 0 + + if args.strict: + if current: + print(f"{len(current)} documentation warning(s) (strict mode):\n") + for sig in sorted(current): + print(f" {sig}") + return 1 + print("No documentation warnings.") + return 0 + + baseline = load_baseline() + new = current - baseline + resolved = baseline - current + + if resolved: + print(f"{len(resolved)} baseline warning(s) no longer present " + "(consider `make checkwarnings-update`):\n") + for sig in sorted(resolved): + print(f" - {sig}") + print() + + if new: + print(f"{len(new)} NEW documentation warning(s):\n") + for sig in sorted(new): + print(f" {sig}") + print("\nFix these, or run `make checkwarnings-update` if intentional.") + return 1 + + print(f"No new documentation warnings ({len(current)} known, baselined).") + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/doc/tools/doc_warnings_baseline.txt b/doc/tools/doc_warnings_baseline.txt new file mode 100644 index 0000000..f28304b --- /dev/null +++ b/doc/tools/doc_warnings_baseline.txt @@ -0,0 +1,72 @@ +# Baseline of pre-existing Clawpack documentation warnings. +# Generated by tools/check_doc_warnings.py --update. +# `make checkwarnings` fails only on warnings NOT listed here. +# Regenerate in the same environment the CI workflow uses. +WARNING: A mocked object is detected: 'clawpack.petclaw.geometry.Domain' [autodoc.mocked_object] +doc/doc/ClawPlotData.rst: WARNING: undefined label: 'clawsolution' [ref.ref] +doc/doc/ClawPlotFigure.rst: WARNING: duplicate object description of gethandle, other instance in ClawPlotAxes, use :no-index: for one of them +doc/doc/ClawPlotItem.rst: WARNING: duplicate object description of getframe, other instance in ClawPlotData, use :no-index: for one of them +doc/doc/ClawPlotItem.rst: WARNING: duplicate object description of gethandle, other instance in ClawPlotFigure, use :no-index: for one of them +doc/doc/amr_algorithm.rst: WARNING: undefined label: 'cfl' [ref.ref] +doc/doc/amrclaw1d.rst: WARNING: undefined label: 'gallery_classic_amrclaw' [ref.ref] +doc/doc/bc.rst: WARNING: citation not found: BergerCalhounHelzelLeVeque [ref.ref] +doc/doc/bc.rst: WARNING: citation not found: CalhounHelzelLeVeque [ref.ref] +doc/doc/biblio.rst: WARNING: Citation [CalHelLeV08] is not referenced. [ref.citation] +doc/doc/biblio.rst: WARNING: Citation [LeVYon03] is not referenced. [ref.citation] +doc/doc/biblio.rst: WARNING: Citation [LeVeque09] is not referenced. [ref.citation] +doc/doc/biblio.rst: WARNING: Citation [LeVeque96] is not referenced. [ref.citation] +doc/doc/biblio.rst: WARNING: Citation [Mandli13a] is not referenced. [ref.citation] +doc/doc/biblio.rst: WARNING: Citation [Mandli13b] is not referenced. [ref.citation] +doc/doc/biblio.rst: WARNING: Citation [MandliEtAl2016] is not referenced. [ref.citation] +doc/doc/clawpack_components.rst: WARNING: undefined label: 'pyclaw/index' [ref.ref] +doc/doc/dclaw.rst: WARNING: citation not found: GeorgeIverson2014 [ref.ref] +doc/doc/dclaw.rst: WARNING: citation not found: IversonGeorge2014 [ref.ref] +doc/doc/developers.rst: WARNING: undefined label: 'contribution' [ref.ref] +doc/doc/developers.rst: WARNING: undefined label: 'git-resources' [ref.ref] +doc/doc/fgmax_tools_module.rst: WARNING: document isn't included in any toctree [toc.not_included] +doc/doc/fgout_tools_module.rst: WARNING: document isn't included in any toctree [toc.not_included] +doc/doc/first_run.rst: WARNING: undefined label: 'first_tests' [ref.ref] +doc/doc/first_run.rst: WARNING: undefined label: 'fortfiles' [ref.ref] +doc/doc/first_run.rst: WARNING: undefined label: 'install_prerequisites' [ref.ref] +doc/doc/first_run.rst: WARNING: undefined label: 'plotting_makeplots' [ref.ref] +doc/doc/first_run_fortran.rst: WARNING: undefined label: 'fortfiles' [ref.ref] +doc/doc/first_run_fortran.rst: WARNING: undefined label: 'install_fortran' [ref.ref] +doc/doc/first_run_fortran.rst: WARNING: undefined label: 'plotting_makeplots' [ref.ref] +doc/doc/first_run_pyclaw.rst: WARNING: undefined label: 'notebooks' [ref.ref] +doc/doc/geoclaw.rst: WARNING: undefined label: 'gallery_geoclaw' [ref.ref] +doc/doc/geoclaw1d.rst: WARNING: undefined label: 'topo1d' [ref.ref] +doc/doc/geohints.rst: WARNING: undefined label: 'sea_level' [ref.ref] +doc/doc/installing_pip.rst: WARNING: undefined label: 'installing_options' [ref.ref] +doc/doc/matlab_plotting.rst: ERROR: Unknown target name: "setplot". [docutils] +doc/doc/plotting_faq.rst: ERROR: Unknown target name: "iplotclaw". [docutils] +doc/doc/plotting_faq.rst: WARNING: undefined label: 'clawplotaxes`' [ref.ref] +doc/doc/plotting_faq.rst: WARNING: undefined label: 'clawplotfigure`' [ref.ref] +doc/doc/plotting_faq.rst: WARNING: undefined label: 'clawplotitem`' [ref.ref] +doc/doc/plotting_faq.rst: WARNING: undefined label: 'plotexample-acou-1d-6' [ref.ref] +doc/doc/plotting_python.rst: WARNING: undefined label: 'python-install' [ref.ref] +doc/doc/pyclaw/about.rst: WARNING: duplicate label about, other instance in /Users/mandli/src/clawpack/doc/doc/about.rst +doc/doc/pyclaw/about.rst: WARNING: undefined label: 'develop' [ref.ref] +doc/doc/pyclaw/cloud.rst: WARNING: undefined label: 'notebooks' [ref.ref] +doc/doc/pyclaw/index.rst: WARNING: undefined label: 'visclaw' [ref.ref] +doc/doc/pyclaw/parallel.rst: WARNING: undefined label: 'installation' [ref.ref] +doc/doc/pyclaw/problem.rst: ERROR: Unknown target name: "here https://github.com/damiansra/empyclaw/tree/master/maxwell_1d_homogeneous". [docutils] +doc/doc/pyclaw/rp.rst: WARNING: citation not found: LeVeque_book_2002 [ref.ref] +doc/doc/pyclaw/solvers_reference.rst: WARNING: document isn't included in any toctree [toc.not_included] +doc/doc/pyclaw/tutorial.rst: WARNING: undefined label: 'acoustics_1d' [ref.ref] +doc/doc/quick_tsunami.rst: WARNING: undefined label: 'notebooks' [ref.ref] +doc/doc/release_5_5_0.rst: WARNING: undefined label: 'topo_netcdf' [ref.ref] +doc/doc/ruled_rectangles.rst: WARNING: undefined label: 'refinement-regions' [ref.ref] +doc/doc/setplot.rst: WARNING: undefined label: 'plotfigure' [ref.ref] +doc/doc/setrun_geoclaw.rst: WARNING: undefined label: 'regions' [ref.ref] +doc/doc/setrun_geoclaw.rst: WARNING: undefined label: 'setrun_setgeo' [ref.ref] +doc/doc/topo.rst: WARNING: duplicate label qinit_file, other instance in /Users/mandli/src/clawpack/doc/doc/dtopo.rst +doc/doc/topo.rst: WARNING: undefined label: 'g_input' [ref.ref] +doc/doc/tsunamidata.rst: WARNING: undefined label: 'topo_netcdf' [ref.ref] +geoclaw/src/python/geoclaw/fgmax_tools.py:docstring of clawpack.geoclaw.fgmax_tools.FGmaxGrid.read_output: ERROR: Unexpected indentation. [docutils] +geoclaw/src/python/geoclaw/fgmax_tools.py:docstring of clawpack.geoclaw.fgmax_tools.FGmaxGrid.read_output: WARNING: Block quote ends without a blank line; unexpected unindent. [docutils] +geoclaw/src/python/geoclaw/netcdf_utils.py:docstring of clawpack.geoclaw.netcdf_utils.CFNormalizer: ERROR: Unexpected indentation. [docutils] +geoclaw/src/python/geoclaw/netcdf_utils.py:docstring of clawpack.geoclaw.netcdf_utils.CFNormalizer: WARNING: Block quote ends without a blank line; unexpected unindent. [docutils] +geoclaw/src/python/geoclaw/topotools.py:docstring of clawpack.geoclaw.topotools.Topography.write: WARNING: undefined label: 'topo_netcdf' [ref.ref] +geoclaw/src/python/geoclaw/topotools.py:docstring of clawpack.geoclaw.topotools.fetch_topo_url: ERROR: Unknown target name: "http://www.geoclaw.org/topo". [docutils] +geoclaw/src/python/geoclaw/util.py:docstring of clawpack.geoclaw.util.bearing: ERROR: Unexpected indentation. [docutils] +pyclaw/src/pyclaw/limiters/tvd.py:docstring of clawpack.pyclaw.limiters.tvd: WARNING: citation not found: kemm_2009 [ref.ref] diff --git a/doc/tools/requirements-docs.txt b/doc/tools/requirements-docs.txt new file mode 100644 index 0000000..dc50ca1 --- /dev/null +++ b/doc/tools/requirements-docs.txt @@ -0,0 +1,14 @@ +# Python toolchain for building the main Clawpack documentation +# (doc/doc). The clawpack packages themselves are installed separately +# (see .github/workflows/docs.yml); this file pins only the Sphinx tooling +# so that the warning baseline (tools/doc_warnings_baseline.txt) and CI agree. +# +# Regenerate the baseline in an environment built from this file: +# pip install -r tools/requirements-docs.txt +# make checkwarnings-update +# +# Lower bounds reflect versions known to build the docs; bump as needed. + +sphinx>=7.0 +sphinx-multiversion>=0.2.4 # only needed for `make versions` +docutils>=0.20