Rewrite the 'extension metadata' section

This commit is contained in:
Adam Turner
2025-01-10 00:54:19 +00:00
parent 74ec220479
commit daade2715a
3 changed files with 68 additions and 42 deletions

View File

@@ -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

View File

@@ -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}

View File

@@ -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: