forked from ansible/ansible-documentation
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🤖 Set build config via Sphinx ext
This is an initial change allowing for restructuring how the config settings are computed and putting that logic into a dedicated Sphinx extension. The idea is that this extension may have multiple callbacks that set configuration for Sphinx based on tags and the environment state. As an example, this in-tree extension implements setting the `is_eol` variable in Jinja2 context very early in Sphinx life cycle. It does this based on inspecting the state of current Git checkout as well as reading a config file listing EOL and supported versions of `ansible-core`. The configuration format is TOML as it's gained a lot of the ecosystem adoption over the past years and its parser made its way into the standard library of Python, while PyYAML remains a third-party dependency. Supersedes ansible#2251.
- Loading branch information
Showing
3 changed files
with
149 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
"""Sphinx extension for setting up the build settings.""" | ||
|
||
import subprocess | ||
from dataclasses import dataclass | ||
from functools import cache | ||
from pathlib import Path | ||
from tomllib import loads as parse_toml_string | ||
from typing import Literal | ||
|
||
from sphinx.application import Sphinx | ||
from sphinx.util import logging | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
DOCSITE_ROOT_DIR = Path(__file__).parents[1].resolve() | ||
DOCSITE_EOL_CONFIG_PATH = DOCSITE_ROOT_DIR / 'end_of_life.toml' | ||
|
||
|
||
@dataclass(frozen=True) | ||
class Distribution: | ||
end_of_life: list[str] | ||
supported: list[str] | ||
|
||
@classmethod | ||
def from_dict(cls, raw_dist: dict[str, list[str]]) -> 'Distribution': | ||
return cls( | ||
**{ | ||
kind.replace('-', '_'): versions | ||
for kind, versions in raw_dist.items() | ||
}, | ||
) | ||
|
||
|
||
EOLConfigType = dict[str, Distribution] | ||
|
||
|
||
@cache | ||
def _read_eol_data() -> EOLConfigType: | ||
raw_config_dict = parse_toml_string(DOCSITE_EOL_CONFIG_PATH.read_text()) | ||
|
||
return { | ||
dist_name: Distribution.from_dict(dist_data) | ||
for dist_name, dist_data in raw_config_dict['distribution'].items() | ||
} | ||
|
||
|
||
@cache | ||
def _is_eol_build(git_branch: str, kind: str) -> bool: | ||
return git_branch in _read_eol_data()[kind].end_of_life | ||
|
||
|
||
@cache | ||
def _get_current_git_branch(): | ||
git_branch_cmd = 'git', 'rev-parse', '--abbrev-ref', 'HEAD' | ||
|
||
try: | ||
return subprocess.check_output(git_branch_cmd, text=True).strip() | ||
except subprocess.CalledProcessError as proc_err: | ||
raise LookupError( | ||
f'Failed to locate current Git branch: {proc_err !s}', | ||
) from proc_err | ||
|
||
|
||
def _set_global_j2_context(app, config): | ||
if 'is_eol' in config.html_context: | ||
raise ValueError( | ||
'`is_eol` found in `html_context` unexpectedly. ' | ||
'It should not be set in `conf.py`.', | ||
) from None | ||
|
||
dist_name = ( | ||
'ansible-core' if app.tags.has('core') | ||
else 'ansible' if app.tags.has('ansible') | ||
else None | ||
) | ||
|
||
if dist_name is None: | ||
return | ||
|
||
try: | ||
git_branch = _get_current_git_branch() | ||
except LookupError as lookup_err: | ||
logger.info(str(lookup_err)) | ||
return | ||
|
||
config.html_context['is_eol'] = _is_eol_build( | ||
git_branch=git_branch, kind=dist_name, | ||
) | ||
|
||
|
||
def setup(app: Sphinx) -> dict[str, bool | str]: | ||
"""Initialize the extension. | ||
:param app: A Sphinx application object. | ||
:returns: Extension metadata as a dict. | ||
""" | ||
|
||
# NOTE: `config-inited` is used because it runs once as opposed to | ||
# NOTE: `html-page-context` that runs per each page. The data we | ||
# NOTE: compute is immutable throughout the build so there's no need | ||
# NOTE: to have a callback that would be executed hundreds of times. | ||
app.connect('config-inited', _set_global_j2_context) | ||
|
||
return { | ||
'parallel_read_safe': True, | ||
'parallel_write_safe': True, | ||
'version': app.config.release, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
[distribution.ansible] | ||
end-of-life = [ | ||
'stable-2.15', | ||
'stable-2.14', | ||
'stable-2.13', | ||
] | ||
supported = [ | ||
'devel', | ||
'stable-2.18', | ||
'stable-2.17', | ||
'stable-2.16', | ||
] | ||
|
||
[distribution.ansible-core] | ||
end-of-life = [ | ||
'stable-2.15', | ||
'stable-2.14', | ||
'stable-2.13', | ||
] | ||
supported = [ | ||
'devel', | ||
'stable-2.18', | ||
'stable-2.17', | ||
'stable-2.16', | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters