diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f917df2aab..15a8f210f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -730,7 +730,7 @@ jobs: path: warp/bin/ - name: Run doctest - run: uv run --extra docs build_docs.py --doctest --no-html + run: uv run --extra docs build_docs.py --doctest --no-html --warnings-as-errors build-docs: if: github.event_name != 'schedule' || github.repository == 'NVIDIA/warp' @@ -758,7 +758,7 @@ jobs: path: warp/bin/ - name: Build Sphinx documentation run: | - uv run --extra docs build_docs.py + uv run --extra docs build_docs.py --warnings-as-errors - name: Upload artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 id: build-docs-output diff --git a/.github/workflows/sphinx.yml b/.github/workflows/sphinx.yml index f5ceeccf1c..ef2a04abb6 100644 --- a/.github/workflows/sphinx.yml +++ b/.github/workflows/sphinx.yml @@ -100,7 +100,7 @@ jobs: - name: Build Sphinx documentation if: steps.dv.outputs.metadata_only != 'true' run: | - DOC_VERSION=${{ steps.dv.outputs.version }} uv run --extra docs build_docs.py + DOC_VERSION=${{ steps.dv.outputs.version }} uv run --extra docs build_docs.py --warnings-as-errors - name: Upload artifacts if: steps.dv.outputs.metadata_only != 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 53894cd8e5..55e1554387 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -219,7 +219,7 @@ doctest: - df -h - !reference [.snippets, move-linux-x86_64-binaries] script: - - uv run --extra docs build_docs.py --doctest --no-html + - uv run --extra docs build_docs.py --doctest --no-html --warnings-as-errors # The only purpose of this job is to make sure documentation can be built on Windows. # The output does not get published anywhere, but the website can be viewed in the @@ -242,7 +242,7 @@ windows-x86_64 docs: script: - | $env:PATH = "$env:CI_PROJECT_DIR\_uv;$env:PATH" - uv run --extra docs build_docs.py + uv run --extra docs build_docs.py --warnings-as-errors - mv docs/_build/html/ ./public/ after_script: - echo "View the website at https://$CI_PROJECT_ROOT_NAMESPACE.$CI_PAGES_DOMAIN/-/$CI_PROJECT_NAME/-/jobs/$CI_JOB_ID/artifacts/public/index.html" @@ -1045,7 +1045,7 @@ publish wheels to github release: - !reference [.snippets, move-linux-x86_64-binaries] - !reference [.snippets, section-end-install-deps] script: - - uv run --extra docs build_docs.py + - uv run --extra docs build_docs.py --warnings-as-errors - mv docs/_build/html/ ./public/ # Merge requests: Build documentation and save as an artifact diff --git a/CHANGELOG.md b/CHANGELOG.md index 8330375ff0..e0103eb0cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -99,6 +99,11 @@ ### Documentation +- Make `build_docs.py` lenient by default: warnings are no longer treated as errors unless the new + `--warnings-as-errors` flag is passed (CI passes it to keep strict checks). Local documentation builds + also skip external intersphinx resolution when the inventories are unreachable (or when + `WARP_DOCS_OFFLINE=1` is set), so an offline build no longer aborts with no output. + ## [1.14.0] - 2026-06-01 ### Added diff --git a/build_docs.py b/build_docs.py index 8238c81775..be8fc8cdf7 100644 --- a/build_docs.py +++ b/build_docs.py @@ -25,6 +25,16 @@ default=False, help="Run doctest tests of code blocks", ) +parser.add_argument( + "--warnings-as-errors", + action=argparse.BooleanOptionalAction, + default=False, + help=( + "Treat Sphinx warnings as errors (passes -W). Off by default so local " + "builds stay lenient (e.g. unreachable intersphinx inventories when " + "offline do not abort the build). CI/CD opts in to enforce strictness." + ), +) parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose logging") args = parser.parse_args() @@ -81,7 +91,7 @@ def format_file_with_ruff(file_path): ) from err -def build_sphinx_docs(source_dir, output_dir, builder="html"): +def build_sphinx_docs(source_dir, output_dir, builder="html", warnings_as_errors=False): """Build Sphinx documentation programmatically.""" logger.info(f"Building {builder} documentation: {source_dir} -> {output_dir}") try: @@ -92,9 +102,24 @@ def build_sphinx_docs(source_dir, output_dir, builder="html"): logger.debug(f"Cleaning previous output directory: {output_dir}") shutil.rmtree(output_dir) - # sphinx-build -W -b html source_dir output_dir - logger.debug(f"Running sphinx-build -W -j auto -b {builder} {source_dir} {output_dir}") - result = build_main(["-W", "-j", "auto", "-b", builder, source_dir, output_dir]) + # sphinx-build [-W] -j auto -b source_dir output_dir + sphinx_args = ["-j", "auto", "-b", builder, source_dir, output_dir] + if warnings_as_errors: + sphinx_args.insert(0, "-W") + logger.debug(f"Running sphinx-build {' '.join(sphinx_args)}") + previous_strict_env = os.environ.get("WARP_DOCS_WARNINGS_AS_ERRORS") + try: + if warnings_as_errors: + os.environ["WARP_DOCS_WARNINGS_AS_ERRORS"] = "1" + else: + os.environ.pop("WARP_DOCS_WARNINGS_AS_ERRORS", None) + + result = build_main(sphinx_args) + finally: + if previous_strict_env is None: + os.environ.pop("WARP_DOCS_WARNINGS_AS_ERRORS", None) + else: + os.environ["WARP_DOCS_WARNINGS_AS_ERRORS"] = previous_strict_env if result != 0: raise RuntimeError(f"Sphinx build failed with exit code {result}") @@ -124,12 +149,12 @@ def build_sphinx_docs(source_dir, output_dir, builder="html"): if args.html: # Build HTML docs html_output_dir = os.path.join(base_path, "docs", "_build", "html") - build_sphinx_docs(source_dir, html_output_dir, "html") + build_sphinx_docs(source_dir, html_output_dir, "html", warnings_as_errors=args.warnings_as_errors) if args.doctest: # Run doctest logger.info("Running doctest...") doctest_output_dir = os.path.join(base_path, "docs", "_build", "doctest") - build_sphinx_docs(source_dir, doctest_output_dir, "doctest") + build_sphinx_docs(source_dir, doctest_output_dir, "doctest", warnings_as_errors=args.warnings_as_errors) logger.info("Documentation build completed successfully") diff --git a/docs/conf.py b/docs/conf.py index 3f015f8414..ae81baa148 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,6 +17,7 @@ import docutils import sphinx +import sphinx.util.logging from sphinx import addnodes from sphinx.environment.adapters.toctree import note_toctree from sphinx.ext.autosummary import autosummary_toc @@ -396,8 +397,21 @@ def render(self, template_name, context): # -- sphinx.ext.intersphinx -------------------------------------------------- -# Mapping to external documentation to enable cross-linking (e.g., :class:`numpy.ndarray`) -intersphinx_mapping = { +# Resolving intersphinx links requires fetching each project's remote inventory +# (objects.inv) over the network. That matters for CI/CD, where we want external +# cross-references (e.g. :class:`numpy.ndarray`) to resolve and stale inventory +# URLs to fail loudly, but a local build should not hard-fail just because the +# network is unavailable (offline machine, agent sandbox with no egress, flaky +# DNS). Under -W an unreachable inventory is promoted to a fatal error and +# aborts the whole build, producing no output. +# +# To keep local builds robust, skip intersphinx entirely when the inventories +# are unreachable (or when WARP_DOCS_OFFLINE=1 is set to force it off). With the +# mapping empty, external references are left unresolved: with nitpicky mode this +# emits (non-fatal) warnings rather than aborting, so the build still succeeds. +# CI runs with network access (and --warnings-as-errors) still asks Sphinx to +# fetch inventories, so moved or invalid inventory URLs are caught there. +_intersphinx_mapping = { "jax": ("https://docs.jax.dev/en/latest", None), "numpy": ("https://numpy.org/doc/stable", None), "python": ("https://docs.python.org/3", None), @@ -405,6 +419,48 @@ def render(self, template_name, context): } +def _inventories_reachable(mapping, timeout=2): + """Check whether configured intersphinx inventories can be fetched. + + Args: + mapping: Intersphinx mapping dictionary. + timeout: Per-inventory probe timeout in seconds. + + Returns: + ``True`` if every configured inventory returns a successful HTTP + response. ``False`` if any inventory cannot be fetched, including + connection-level failures and HTTP errors such as stale ``404`` URLs. + """ + import urllib.error # noqa: PLC0415 + import urllib.request # noqa: PLC0415 + + for base, _ in mapping.values(): + url = base.rstrip("/") + "/objects.inv" + try: + with urllib.request.urlopen(url, timeout=timeout): + continue + except (urllib.error.HTTPError, urllib.error.URLError, OSError): + return False + return bool(mapping) + + +_sphinx_logger = sphinx.util.logging.getLogger(__name__) +if os.environ.get("WARP_DOCS_OFFLINE") == "1": + _sphinx_logger.info("intersphinx: WARP_DOCS_OFFLINE=1 set; skipping external cross-reference resolution.") + intersphinx_mapping = {} +elif os.environ.get("WARP_DOCS_WARNINGS_AS_ERRORS") == "1": + intersphinx_mapping = _intersphinx_mapping +elif not _inventories_reachable(_intersphinx_mapping): + _sphinx_logger.info( + "intersphinx: external inventories unreachable; skipping external cross-reference resolution " + "for this build. Set WARP_DOCS_OFFLINE=1 to silence this probe, or build with network access " + "to resolve external links." + ) + intersphinx_mapping = {} +else: + intersphinx_mapping = _intersphinx_mapping + + # -- sphinx.ext.linkcode ----------------------------------------------------- diff --git a/docs/user_guide/contribution_guide.rst b/docs/user_guide/contribution_guide.rst index ea36ddd6ef..acc1d8322a 100644 --- a/docs/user_guide/contribution_guide.rst +++ b/docs/user_guide/contribution_guide.rst @@ -333,6 +333,19 @@ The ``--no-html`` flag can also be used to skip building the HTML documentation, # Build the HTML documentation AND run the doctest tests uv run --extra docs build_docs.py --doctest +By default, warnings are not treated as errors, so a local build still succeeds (and produces +output) even when, for example, external `intersphinx `__ +inventories cannot be reached without network access. CI builds run with the ``--warnings-as-errors`` +flag to enforce strictness, so pass it locally to reproduce the CI build and catch warnings before +opening a pull request: + +.. code-block:: bash + + uv run --extra docs build_docs.py --warnings-as-errors + +If you are building offline and want to skip the external cross-reference resolution entirely +(rather than relying on the automatic reachability probe), set ``WARP_DOCS_OFFLINE=1``. + Running ``build_docs.py`` also regenerates both the stub file (``warp/__init__.pyi``) and the reStructuredText files for the reference pages. After building the documentation, it is recommended to run a ``git status`` to check if your changes have modified these files. If so, please commit the modified files to your branch.