Add full %%py3_check_import macro

epel8
Orion Poplawski 1 year ago
parent 7761cacd3a
commit b52ef20b2b

@ -1,9 +1,10 @@
Name: epel-rpm-macros
Version: 8
Release: 39
Release: 40
Summary: Extra Packages for Enterprise Linux RPM macros
License: GPLv2
# import_all_modules.py: MIT
License: GPLv2 and MIT
# This is a EPEL maintained package which is specific to
# our distribution. Thus the source is only available from
@ -27,6 +28,9 @@ Source151: https://src.fedoraproject.org/rpms/redhat-rpm-config/raw/rawhide
%global rpmautospec_commit 52f3c2017e10c5ab5a183fed772e9fe8a86a20fb
Source152: https://pagure.io/fedora-infra/rpmautospec/raw/%{rpmautospec_commit}/f/rpm/macros.d/macros.rpmautospec
# Python code
Source302: import_all_modules.py
BuildArch: noarch
Requires: redhat-release >= %{version}
# For FPC buildroot macros
@ -106,6 +110,10 @@ install -Dpm 644 %{SOURCE151} \
install -Dpm 644 %{SOURCE152} \
%{buildroot}%{_rpmmacrodir}/macros.rpmautospec
# python scripts
mkdir -p %{buildroot}%{_rpmconfigdir}/redhat
install -Dpm 644 %{SOURCE302} %{buildroot}%{_rpmconfigdir}/redhat/
%files
%license GPL
%{_rpmmacrodir}/macros.epel-rpm-macros
@ -117,6 +125,9 @@ install -Dpm 644 %{SOURCE152} \
%{_rpmmacrodir}/macros.build-constraints
%{_rpmmacrodir}/macros.shell-completions
# python scripts
%{_rpmconfigdir}/redhat/import_all_modules.py
%files systemd
# sysusers
%{_rpmconfigdir}/macros.d/macros.sysusers
@ -126,6 +137,9 @@ install -Dpm 644 %{SOURCE152} \
%changelog
* Fri Oct 06 2023 Orion Poplawski <orion@nwra.com> - 8-40
- Add full %%py3_check_import macro
* Fri Apr 07 2023 Miro Hrončok <mhroncok@redhat.com> - 8-39
- Prepare support for Python 3.11

