[backport-2.14] 🎨 Convert RST to manpage in-memory @ PEP 517 (#80237)

* Add _convert_rst_in_template_to_manpage arg types

* 📦 Make manpage build dependencies conditional

Previously, said dependencies were declared as unconditionally
required even when manpages not needed to be built. This patch
Makes it so they are only required when needed.

* Correct _generate_rst_in_templates returned type

It was marked as Path before this patch but in fact, it's iterable of
paths.

* 🎨 Convert RST to manpage in-memory @ PEP 517

Previously, the automation was writing a temporary templated RST on
disk and calling a helper CLI script on that. But with this change, it
happens with less unnecessary I/O.

Co-Authored-By: Matt Davis <6775756+nitzmahone@users.noreply.github.com>

* 📦Expose sdist manpage build deps unconditionally

Due to a bug in pypa/build, the `get_requires_for_build_sdist()` hook
is always invoked with `config_settings=None`. This means that we
cannot conditionally extend build requirements in said hook.

As a workaround, this patch makes hook pretend that `--built-manpages`
is always passed.

Ref: https://github.com/pypa/build/issues/559.

---------

Co-authored-by: Matt Davis <6775756+nitzmahone@users.noreply.github.com>
(cherry picked from commit 67bafafbc0)

This is a backport of #80098.
pull/80260/head
Sviatoslav Sydorenko 2 years ago committed by GitHub
parent 86ebd55a3c
commit 913db09fbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,8 +5,11 @@ from __future__ import annotations
import os
import subprocess
import sys
import typing as t
from configparser import ConfigParser
from contextlib import suppress
from importlib.metadata import import_module
from io import StringIO
from pathlib import Path
from setuptools.build_meta import (
@ -14,16 +17,25 @@ from setuptools.build_meta import (
get_requires_for_build_sdist as _setuptools_get_requires_for_build_sdist,
)
with suppress(ImportError):
# NOTE: Only available for sdist builds that bundle manpages. Declared by
# NOTE: `get_requires_for_build_sdist()` when `--build-manpages` is passed.
from docutils.core import publish_file
from docutils.writers import manpage
__all__ = ( # noqa: WPS317, WPS410
'build_sdist', 'get_requires_for_build_sdist',
)
BUILD_MANPAGES_CONFIG_SETTING = '--build-manpages'
"""Config setting name toggle that is used to request manpage in sdists."""
def _make_in_tree_ansible_importable() -> None:
"""Add the library directory to module lookup paths."""
lib_path = str(Path.cwd() / 'lib/')
os.environ['PYTHONPATH'] = lib_path # NOTE: for subprocesses
sys.path.insert(0, lib_path) # NOTE: for the current runtime session
@ -35,10 +47,11 @@ def _get_package_distribution_version() -> str:
cfg_version = setup_cfg.get('metadata', 'version')
importable_version_str = cfg_version.removeprefix('attr: ')
version_mod_str, version_var_str = importable_version_str.rsplit('.', 1)
_make_in_tree_ansible_importable()
return getattr(import_module(version_mod_str), version_var_str)
def _generate_rst_in_templates() -> Path:
def _generate_rst_in_templates() -> t.Iterable[Path]:
"""Create ``*.1.rst.in`` files out of CLI Python modules."""
generate_man_cmd = (
sys.executable,
@ -53,41 +66,43 @@ def _generate_rst_in_templates() -> Path:
return Path('docs/man/man1/').glob('*.1.rst.in')
def _convert_rst_in_template_to_manpage(rst_in, version_number) -> None:
def _convert_rst_in_template_to_manpage(
rst_doc_template: str,
destination_path: os.PathLike,
version_number: str,
) -> None:
"""Render pre-made ``*.1.rst.in`` templates into manpages.
This includes pasting the hardcoded version into the resulting files.
The resulting ``in``-files are wiped in the process.
"""
templated_rst_doc = rst_in.with_suffix('')
templated_rst_doc.write_text(
rst_in.read_text().replace('%VERSION%', version_number))
templated_rst_doc = rst_doc_template.replace('%VERSION%', version_number)
rst_in.unlink()
rst2man_cmd = (
sys.executable,
Path(sys.executable).parent / 'rst2man.py',
templated_rst_doc,
templated_rst_doc.with_suffix(''),
with StringIO(templated_rst_doc) as in_mem_rst_doc:
publish_file(
source=in_mem_rst_doc,
destination_path=destination_path,
writer=manpage.Writer(),
)
subprocess.check_call(tuple(map(str, rst2man_cmd)))
templated_rst_doc.unlink()
def build_sdist( # noqa: WPS210, WPS430
sdist_directory: os.PathLike,
config_settings: dict[str, str] | None = None,
) -> str:
build_manpages_requested = '--build-manpages' in (
build_manpages_requested = BUILD_MANPAGES_CONFIG_SETTING in (
config_settings or {}
)
if build_manpages_requested:
Path('docs/man/man1/').mkdir(exist_ok=True, parents=True)
_make_in_tree_ansible_importable()
version_number = _get_package_distribution_version()
for rst_in in _generate_rst_in_templates():
_convert_rst_in_template_to_manpage(rst_in, version_number)
_convert_rst_in_template_to_manpage(
rst_doc_template=rst_in.read_text(),
destination_path=rst_in.with_suffix('').with_suffix(''),
version_number=version_number,
)
rst_in.unlink()
return _setuptools_build_sdist(
sdist_directory=sdist_directory,
@ -98,11 +113,18 @@ def build_sdist( # noqa: WPS210, WPS430
def get_requires_for_build_sdist(
config_settings: dict[str, str] | None = None,
) -> list[str]:
return _setuptools_get_requires_for_build_sdist(
config_settings=config_settings,
) + [
build_manpages_requested = BUILD_MANPAGES_CONFIG_SETTING in (
config_settings or {}
)
build_manpages_requested = True # FIXME: Once pypa/build#559 is addressed.
manpage_build_deps = [
'docutils', # provides `rst2man`
'jinja2', # used in `hacking/build-ansible.py generate-man`
'straight.plugin', # used in `hacking/build-ansible.py` for subcommand
'pyyaml', # needed for importing in-tree `ansible-core` from `lib/`
]
] if build_manpages_requested else []
return _setuptools_get_requires_for_build_sdist(
config_settings=config_settings,
) + manpage_build_deps

Loading…
Cancel
Save