From 5f78fc48055733cfdb3915c431d5411c50275576 Mon Sep 17 00:00:00 2001 From: Tom Augspurger Date: Mon, 28 Sep 2026 10:19:39 -0500 Subject: [PATCH] Support Breathe 5 in the docs build (#1231) (#1236) Breathe 5.0.0 breaks the docs build on every constexpr constructor. It renders `constexpr` from the member's attribute and is meant to strip it from the type, but Doxygen 1.18 gives a constructor a type of just `constexpr`, which the stripping misses. The qualifier then appears twice, Sphinx's C++ parser rejects the declaration, and since warnings are errors the build fails. `clean_doxygen_xml` now clears a type that is only `constexpr` when the member also has the attribute. It does so only for Breathe 5 and later, because older Breathe reads `constexpr` from the type alone and would lose it. Verified by rendering `docs/source/cpp/*.md` against Doxygen 1.18.0 XML with Sphinx 8.2.3. With Breathe 5.0.0 the five parse errors are gone, with Breathe 4.36.0 and 4.35.0 the output is unchanged, and all three render each constexpr constructor with a single `constexpr`. cudf might need something similar. Its Doxygen 1.18 XML has the same pattern, and AFAICT `clean_definitions` in `docs/cudf/source/conf.py` doesn't handle it. Authors: - Mads R. B. Kristensen (https://github.com/madsbk) Approvers: - Lawrence Mitchell (https://github.com/wence-) URL: https://github.com/rapidsai/rapidsmpf/pull/1231 (cherry picked from commit 10282c8f163f02124dafd2d81f4d5b606fe5f297) Co-authored-by: Mads R. B. Kristensen --- docs/source/conf.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 5ff191fce..95a0b9fc7 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -19,6 +19,7 @@ from enum import IntEnum, IntFlag from typing import Any +import breathe from packaging.version import Version from sphinx.ext.autodoc import ClassDocumenter from sphinx.ext.intersphinx import ( @@ -74,6 +75,12 @@ def clean_doxygen_xml(path: str) -> None: "rapidsmpf::safe_cast": "To", } + # Breathe 5 renders `constexpr` from the member's attribute and strips it from the + # type, but misses a type that is only `constexpr`, which is what Doxygen 1.18 gives + # a constexpr constructor, so it renders it twice. Earlier Breathe renders it from + # the type alone. + strip_constexpr_type = Version(breathe.__version__) >= Version("5") + for filename in glob.glob(os.path.join(path, "*.xml")): tree = ET.parse(filename) changed = False @@ -86,6 +93,14 @@ def clean_doxygen_xml(path: str) -> None: changed = True continue + if ( + strip_constexpr_type + and type_text.strip() == "constexpr" + and member.get("constexpr") == "yes" + ): + type_node.clear() + changed = True + definition = member.find("definition") if type_text.startswith("requires ") and definition is not None: qualified_name = "".join(definition.itertext()).rsplit(" ", 1)[-1]