@ -0,0 +1,171 @@
'''Script to perform import of each module given to %%py_check_import
'''
import argparse
import importlib
import fnmatch
import os
import re
import site
import sys
from contextlib import contextmanager
from pathlib import Path
def read_modules_files(file_paths):
'''Read module names from the files (modules must be newline separated).
Return the module names list or, if no files were provided, an empty list.
'''
if not file_paths:
return []
modules = []
for file in file_paths:
file_contents = file.read_text()
modules.extend(file_contents.split())
return modules
def read_modules_from_cli(argv):
'''Read module names from command-line arguments (space or comma separated).
Return the module names list.
'''
if not argv:
return []
# %%py3_check_import allows to separate module list with comma or whitespace,
# we need to unify the output to a list of particular elements
modules_as_str = ' '.join(argv)
modules = re.split(r'[\s,]+', modules_as_str)
# Because of shell expansion in some less typical cases it may happen
# that a trailing space will occur at the end of the list.
# Remove the empty items from the list before passing it further
modules = [m for m in modules if m]
return modules
def filter_top_level_modules_only(modules):
'''Filter out entries with nested modules (containing dot) ie. 'foo.bar'.
Return the list of top-level modules.
'''
return [module for module in modules if '.' not in module]
def any_match(text, globs):
'''Return True if any of given globs fnmatchcase's the given text.'''
return any(fnmatch.fnmatchcase(text, g) for g in globs)
def exclude_unwanted_module_globs(globs, modules):
'''Filter out entries which match the either of the globs given as argv.
Return the list of filtered modules.
'''
return [m for m in modules if not any_match(m, globs)]
def read_modules_from_all_args(args):
'''Return a joined list of modules from all given command-line arguments.
'''
modules = read_modules_files(args.filename)
modules.extend(read_modules_from_cli(args.modules))
if args.exclude:
modules = exclude_unwanted_module_globs(args.exclude, modules)
if args.top_level:
modules = filter_top_level_modules_only(modules)
# Error when someone accidentally managed to filter out everything
if len(modules) == 0:
raise ValueError('No modules to check were left')
return modules
def import_modules(modules):
'''Procedure to perform import check for each module name from the given list of modules.
'''
for module in modules:
print('Check import:', module, file=sys.stderr)
importlib.import_module(module)
def argparser():
parser = argparse.ArgumentParser(
description='Generate list of all importable modules for import check.'
)
parser.add_argument(
'modules', nargs='*',
help=('Add modules to check the import (space or comma separated).'),
)
parser.add_argument(
'-f', '--filename', action='append', type=Path,
help='Add importable module names list from file.',
)
parser.add_argument(
'-t', '--top-level', action='store_true',
help='Check only top-level modules.',
)
parser.add_argument(
'-e', '--exclude', action='append',
help='Provide modules globs to be excluded from the check.',
)
return parser
@contextmanager
def remove_unwanteds_from_sys_path():
'''Remove cwd and this script's parent from sys.path for the import test.
Bring the original contents back after import is done (or failed)
'''
cwd_absolute = Path.cwd().absolute()
this_file_parent = Path(__file__).parent.absolute()
old_sys_path = list(sys.path)
for path in old_sys_path:
if Path(path).absolute() in (cwd_absolute, this_file_parent):
sys.path.remove(path)
try:
yield
finally:
sys.path = old_sys_path
def addsitedirs_from_environ():
'''Load directories from the _PYTHONSITE environment variable (separated by :)
and load the ones already present in sys.path via site.addsitedir()
to handle .pth files in them.
This is needed to properly import old-style namespace packages with nspkg.pth files.
See https://bugzilla.redhat.com/2018551 for a more detailed rationale.'''
for path in os.getenv('_PYTHONSITE', '').split(':'):
if path in sys.path:
site.addsitedir(path)
def main(argv=None):
cli_args = argparser().parse_args(argv)
if not cli_args.modules and not cli_args.filename:
raise ValueError('No modules to check were provided')
modules = read_modules_from_all_args(cli_args)
with remove_unwanteds_from_sys_path():
addsitedirs_from_environ()
import_modules(modules)
if __name__ == '__main__':
main()

@ -52,16 +52,29 @@
%{__python2} -c "import %{lua:local m=rpm.expand('%{?*}'):gsub('[%s,]+', ', ');print(m)}"
)
}
# With $PATH and $PYTHONPATH set to the %%buildroot,
# try to import the Python 3 module(s) given as command-line args or read from file (-f).
# Respect the custom values of %%py3_shebang_flags or set nothing if it's undefined.
# Filter and check import on only top-level modules using -t flag.
# Exclude unwanted modules by passing their globs to -e option.
# Useful as a smoke test in %%check when running tests is not feasible.
# Use spaces or commas as separators if providing list directly.
# Use newlines as separators if providing list in a file.
%py3_check_import(e:tf:) %{expand:\\\
%{-e:echo 'WARNING: The -e option of %%%%py3_check_import is not currently supported on EPEL.' >&2}
%{-t:echo 'WARNING: The -t option of %%%%py3_check_import is not currently supported on EPEL.' >&2}
%{-f:echo 'WARNING: The -f option of %%%%py3_check_import is not currently supported on EPEL.' >&2}
(cd %{_topdir} &&\\\
PATH="%{buildroot}%{_bindir}:$PATH"\\\
PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}"\\\
_PYTHONSITE="%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}"\\\
PYTHONDONTWRITEBYTECODE=1\\\
%{__python3} -c "import %{lua:local m=rpm.expand('%{?*}'):gsub('[%s,]+', ', ');print(m)}"
)
%{lua:
local command = "%{__python3} "
if rpm.expand("%{?py3_shebang_flags}") ~= "" then
command = command .. "-%{py3_shebang_flags}"
end
command = command .. " %{_rpmconfigdir}/redhat/import_all_modules.py "
-- handle multiline arguments correctly, see https://bugzilla.redhat.com/2018809
local args=rpm.expand('%{?**}'):gsub("[%s\\\\]*%s+", " ")
print(command .. args)
}
}
# When packagers go against the Packaging Guidelines and disable the runtime

Loading…
Cancel
Save