From 811cad3be81bb068e5bdf10a6d7d7254290d21ee Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 2 Aug 2026 21:15:24 +0200 Subject: [PATCH 1/2] Fix docs changes builder for our custom directives --- Doc/tools/extensions/changes.py | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Doc/tools/extensions/changes.py b/Doc/tools/extensions/changes.py index 02dc51b3a76943a..e6a912cef8810ea 100644 --- a/Doc/tools/extensions/changes.py +++ b/Doc/tools/extensions/changes.py @@ -6,6 +6,7 @@ from docutils import nodes from sphinx import addnodes +from sphinx.builders.changes import ChangesBuilder from sphinx.domains.changeset import ( VersionChange, versionlabel_classes, @@ -17,6 +18,7 @@ if TYPE_CHECKING: from docutils.nodes import Node from sphinx.application import Sphinx + from sphinx.environment import BuildEnvironment from sphinx.util.typing import ExtensionMetadata @@ -146,6 +148,32 @@ def _add_glossary_link(cls, inline: nodes.inline) -> None: break +def _fixup_changesets(app: Sphinx, env: BuildEnvironment) -> None: + changesets = env.get_domain("changeset").changesets + + # The changeset domain records each entry's plain text before SoftDeprecated + # replaces the :term:, so strip the markup before the changes builder renders it. + for entries in changesets.values(): + for i, entry in enumerate(entries): + if entry.type == "soft-deprecated": + entries[i] = entry._replace( + content=SoftDeprecated._TERM_RE.sub(r"\1", entry.content) + ) + + # DeprecatedRemoved entries are recorded under their (deprecated, + # removed) version tuple, which the changes builder ignores. + # Re-file them under both versions. + for versions in [v for v in changesets if isinstance(v, tuple)]: + deprecated, removed = versions + for entry in changesets.pop(versions): + changesets.setdefault(deprecated, []).append( + entry._replace(type="deprecated") + ) + changesets.setdefault(removed, []).append( + entry._replace(type="versionremoved") + ) + + def setup(app: Sphinx) -> ExtensionMetadata: # Override Sphinx's directives with support for 'next' app.add_directive("versionadded", PyVersionChange, override=True) @@ -155,9 +183,15 @@ def setup(app: Sphinx) -> ExtensionMetadata: # Register the ``.. deprecated-removed::`` directive app.add_directive("deprecated-removed", DeprecatedRemoved) + # _fixup_changesets() changes these entries to 'deprecated'/'versionremoved' + ChangesBuilder.typemap["deprecated-removed"] = "deprecated-removed" # Register the ``.. soft-deprecated::`` directive app.add_directive("soft-deprecated", SoftDeprecated) + ChangesBuilder.typemap["soft-deprecated"] = "soft deprecated" + + # Repair the recorded changesets for the couple of custom directives above + app.connect("env-updated", _fixup_changesets) return { "version": "1.0", From da46dc7266ca965ea2b2b0281821f34dd27ee8e8 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Fri, 11 Sep 2026 15:54:23 +0100 Subject: [PATCH 2/2] Add a `make changes` to the CI --- .github/workflows/reusable-docs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/reusable-docs.yml b/.github/workflows/reusable-docs.yml index 199e0fd8d181f0b..c7f7663e3162ba2 100644 --- a/.github/workflows/reusable-docs.yml +++ b/.github/workflows/reusable-docs.yml @@ -86,6 +86,9 @@ jobs: --fail-if-regression \ --fail-if-improved \ --fail-if-new-news-nit + - name: 'Build list of changes' + run: | + make -C Doc/ PYTHON=../python changes - name: 'Collect HTML IDs' if: github.event_name == 'pull_request' run: python Doc/tools/check-html-ids.py collect Doc/build/html -o Doc/build/html-ids-head.json.gz