From 913db09fbb65caea4f098cde9560c6ae59fe0a4d Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 15 Mar 2023 22:48:43 +0100 Subject: [PATCH] =?UTF-8?q?[backport-2.14]=20=F0=9F=8E=A8=20Convert=20RST?= =?UTF-8?q?=20to=20manpage=20in-memory=20@=20PEP=20517=20(#80237)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 67bafafbc0d61751d2ec10b0aeccb6b01482a074) This is a backport of #80098. --- packaging/pep517_backend/_backend.py | 68 ++++++++++++++++++---------- 1 file changed, 45 insertions(+), 23 deletions(-) diff --git a/packaging/pep517_backend/_backend.py b/packaging/pep517_backend/_backend.py index 9adbc50f2fe..973279d8280 100644 --- a/packaging/pep517_backend/_backend.py +++ b/packaging/pep517_backend/_backend.py @@ -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(''), - ) - subprocess.check_call(tuple(map(str, rst2man_cmd))) - templated_rst_doc.unlink() + with StringIO(templated_rst_doc) as in_mem_rst_doc: + publish_file( + source=in_mem_rst_doc, + destination_path=destination_path, + writer=manpage.Writer(), + ) 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