From daade2715a81e21ca58d3356c5e17c66accc8f65 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 10 Jan 2025 00:54:19 +0000 Subject: [PATCH] Rewrite the 'extension metadata' section --- doc/extdev/index.rst | 77 +++++++++++++++++++++------------- sphinx/environment/__init__.py | 19 +++++++-- sphinx/registry.py | 14 ++----- 3 files changed, 68 insertions(+), 42 deletions(-) diff --git a/doc/extdev/index.rst b/doc/extdev/index.rst index 176189927..3442d1c64 100644 --- a/doc/extdev/index.rst +++ b/doc/extdev/index.rst @@ -159,40 +159,61 @@ Extension metadata .. versionadded:: 1.3 -The ``setup()`` function can return a dictionary. This is treated by Sphinx -as metadata of the extension. Metadata keys currently recognized are: +The ``setup()`` function should return a dictionary. +This is treated by Sphinx as metadata of the extension. +Metadata keys currently recognized are: -* ``'version'``: a string that identifies the extension version. It is used for - extension version requirement checking (see :confval:`needs_extensions`) and - informational purposes. If not given, ``"unknown version"`` is substituted. -* ``'env_version'``: an integer that identifies the version of env data - structure if the extension stores any data to environment. It is used to - detect the data structure has been changed from last build. The extensions - have to increment the version when data structure has changed. If not given, - Sphinx considers the extension does not stores any data to environment. -* ``'parallel_read_safe'``: a boolean that specifies if parallel reading of - source files can be used when the extension is loaded. It defaults to - ``False``, i.e. you have to explicitly specify your extension to be - parallel-read-safe after checking that it is. +``'version'`` + a string that identifies the extension version. + It is used for extension version requirement checking + (see :confval:`needs_extensions`) and informational purposes. + If not given, ``"unknown version"`` is substituted. - .. note:: The *parallel-read-safe* extension must satisfy the following - conditions: +``'env_version'`` + a non-zero positive integer integer that records + the version of data stored in the environment by the extension. - * The core logic of the extension is parallelly executable during - the reading phase. - * It has event handlers for :event:`env-merge-info` and - :event:`env-purge-doc` events if it stores data to the build - environment object (env) during the reading phase. + .. attention:: + If ``'env_version'`` is not set, the extension **must not** + store any data or state directly on the environment object (``env``). -* ``'parallel_write_safe'``: a boolean that specifies if parallel writing of - output files can be used when the extension is loaded. Since extensions - usually don't negatively influence the process, this defaults to ``True``. + This key must be defined if the extension uses the ``env`` object to store data. + The version number must be incremented whenever the type, structure, or meaning + of the stored data change, to ensure Sphinx does not try and load invalid data + from a cached environment. - .. note:: The *parallel-write-safe* extension must satisfy the following - conditions: + .. versionadded:: 1.8 - * The core logic of the extension is parallelly executable during - the writing phase. +``'parallel_read_safe'`` + a boolean that specifies if parallel reading of source files + can be used when the extension is loaded. + It defaults to ``False``, meaning that you have to explicitly specify + your extension to be safe for parallel reading after checking that it is. + + .. important:: + + When *parallel-read-safe* is ``True``, + the extension must satisfy the following conditions: + + * The core logic of the extension is parallelly executable during + the reading phase. + * It has event handlers for :event:`env-merge-info` and + :event:`env-purge-doc` events if it stores data to the build + environment object (``env``) during the reading phase. + +``'parallel_write_safe'`` + a boolean that specifies if parallel writing of output files + can be used when the extension is loaded. + Since extensions usually don't negatively influence the process, + this defaults to ``True``. + + .. important:: + + When *parallel-write-safe* is ``True``, + the extension must satisfy the following conditions: + + * The core logic of the extension is parallelly executable during + the writing phase. APIs used for writing extensions diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index f8774e46a..8708b0683 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -31,7 +31,7 @@ from sphinx.util.nodes import is_translatable from sphinx.util.osutil import _last_modified_time, _relative_path, canon_path if TYPE_CHECKING: - from collections.abc import Callable, Iterable, Iterator + from collections.abc import Callable, Iterable, Iterator, Mapping from typing import Any, Literal from docutils import nodes @@ -45,6 +45,7 @@ if TYPE_CHECKING: from sphinx.domains.c._symbol import Symbol as CSymbol from sphinx.domains.cpp._symbol import Symbol as CPPSymbol from sphinx.events import EventManager + from sphinx.extension import Extension from sphinx.project import Project from sphinx.util._pathlib import _StrPath @@ -113,7 +114,7 @@ class BuildEnvironment: self.config_status_extra: str = '' self.events: EventManager = app.events self.project: Project = app.project - self.version: dict[str, int] = app.registry.get_envversion(app) + self.version: Mapping[str, int] = _get_env_version(app.extensions) # the method of doctree versioning; see set_versioning_method self.versioning_condition: Literal[False] | Callable[[Node], bool] | None = None @@ -247,7 +248,7 @@ class BuildEnvironment: def setup(self, app: Sphinx) -> None: """Set up BuildEnvironment object.""" - if self.version and self.version != app.registry.get_envversion(app): + if self.version and self.version != _get_env_version(app.extensions): raise BuildEnvironmentError(__('build environment version not current')) if self.srcdir and self.srcdir != app.srcdir: raise BuildEnvironmentError(__('source directory has changed')) @@ -260,7 +261,7 @@ class BuildEnvironment: self.events = app.events self.srcdir = app.srcdir self.project = app.project - self.version = app.registry.get_envversion(app) + self.version = _get_env_version(app.extensions) # initialise domains if self.domains is None: @@ -808,6 +809,16 @@ class BuildEnvironment: self.events.emit('env-check-consistency', self) +def _get_env_version(extensions: Mapping[str, Extension]) -> Mapping[str, int]: + env_version = { + ext.name: ext_env_version + for ext in extensions.values() + if (ext_env_version := ext.metadata.get('env_version')) + } + env_version['sphinx'] = ENV_VERSION + return env_version + + def _differing_config_keys(old: Config, new: Config) -> frozenset[str]: """Return a set of keys that differ between two config objects.""" old_vals = {c.name: c.value for c in old} diff --git a/sphinx/registry.py b/sphinx/registry.py index 49b121f82..0fe19faa4 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -22,7 +22,7 @@ from sphinx.util.logging import prefixed_warnings if TYPE_CHECKING: import os - from collections.abc import Callable, Iterator, Sequence + from collections.abc import Callable, Iterator, Mapping, Sequence from docutils import nodes from docutils.core import Publisher @@ -565,16 +565,10 @@ class SphinxComponentRegistry: app.extensions[extname] = Extension(extname, mod, **metadata) - def get_envversion(self, app: Sphinx) -> dict[str, int]: - from sphinx.environment import ENV_VERSION + def get_envversion(self, app: Sphinx) -> Mapping[str, int]: + from sphinx.environment import _get_env_version - envversion = { - ext.name: ext.metadata['env_version'] - for ext in app.extensions.values() - if ext.metadata.get('env_version') - } - envversion['sphinx'] = ENV_VERSION - return envversion + return _get_env_version(app.extensions) def get_publisher(self, app: Sphinx, filetype: str) -> Publisher: try: