Allow passing config_settings to the build backend

Resolves: https://bugzilla.redhat.com/2192581
This commit is contained in:
Maxwell G 2023-05-20 00:37:27 +00:00
parent 638ba27daf
commit 156e2fc8fe
No known key found for this signature in database
GPG Key ID: F79E4E25E8C661F8
10 changed files with 197 additions and 13 deletions

View File

@ -4,4 +4,4 @@
# this macro will cause the package with the real macro to be installed. # this macro will cause the package with the real macro to be installed.
# When macros.pyproject is installed, it overrides this macro. # When macros.pyproject is installed, it overrides this macro.
# Note: This needs to maintain the same set of options as the real macro. # Note: This needs to maintain the same set of options as the real macro.
%pyproject_buildrequires(rRxtNwe:) echo 'pyproject-rpm-macros' && exit 0 %pyproject_buildrequires(rRxtNwe:C:) echo 'pyproject-rpm-macros' && exit 0

View File

@ -26,11 +26,11 @@
# The value is read and used by the %%pytest and %%tox macros: # The value is read and used by the %%pytest and %%tox macros:
%_set_pytest_addopts %global __pytest_addopts --ignore=%{_pyproject_builddir} %_set_pytest_addopts %global __pytest_addopts --ignore=%{_pyproject_builddir}
%pyproject_wheel() %{expand:\\\ %pyproject_wheel(C:) %{expand:\\\
%_set_pytest_addopts %_set_pytest_addopts
mkdir -p "%{_pyproject_builddir}" mkdir -p "%{_pyproject_builddir}"
CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}" TMPDIR="%{_pyproject_builddir}" \\\ CFLAGS="${CFLAGS:-${RPM_OPT_FLAGS}}" LDFLAGS="${LDFLAGS:-${RPM_LD_FLAGS}}" TMPDIR="%{_pyproject_builddir}" \\\
%{__python3} -Bs %{_rpmconfigdir}/redhat/pyproject_wheel.py %{_pyproject_wheeldir} %{__python3} -Bs %{_rpmconfigdir}/redhat/pyproject_wheel.py %{?**} %{_pyproject_wheeldir}
} }
@ -140,7 +140,7 @@ fi
# Note: Keep the options in sync with this macro from macros.aaa-pyproject-srpm # Note: Keep the options in sync with this macro from macros.aaa-pyproject-srpm
%pyproject_buildrequires(rRxtNwe:) %{expand:\\\ %pyproject_buildrequires(rRxtNwe:C:) %{expand:\\\
%_set_pytest_addopts %_set_pytest_addopts
# The _auto_set_build_flags feature does not do this in %%generate_buildrequires section, # The _auto_set_build_flags feature does not do this in %%generate_buildrequires section,
# but we want to get an environment consistent with %%build: # but we want to get an environment consistent with %%build:

View File

@ -13,7 +13,7 @@ License: MIT
# Increment Y and reset Z when new macros or features are added # Increment Y and reset Z when new macros or features are added
# Increment Z when this is a bugfix or a cosmetic change # Increment Z when this is a bugfix or a cosmetic change
# Dropping support for EOL Fedoras is *not* considered a breaking change # Dropping support for EOL Fedoras is *not* considered a breaking change
Version: 1.8.1 Version: 1.9.0
Release: 1%{?dist} Release: 1%{?dist}
# Macro files # Macro files
@ -161,6 +161,10 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856
%changelog %changelog
* Wed May 31 2023 Maxwell G <maxwell@gtmx.me> - 1.9.0-1
- Allow passing config_settings to the build backend.
- Resolves: rhbz#2192581
* Wed May 31 2023 Miro Hrončok <mhroncok@redhat.com> - 1.8.1-1 * Wed May 31 2023 Miro Hrončok <mhroncok@redhat.com> - 1.8.1-1
- On Python older than 3.11, use tomli instead of deprecated toml - On Python older than 3.11, use tomli instead of deprecated toml
- Fix literal %% handling in %%{pyproject_files} on RPM 4.19 - Fix literal %% handling in %%{pyproject_files} on RPM 4.19

View File

@ -14,6 +14,7 @@ import pathlib
import zipfile import zipfile
from pyproject_requirements_txt import convert_requirements_txt from pyproject_requirements_txt import convert_requirements_txt
from pyproject_wheel import parse_config_settings_args
# Some valid Python version specifiers are not supported. # Some valid Python version specifiers are not supported.
@ -67,7 +68,7 @@ def guess_reason_for_invalid_requirement(requirement_str):
class Requirements: class Requirements:
"""Requirement gatherer. The macro will eventually print out output_lines.""" """Requirement gatherer. The macro will eventually print out output_lines."""
def __init__(self, get_installed_version, extras=None, def __init__(self, get_installed_version, extras=None,
generate_extras=False, python3_pkgversion='3'): generate_extras=False, python3_pkgversion='3', config_settings=None):
self.get_installed_version = get_installed_version self.get_installed_version = get_installed_version
self.output_lines = [] self.output_lines = []
self.extras = set() self.extras = set()
@ -81,6 +82,7 @@ class Requirements:
self.generate_extras = generate_extras self.generate_extras = generate_extras
self.python3_pkgversion = python3_pkgversion self.python3_pkgversion = python3_pkgversion
self.config_settings = config_settings
def add_extras(self, *extras): def add_extras(self, *extras):
self.extras |= set(e.strip() for e in extras) self.extras |= set(e.strip() for e in extras)
@ -269,7 +271,7 @@ def get_backend(requirements):
def generate_build_requirements(backend, requirements): def generate_build_requirements(backend, requirements):
get_requires = getattr(backend, 'get_requires_for_build_wheel', None) get_requires = getattr(backend, 'get_requires_for_build_wheel', None)
if get_requires: if get_requires:
new_reqs = get_requires() new_reqs = get_requires(config_settings=requirements.config_settings)
requirements.extend(new_reqs, source='get_requires_for_build_wheel') requirements.extend(new_reqs, source='get_requires_for_build_wheel')
requirements.check(source='get_requires_for_build_wheel') requirements.check(source='get_requires_for_build_wheel')
@ -303,7 +305,7 @@ def generate_run_requirements_hook(backend, requirements):
'Use the provisional -w flag to build the wheel and parse the metadata from it, ' 'Use the provisional -w flag to build the wheel and parse the metadata from it, '
'or use the -R flag not to generate runtime dependencies.' 'or use the -R flag not to generate runtime dependencies.'
) )
dir_basename = prepare_metadata('.') dir_basename = prepare_metadata('.', config_settings=requirements.config_settings)
with open(dir_basename + '/METADATA') as metadata_file: with open(dir_basename + '/METADATA') as metadata_file:
name, requires = package_name_and_requires_from_metadata_file(metadata_file) name, requires = package_name_and_requires_from_metadata_file(metadata_file)
for key, req in requires.items(): for key, req in requires.items():
@ -327,7 +329,11 @@ def generate_run_requirements_wheel(backend, requirements, wheeldir):
wheel = find_built_wheel(wheeldir) wheel = find_built_wheel(wheeldir)
if not wheel: if not wheel:
import pyproject_wheel import pyproject_wheel
returncode = pyproject_wheel.build_wheel(wheeldir=wheeldir, stdout=sys.stderr) returncode = pyproject_wheel.build_wheel(
wheeldir=wheeldir,
stdout=sys.stderr,
config_settings=requirements.config_settings,
)
if returncode != 0: if returncode != 0:
raise RuntimeError('Failed to build the wheel for %pyproject_buildrequires -w.') raise RuntimeError('Failed to build the wheel for %pyproject_buildrequires -w.')
wheel = find_built_wheel(wheeldir) wheel = find_built_wheel(wheeldir)
@ -415,7 +421,7 @@ def generate_requires(
*, include_runtime=False, build_wheel=False, wheeldir=None, toxenv=None, extras=None, *, include_runtime=False, build_wheel=False, wheeldir=None, toxenv=None, extras=None,
get_installed_version=importlib.metadata.version, # for dep injection get_installed_version=importlib.metadata.version, # for dep injection
generate_extras=False, python3_pkgversion="3", requirement_files=None, use_build_system=True, generate_extras=False, python3_pkgversion="3", requirement_files=None, use_build_system=True,
output, output, config_settings=None,
): ):
"""Generate the BuildRequires for the project in the current directory """Generate the BuildRequires for the project in the current directory
@ -426,7 +432,8 @@ def generate_requires(
requirements = Requirements( requirements = Requirements(
get_installed_version, extras=extras or [], get_installed_version, extras=extras or [],
generate_extras=generate_extras, generate_extras=generate_extras,
python3_pkgversion=python3_pkgversion python3_pkgversion=python3_pkgversion,
config_settings=config_settings,
) )
try: try:
@ -516,6 +523,12 @@ def main(argv):
metavar='REQUIREMENTS.TXT', metavar='REQUIREMENTS.TXT',
help=('Add buildrequires from file'), help=('Add buildrequires from file'),
) )
parser.add_argument(
'-C',
dest='config_settings',
action='append',
help='Configuration settings to pass to the PEP 517 backend',
)
args = parser.parse_args(argv) args = parser.parse_args(argv)
@ -550,6 +563,7 @@ def main(argv):
requirement_files=args.requirement_files, requirement_files=args.requirement_files,
use_build_system=args.use_build_system, use_build_system=args.use_build_system,
output=args.output, output=args.output,
config_settings=parse_config_settings_args(args.config_settings),
) )
except Exception: except Exception:
# Log the traceback explicitly (it's useful debug info) # Log the traceback explicitly (it's useful debug info)

View File

@ -986,3 +986,40 @@ Self-referencing extras (maze):
python3dist(rightdep) python3dist(rightdep)
python3dist(startdep) python3dist(startdep)
result: 0 result: 0
config_settings_control:
include_runtime: false
config_settings:
pyproject.toml: |
[build-system]
build-backend = "test_backend"
backend-path = ["."]
test_backend.py: |
def get_requires_for_build_wheel(config_settings=None):
if not (config_settings is None or isinstance(config_settings, dict)):
raise TypeError
if config_settings and "test-config-setting" in config_settings:
return ["test-config-setting"]
return ["test-no-config-setting"]
expected: |
python3dist(test-no-config-setting)
result: 0
config_settings:
include_runtime: false
config_settings:
test-config-setting: ""
pyproject.toml: |
[build-system]
build-backend = "test_backend"
backend-path = ["."]
test_backend.py: |
def get_requires_for_build_wheel(config_settings=None):
if not (config_settings is None or isinstance(config_settings, dict)):
raise TypeError
if config_settings and "test-config-setting" in config_settings:
return ["test-config-setting"]
return ["test-no-config-setting"]
expected: |
python3dist(test-config-setting)
result: 0

View File

@ -1,8 +1,37 @@
import argparse
import sys import sys
import subprocess import subprocess
def build_wheel(*, wheeldir, stdout=None): def parse_config_settings_args(config_settings):
"""
Given a list of config `KEY=VALUE` formatted config settings,
return a dictionary that can be passed to PEP 517 hook functions.
"""
if not config_settings:
return config_settings
new_config_settings = {}
for arg in config_settings:
key, _, value = arg.partition('=')
new_config_settings[key] = value
return new_config_settings
def get_config_settings_args(config_settings):
"""
Given a dictionary of PEP 517 backend config_settings,
yield --config-settings args that can be passed to pip's CLI
"""
if not config_settings:
return
for key, value in config_settings.items():
if value == '':
yield f'--config-settings={key}'
else:
yield f'--config-settings={key}={value}'
def build_wheel(*, wheeldir, stdout=None, config_settings=None):
command = ( command = (
sys.executable, sys.executable,
'-m', 'pip', '-m', 'pip',
@ -15,11 +44,26 @@ def build_wheel(*, wheeldir, stdout=None):
'--no-clean', '--no-clean',
'--progress-bar', 'off', '--progress-bar', 'off',
'--verbose', '--verbose',
*get_config_settings_args(config_settings),
'.', '.',
) )
cp = subprocess.run(command, stdout=stdout) cp = subprocess.run(command, stdout=stdout)
return cp.returncode return cp.returncode
def parse_args(argv=None):
parser = argparse.ArgumentParser(prog='%pyproject_wheel')
parser.add_argument('wheeldir', help=argparse.SUPPRESS)
parser.add_argument(
'-C',
dest='config_settings',
action='append',
help='Configuration settings to pass to the PEP 517 backend',
)
args = parser.parse_args(argv)
args.config_settings = parse_config_settings_args(args.config_settings)
return args
if __name__ == '__main__': if __name__ == '__main__':
sys.exit(build_wheel(wheeldir=sys.argv[1])) sys.exit(build_wheel(**vars(parse_args())))

View File

@ -63,6 +63,7 @@ def test_data(case_name, capfd, tmp_path, monkeypatch):
requirement_files=requirement_files, requirement_files=requirement_files,
use_build_system=use_build_system, use_build_system=use_build_system,
output=output, output=output,
config_settings=case.get('config_settings'),
) )
except SystemExit as e: except SystemExit as e:
assert e.code == case['result'] assert e.code == case['result']

View File

@ -0,0 +1,50 @@
Name: config-settings-test
Version: 1.0.0
Release: 1%{?dist}
Summary: Test config_settings support
License: MIT
URL: ...
Source0: config_settings_test_backend.py
%description
%{summary}.
%prep
%autosetup -cT
cp -p %{sources} .
cat <<'EOF' >config_settings.py
"""
This is a test package
"""
EOF
cat <<'EOF' >pyproject.toml
[build-system]
build-backend = "config_settings_test_backend"
backend-path = ["."]
requires = ["flit-core"]
[project]
name = "config_settings"
version = "%{version}"
dynamic = ["description"]
EOF
%generate_buildrequires
%pyproject_buildrequires -C abc=123 -C xyz=456 -C--option-with-dashes=1
%pyproject_buildrequires -C abc=123 -C xyz=456 -C--option-with-dashes=1 -w
%build
%pyproject_wheel -C abc=123 -C xyz=456 -C--option-with-dashes=1
%changelog
* Fri May 19 2023 Maxwell G <maxwell@gtmx.me>
- Initial package

View File

@ -0,0 +1,31 @@
"""
This is a test backend for pyproject-rpm-macros' integration tests
It is not compliant with PEP 517 and omits some required hooks.
"""
from flit_core import buildapi
EXPECTED_CONFIG_SETTINGS = {"abc": "123", "xyz": "456", "--option-with-dashes": "1"}
def _verify_config_settings(config_settings):
print(f"config_settings={config_settings}")
if config_settings != EXPECTED_CONFIG_SETTINGS:
raise ValueError(
f"{config_settings!r} does not match expected {EXPECTED_CONFIG_SETTINGS!r}"
)
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
_verify_config_settings(config_settings)
return buildapi.build_wheel(wheel_directory, None, metadata_directory)
def get_requires_for_build_wheel(config_settings=None):
_verify_config_settings(config_settings)
return buildapi.get_requires_for_build_wheel(None)
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
_verify_config_settings(config_settings)
return buildapi.prepare_metadata_for_build_wheel(metadata_directory, None)

View File

@ -94,6 +94,9 @@
- escape_percentages: - escape_percentages:
dir: . dir: .
run: ./mocktest.sh escape_percentages run: ./mocktest.sh escape_percentages
- config-settings-test:
dir: .
run: ./mocktest.sh config-settings-test
required_packages: required_packages:
- mock - mock
- rpmdevtools - rpmdevtools