diff --git a/SOURCES/README.md b/SOURCES/README.md index f5aea49..970580b 100644 --- a/SOURCES/README.md +++ b/SOURCES/README.md @@ -140,7 +140,7 @@ such plugins will be BuildRequired as well. Not all plugins are guaranteed to play well with [tox-current-env], in worst case, patch/sed the requirement out from the tox configuration. -Note that both `-x` and `-t` imply `-r`, +Note that neither `-x` or `-t` can be used with `-R`, because runtime dependencies are always required for testing. You can only use those options if the build backend supports the [prepare-metadata-for-build-wheel hook], or together with `-w`. @@ -152,12 +152,16 @@ or together with `-w`. Additionally to generated requirements you can supply multiple file names to `%pyproject_buildrequires` macro. Dependencies will be loaded from them: - %pyproject_buildrequires -r requirements/tests.in requirements/docs.in requirements/dev.in + %pyproject_buildrequires requirements/tests.in requirements/docs.in requirements/dev.in For packages not using build system you can use `-N` to entirely skip automatical generation of requirements and install requirements only from manually specified files. -`-N` option cannot be used in combination with other options mentioned above -(`-r`, `-w`, `-e`, `-t`, `-x`). +`-N` option implies `-R` and cannot be used in combination with other options mentioned above +(`-w`, `-e`, `-t`, `-x`). + +The `%pyproject_buildrequires` macro also accepts the `-r` flag for backward compatibility; +it means "include runtime dependencies" which has been the default since version 0-53. + Running tox based tests ----------------------- @@ -171,8 +175,9 @@ Then, use the `%tox` macro in `%check`: The macro: - - Always prepends `$PATH` with `%{buildroot}%{_bindir}` - - If not defined, sets `$PYTHONPATH` to `%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}` + - Sets environment variables via `%{py3_test_envvars}`, namely: + - Always prepends `$PATH` with `%{buildroot}%{_bindir}` + - If not defined, sets `$PYTHONPATH` to `%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}` - If not defined, sets `$TOX_TESTENV_PASSENV` to `*` - Runs `tox` with `-q` (quiet), `--recreate` and `--current-env` (from [tox-current-env]) flags - Implicitly uses the tox environment name stored in `%{toxenv}` - as overridden by `%pyproject_buildrequires -e` @@ -265,6 +270,7 @@ If `%pyproject_save_files` is not used, calling `%pyproject_check_import` will f When `%pyproject_save_files` is invoked, it creates a list of all valid and public (i.e. not starting with `_`) importable module names found in the package. +Each top-level module name matches at least one of the globs provided as an argument to `%pyproject_save_files`. This list is then usable by `%pyproject_check_import` which performs an import check for each listed module. When a module fails to import, the build fails. diff --git a/SOURCES/macros.aaa-pyproject-srpm b/SOURCES/macros.aaa-pyproject-srpm new file mode 100644 index 0000000..9dab590 --- /dev/null +++ b/SOURCES/macros.aaa-pyproject-srpm @@ -0,0 +1,7 @@ +# This file is called macros.aaa-pyproject-srpm +# to sort alphabetically before macros.pyproject. +# When this file is installed but macros.pyproject is not +# this macro will cause the package with the real macro to be installed. +# When macros.pyproject is installed, it overrides this macro. +# Note: This needs to maintain the same set of options as the real macro. +%pyproject_buildrequires(rRxtNwe:) echo 'pyproject-rpm-macros' && exit 0 diff --git a/SOURCES/macros.pyproject b/SOURCES/macros.pyproject index ca1ca68..8cc59ae 100644 --- a/SOURCES/macros.pyproject +++ b/SOURCES/macros.pyproject @@ -124,6 +124,7 @@ fi %toxenv %{default_toxenv} +# Note: Keep the options in sync with this macro from macros.aaa-pyproject-srpm %pyproject_buildrequires(rRxtNwe:) %{expand:\\\ %_set_pytest_addopts # The _auto_set_build_flags feature does not do this in %%generate_buildrequires section, @@ -144,7 +145,7 @@ fi %{-w:%{error:The -N and -w options are mutually exclusive}} } %{-e:%{expand:%global toxenv %(%{__python3} -s %{_rpmconfigdir}/redhat/pyproject_construct_toxenv.py %{?**})}} -echo 'pyproject-rpm-macros' # we already have this installed, but this way, it's repoqueryable +echo 'pyproject-rpm-macros' # first stdout line matches the implementation in macros.aaa-pyproject-srpm echo 'python%{python3_pkgversion}-devel' echo 'python%{python3_pkgversion}dist(pip) >= 19' echo 'python%{python3_pkgversion}dist(packaging)' @@ -176,10 +177,10 @@ fi %tox(e:) %{expand:\\\ TOX_TESTENV_PASSENV="${TOX_TESTENV_PASSENV:-*}" \\ -PYTHONDONTWRITEBYTECODE=1 \\ +%{?py3_test_envvars}%{?!py3_test_envvars:PYTHONDONTWRITEBYTECODE=1 \\ PATH="%{buildroot}%{_bindir}:$PATH" \\ PYTHONPATH="${PYTHONPATH:-%{buildroot}%{python3_sitearch}:%{buildroot}%{python3_sitelib}}" \\ -%{?__pytest_addopts:PYTEST_ADDOPTS="${PYTEST_ADDOPTS:-} %{__pytest_addopts}"} \\ +%{?__pytest_addopts:PYTEST_ADDOPTS="${PYTEST_ADDOPTS:-} %{__pytest_addopts}"}} \\ HOSTNAME="rpmbuild" \\ %{__python3} -m tox --current-env -q --recreate -e "%{-e:%{-e*}}%{!-e:%{toxenv}}" %{?*} } diff --git a/SOURCES/pyproject_buildrequires.py b/SOURCES/pyproject_buildrequires.py index e604de5..323ab2a 100644 --- a/SOURCES/pyproject_buildrequires.py +++ b/SOURCES/pyproject_buildrequires.py @@ -4,9 +4,9 @@ import os import sys import importlib.metadata import argparse +import tempfile import traceback import contextlib -from io import StringIO import json import subprocess import re @@ -48,11 +48,35 @@ from pyproject_convert import convert @contextlib.contextmanager def hook_call(): - captured_out = StringIO() - with contextlib.redirect_stdout(captured_out): + """Context manager that records all stdout content (on FD level) + and prints it to stderr at the end, with a 'HOOK STDOUT: ' prefix.""" + tmpfile = io.TextIOWrapper( + tempfile.TemporaryFile(buffering=0), + encoding='utf-8', + errors='replace', + write_through=True, + ) + + stdout_fd = 1 + stdout_fd_dup = os.dup(stdout_fd) + stdout_orig = sys.stdout + + # begin capture + sys.stdout = tmpfile + os.dup2(tmpfile.fileno(), stdout_fd) + + try: yield - for line in captured_out.getvalue().splitlines(): - print_err('HOOK STDOUT:', line) + finally: + # end capture + sys.stdout = stdout_orig + os.dup2(stdout_fd_dup, stdout_fd) + + tmpfile.seek(0) # rewind + for line in tmpfile: + print_err('HOOK STDOUT:', line, end='') + + tmpfile.close() def guess_reason_for_invalid_requirement(requirement_str): @@ -100,7 +124,7 @@ class Requirements: return [{'extra': e} for e in sorted(self.extras)] return [{'extra': ''}] - def evaluate_all_environamnets(self, requirement): + def evaluate_all_environments(self, requirement): for marker_env in self.marker_envs: if requirement.marker.evaluate(environment=marker_env): return True @@ -126,7 +150,7 @@ class Requirements: name = canonicalize_name(requirement.name) if (requirement.marker is not None and - not self.evaluate_all_environamnets(requirement)): + not self.evaluate_all_environments(requirement)): print_err(f'Ignoring alien requirement:', requirement_str) return @@ -424,34 +448,30 @@ def generate_requires( def main(argv): parser = argparse.ArgumentParser( - description='Generate BuildRequires for a Python project.' + description='Generate BuildRequires for a Python project.', + prog='%pyproject_buildrequires', + add_help=False, + ) + parser.add_argument( + '--help', action='help', + default=argparse.SUPPRESS, + help=argparse.SUPPRESS, ) parser.add_argument( '-r', '--runtime', action='store_true', default=True, - help='Generate run-time requirements (default, disable with -R)', + help=argparse.SUPPRESS, # Generate run-time requirements (backwards-compatibility only) ) parser.add_argument( - '-w', '--wheel', action='store_true', default=False, - help=('Generate run-time requirements by building the wheel ' - '(useful for build backends without the prepare_metadata_for_build_wheel hook)'), + '--generate-extras', action='store_true', + help=argparse.SUPPRESS, + ) + parser.add_argument( + '-p', '--python3_pkgversion', metavar='PYTHON3_PKGVERSION', + default="3", help=argparse.SUPPRESS, ) parser.add_argument( '--wheeldir', metavar='PATH', default=None, - help='The directory with wheel, used when -w.', - ) - parser.add_argument( - '-R', '--no-runtime', action='store_false', dest='runtime', - help="Don't generate run-time requirements (implied by -N)", - ) - parser.add_argument( - '-e', '--toxenv', metavar='TOXENVS', action='append', - help=('specify tox environments (comma separated and/or repeated)' - '(implies --tox)'), - ) - parser.add_argument( - '-t', '--tox', action='store_true', - help=('generate test tequirements from tox environment ' - '(implies --runtime)'), + help=argparse.SUPPRESS, ) parser.add_argument( '-x', '--extras', metavar='EXTRAS', action='append', @@ -459,20 +479,31 @@ def main(argv): '(e.g. -x testing,feature-x) (implies --runtime, can be repeated)', ) parser.add_argument( - '--generate-extras', action='store_true', - help='Generate build requirements on Python Extras', + '-t', '--tox', action='store_true', + help=('generate test tequirements from tox environment ' + '(implies --runtime)'), ) parser.add_argument( - '-p', '--python3_pkgversion', metavar='PYTHON3_PKGVERSION', - default="3", help=('Python version for pythonXdist()' - 'or pythonX.Ydist() requirements'), + '-e', '--toxenv', metavar='TOXENVS', action='append', + help=('specify tox environments (comma separated and/or repeated)' + '(implies --tox)'), + ) + parser.add_argument( + '-w', '--wheel', action='store_true', default=False, + help=('Generate run-time requirements by building the wheel ' + '(useful for build backends without the prepare_metadata_for_build_wheel hook)'), + ) + parser.add_argument( + '-R', '--no-runtime', action='store_false', dest='runtime', + help="Don't generate run-time requirements (implied by -N)", ) parser.add_argument( '-N', '--no-use-build-system', dest='use_build_system', action='store_false', help='Use -N to indicate that project does not use any build system', ) parser.add_argument( - 'requirement_files', nargs='*', type=argparse.FileType('r'), + 'requirement_files', nargs='*', type=argparse.FileType('r'), + metavar='REQUIREMENTS.TXT', help=('Add buildrequires from file'), ) diff --git a/SOURCES/pyproject_buildrequires_testcases.yaml b/SOURCES/pyproject_buildrequires_testcases.yaml index 50f9d4e..1a2e6bb 100644 --- a/SOURCES/pyproject_buildrequires_testcases.yaml +++ b/SOURCES/pyproject_buildrequires_testcases.yaml @@ -413,14 +413,24 @@ Tox dependencies: toxdep2 commands = true - expected: | - python3dist(setuptools) >= 40.8 - python3dist(wheel) - python3dist(wheel) - python3dist(tox-current-env) >= 0.0.6 - python3dist(toxdep1) - python3dist(toxdep2) - python3dist(inst) + expected: + - | # tox 3 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(toxdep1) + python3dist(toxdep2) + python3dist(inst) + - | # tox 4 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(tox) + python3dist(toxdep1) + python3dist(toxdep2) + python3dist(inst) result: 0 Tox extras: @@ -455,20 +465,36 @@ Tox extras: extra1 commands = true - expected: | - python3dist(setuptools) >= 40.8 - python3dist(wheel) - python3dist(wheel) - python3dist(tox-current-env) >= 0.0.6 - python3dist(toxdep) - python3dist(inst) - python3dist(dep11) > 11.0 - python3dist(dep12) - python3dist(dep21) - python3dist(dep22) - python3dist(dep23) - python3dist(extra-dep) - python3dist(extra-dep[extra_dep]) + expected: + - | # tox 3 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(toxdep) + python3dist(inst) + python3dist(dep11) > 11.0 + python3dist(dep12) + python3dist(dep21) + python3dist(dep22) + python3dist(dep23) + python3dist(extra-dep) + python3dist(extra-dep[extra_dep]) + - | # tox 4 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(tox) + python3dist(toxdep) + python3dist(inst) + python3dist(dep11) > 11.0 + python3dist(dep12) + python3dist(dep21) + python3dist(dep22) + python3dist(dep23) + python3dist(extra-dep) + python3dist(extra-dep[extra_dep]) result: 0 Tox provision unsatisfied: @@ -496,14 +522,24 @@ Tox provision unsatisfied: deps = toxdep1 toxdep2 - expected: | - python3dist(setuptools) >= 40.8 - python3dist(wheel) - python3dist(wheel) - python3dist(tox-current-env) >= 0.0.6 - python3dist(tox) >= 3.999 - python3dist(setuptools) > 40.0 - python3dist(wheel) > 2.0 + expected: + - | # tox 3 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(tox) >= 3.999 + python3dist(setuptools) > 40.0 + python3dist(wheel) > 2.0 + - | # tox 4 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(tox) >= 3.999 + python3dist(setuptools) > 40.0 + python3dist(wheel) > 2.0 + python3dist(tox) >= 3.999 result: 0 Tox provision satisfied: @@ -530,16 +566,27 @@ Tox provision satisfied: deps = toxdep1 toxdep2 - expected: | - python3dist(setuptools) >= 40.8 - python3dist(wheel) - python3dist(wheel) - python3dist(tox-current-env) >= 0.0.6 - python3dist(tox) >= 3.5 - python3dist(setuptools) > 40.0 - python3dist(toxdep1) - python3dist(toxdep2) - python3dist(inst) + expected: + - | # tox 3 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(tox) >= 3.5 + python3dist(setuptools) > 40.0 + python3dist(toxdep1) + python3dist(toxdep2) + python3dist(inst) + - | # tox 4 + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + python3dist(tox-current-env) >= 0.0.6 + python3dist(setuptools) > 40.0 + python3dist(tox) >= 3.5 + python3dist(toxdep1) + python3dist(toxdep2) + python3dist(inst) result: 0 Default build system, unmet deps in requirements file: @@ -771,3 +818,21 @@ Pre-releases are accepted: python3dist(wheel) stderr_contains: "Requirement satisfied: cffi" result: 0 + + +Wrapped subprocess prints to stdout from setup.py: + installed: + setuptools: 50 + wheel: 1 + include_runtime: false + setup.py: | + import os + os.system('echo LEAK?') + from setuptools import setup + setup(name='test', version='0.1') + expected: | + python3dist(setuptools) >= 40.8 + python3dist(wheel) + python3dist(wheel) + stderr_contains: "HOOK STDOUT: LEAK?" + result: 0 diff --git a/SOURCES/pyproject_requirements_txt.py b/SOURCES/pyproject_requirements_txt.py index d2389e8..5ff1f26 100644 --- a/SOURCES/pyproject_requirements_txt.py +++ b/SOURCES/pyproject_requirements_txt.py @@ -98,6 +98,4 @@ def expand_env_vars(lines): return match['var'] return value for line in lines: - if match := ENV_VAR_RE.search(line): - var = match['var'] yield ENV_VAR_RE.sub(repl, line) diff --git a/SOURCES/pyproject_save_files.py b/SOURCES/pyproject_save_files.py index 18925f1..00d706d 100644 --- a/SOURCES/pyproject_save_files.py +++ b/SOURCES/pyproject_save_files.py @@ -154,8 +154,8 @@ def add_lang_to_module(paths, module_name, path): Returns True if the language code detection was successful """ for i, parent in enumerate(path.parents): - if i > 0 and parent.name == 'locale': - lang_country_code = path.parents[i-1].name + if parent.name == 'LC_MESSAGES': + lang_country_code = path.parents[i+1].name break else: return False @@ -286,6 +286,36 @@ def module_names_from_path(path): return {'.'.join(parts[:x+1]) for x in range(len(parts))} +def is_license_file(path, license_files, license_directories): + """ + Check if the given BuildrootPath path matches any of the "License-File" entries. + The path is considered matched when resolved from any of the license_directories + matches string-wise what is stored in any "License-File" entry (license_files). + + Examples: + >>> site_packages = BuildrootPath('/usr/lib/python3.12/site-packages') + >>> distinfo = site_packages / 'foo-1.0.dist-info' + >>> license_directories = [distinfo / 'licenses', distinfo] + >>> license_files = ['LICENSE.txt', 'AUTHORS.md'] + >>> is_license_file(distinfo / 'AUTHORS.md', license_files, license_directories) + True + >>> is_license_file(distinfo / 'licenses/LICENSE.txt', license_files, license_directories) + True + >>> # we don't match based on directory only + >>> is_license_file(distinfo / 'licenses/COPYING', license_files, license_directories) + False + >>> is_license_file(site_packages / 'foo/LICENSE.txt', license_files, license_directories) + False + """ + if not license_files or not license_directories: + return False + for license_dir in license_directories: + if (path.is_relative_to(license_dir) and + str(path.relative_to(license_dir)) in license_files): + return True + return False + + def classify_paths( record_path, parsed_record_content, metadata, sitedirs, python_version, prefix ): @@ -311,10 +341,17 @@ def classify_paths( "other": {"files": []}, # regular %file entries we could not parse :( } + license_files = metadata.get_all('License-File') + license_directory = distinfo / 'licenses' # See PEP 369 "Root License Directory" + # setuptools was the first known build backend to implement License-File. + # Unfortunately they don't put licenses to the license directory (yet): + # https://github.com/pypa/setuptools/issues/3596 + # Hence, we check licenses in both licenses and dist-info + license_directories = (license_directory, distinfo) + # In RECORDs generated by pip, there are no directories, only files. # The example RECORD from PEP 376 does not contain directories either. # Hence, we'll only assume files, but TODO get it officially documented. - license_files = metadata.get_all('License-File') for path in parsed_record_content: if path.suffix == ".pyc": # we handle bytecode separately @@ -325,7 +362,7 @@ def classify_paths( # RECORD and REQUESTED files are removed in %pyproject_install # See PEP 627 continue - if license_files and str(path.relative_to(distinfo)) in license_files: + if is_license_file(path, license_files, license_directories): paths["metadata"]["licenses"].append(path) else: paths["metadata"]["files"].append(path) @@ -499,6 +536,50 @@ def generate_file_list(paths_dict, module_globs, include_others=False): return sorted(files) +def generate_module_list(paths_dict, module_globs): + """ + This function takes the paths_dict created by the classify_paths() function and + reads the modules names from it. + It filters those whose top-level module names match any of the provided module_globs. + + Returns list with matching qualified module names. + + Examples: + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'foo'}) + ['foo', 'foo.bar'] + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'*foo'}) + ['foo', 'foo.bar'] + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'foo', 'baz'}) + ['baz', 'foo', 'foo.bar'] + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'*'}) + ['baz', 'foo', 'foo.bar'] + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'bar'}) + [] + + Submodules aren't discovered: + + >>> generate_module_list({'module_names': {'foo', 'foo.bar', 'baz'}}, {'*bar'}) + [] + """ + + module_names = paths_dict['module_names'] + filtered_module_names = set() + + for glob in module_globs: + for name in module_names: + # Match the top-level part of the qualified name, eg. 'foo.bar.baz' -> 'foo' + top_level_name = name.split('.')[0] + if fnmatch.fnmatchcase(top_level_name, glob): + filtered_module_names.add(name) + + return sorted(filtered_module_names) + + def parse_varargs(varargs): """ Parse varargs from the %pyproject_save_files macro @@ -627,7 +708,7 @@ def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_versio parsed_records = load_parsed_record(pyproject_record) final_file_list = [] - all_module_names = set() + final_module_list = [] for record_path, files in parsed_records.items(): metadata = dist_metadata(buildroot, record_path) @@ -638,12 +719,11 @@ def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_versio final_file_list.extend( generate_file_list(paths_dict, globs, include_auto) ) - all_module_names.update(paths_dict["module_names"]) + final_module_list.extend( + generate_module_list(paths_dict, globs) + ) - # Sort values, so they are always checked in the same order - all_module_names = sorted(all_module_names) - - return final_file_list, all_module_names + return final_file_list, final_module_list def main(cli_args): @@ -662,17 +742,31 @@ def main(cli_args): def argparser(): - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser( + description="Create %{pyproject_files} for a Python project.", + prog="%pyproject_save_files", + add_help=False, + # custom usage to add +auto + usage="%(prog)s MODULE_GLOB [MODULE_GLOB ...] [+auto]", + ) + parser.add_argument( + '--help', action='help', + default=argparse.SUPPRESS, + help=argparse.SUPPRESS, + ) r = parser.add_argument_group("required arguments") - r.add_argument("--output-files", type=PosixPath, required=True) - r.add_argument("--output-modules", type=PosixPath, required=True) - r.add_argument("--buildroot", type=PosixPath, required=True) - r.add_argument("--sitelib", type=BuildrootPath, required=True) - r.add_argument("--sitearch", type=BuildrootPath, required=True) - r.add_argument("--python-version", type=str, required=True) - r.add_argument("--pyproject-record", type=PosixPath, required=True) - r.add_argument("--prefix", type=PosixPath, required=True) - parser.add_argument("varargs", nargs="+") + r.add_argument("--output-files", type=PosixPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--output-modules", type=PosixPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--buildroot", type=PosixPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--sitelib", type=BuildrootPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--sitearch", type=BuildrootPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--python-version", type=str, required=True, help=argparse.SUPPRESS) + r.add_argument("--pyproject-record", type=PosixPath, required=True, help=argparse.SUPPRESS) + r.add_argument("--prefix", type=PosixPath, required=True, help=argparse.SUPPRESS) + parser.add_argument( + "varargs", nargs="+", metavar="MODULE_GLOB", + help="Shell-like glob matching top-level module names to save into %%{pyproject_files}", + ) return parser diff --git a/SOURCES/pyproject_save_files_test_data.yaml b/SOURCES/pyproject_save_files_test_data.yaml index c6d2291..8f6775d 100644 --- a/SOURCES/pyproject_save_files_test_data.yaml +++ b/SOURCES/pyproject_save_files_test_data.yaml @@ -230,6 +230,28 @@ classified: - /usr/lib/python3.7/site-packages/ipykernel-5.2.1.dist-info/COPYING.md - /usr/lib/python3.7/site-packages/ipykernel-5.2.1.dist-info/INSTALLER licenses: [] + lang: + ipykernel: + fr: + - /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/nbjs.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/nbui.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/notebook.mo + ja: + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/nbjs.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/nbui.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/notebook.mo + nl: + - /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/nbjs.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/nbui.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/notebook.mo + ru: + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/nbjs.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/nbui.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/notebook.mo + zh: + - /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/nbjs.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/nbui.mo + - /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/notebook.mo modules: ipykernel: - files: @@ -279,6 +301,8 @@ classified: - /usr/lib/python3.7/site-packages/ipykernel/gui/gtk3embed.py - /usr/lib/python3.7/site-packages/ipykernel/gui/gtkembed.py - /usr/lib/python3.7/site-packages/ipykernel/heartbeat.py + - /usr/lib/python3.7/site-packages/ipykernel/i18n/__init__.py + - /usr/lib/python3.7/site-packages/ipykernel/i18n/__pycache__/__init__.cpython-37{,.opt-?}.pyc - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__init__.py - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__/__init__.cpython-37{,.opt-?}.pyc - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__/blocking.cpython-37{,.opt-?}.pyc @@ -362,6 +386,18 @@ classified: - /usr/lib/python3.7/site-packages/ipykernel/comm/__pycache__ - /usr/lib/python3.7/site-packages/ipykernel/gui - /usr/lib/python3.7/site-packages/ipykernel/gui/__pycache__ + - /usr/lib/python3.7/site-packages/ipykernel/i18n + - /usr/lib/python3.7/site-packages/ipykernel/i18n/__pycache__ + - /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR + - /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES + - /usr/lib/python3.7/site-packages/ipykernel/i18n/nl + - /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU + - /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES + - /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN + - /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES - /usr/lib/python3.7/site-packages/ipykernel/inprocess - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__ - /usr/lib/python3.7/site-packages/ipykernel/inprocess/tests @@ -7559,6 +7595,18 @@ dumped: - '%dir /usr/lib/python3.7/site-packages/ipykernel/comm/__pycache__' - '%dir /usr/lib/python3.7/site-packages/ipykernel/gui' - '%dir /usr/lib/python3.7/site-packages/ipykernel/gui/__pycache__' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/__pycache__' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/nl' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN' + - '%dir /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES' - '%dir /usr/lib/python3.7/site-packages/ipykernel/inprocess' - '%dir /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__' - '%dir /usr/lib/python3.7/site-packages/ipykernel/inprocess/tests' @@ -7568,6 +7616,21 @@ dumped: - '%dir /usr/lib/python3.7/site-packages/ipykernel/resources' - '%dir /usr/lib/python3.7/site-packages/ipykernel/tests' - '%dir /usr/lib/python3.7/site-packages/ipykernel/tests/__pycache__' + - '%lang(fr) /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/nbjs.mo' + - '%lang(fr) /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/nbui.mo' + - '%lang(fr) /usr/lib/python3.7/site-packages/ipykernel/i18n/fr_FR/LC_MESSAGES/notebook.mo' + - '%lang(ja) /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/nbjs.mo' + - '%lang(ja) /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/nbui.mo' + - '%lang(ja) /usr/lib/python3.7/site-packages/ipykernel/i18n/ja_JP/LC_MESSAGES/notebook.mo' + - '%lang(nl) /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/nbjs.mo' + - '%lang(nl) /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/nbui.mo' + - '%lang(nl) /usr/lib/python3.7/site-packages/ipykernel/i18n/nl/LC_MESSAGES/notebook.mo' + - '%lang(ru) /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/nbjs.mo' + - '%lang(ru) /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/nbui.mo' + - '%lang(ru) /usr/lib/python3.7/site-packages/ipykernel/i18n/ru_RU/LC_MESSAGES/notebook.mo' + - '%lang(zh) /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/nbjs.mo' + - '%lang(zh) /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/nbui.mo' + - '%lang(zh) /usr/lib/python3.7/site-packages/ipykernel/i18n/zh_CN/LC_MESSAGES/notebook.mo' - /usr/lib/python3.7/site-packages/ipykernel-5.2.1.dist-info/COPYING.md - /usr/lib/python3.7/site-packages/ipykernel-5.2.1.dist-info/INSTALLER - /usr/lib/python3.7/site-packages/ipykernel-5.2.1.dist-info/METADATA @@ -7619,6 +7682,8 @@ dumped: - /usr/lib/python3.7/site-packages/ipykernel/gui/gtk3embed.py - /usr/lib/python3.7/site-packages/ipykernel/gui/gtkembed.py - /usr/lib/python3.7/site-packages/ipykernel/heartbeat.py + - /usr/lib/python3.7/site-packages/ipykernel/i18n/__init__.py + - /usr/lib/python3.7/site-packages/ipykernel/i18n/__pycache__/__init__.cpython-37{,.opt-?}.pyc - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__init__.py - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__/__init__.cpython-37{,.opt-?}.pyc - /usr/lib/python3.7/site-packages/ipykernel/inprocess/__pycache__/blocking.cpython-37{,.opt-?}.pyc @@ -7713,6 +7778,7 @@ dumped: - ipykernel.gui.gtk3embed - ipykernel.gui.gtkembed - ipykernel.heartbeat + - ipykernel.i18n - ipykernel.inprocess - ipykernel.inprocess.blocking - ipykernel.inprocess.channels @@ -7755,7 +7821,6 @@ dumped: - ipykernel.tests.utils - ipykernel.trio_runner - ipykernel.zmqshell - - ipykernel_launcher - - zope - zope - - '%dir /usr/lib/python3.7/site-packages/zope' @@ -15484,8 +15549,8 @@ metadata: content: | Name: Django Version: 3.0.7 - License-File: licenses/LICENSE - License-File: licenses/LICENSE.python + License-File: LICENSE + License-File: LICENSE.python Whatever: False data records: @@ -15674,6 +15739,24 @@ records: ipykernel/gui/gtk3embed.py,sha256=mjUXqAzPxF956OcmWdWzvU2VLJoZ4ZyXrqCImJcn_Ug,3222 ipykernel/gui/gtkembed.py,sha256=yYp-Npg8jPrfXiN6mrzFy8L6JS7JeBOHz5WxTxSdvMA,3131 ipykernel/heartbeat.py,sha256=ZwIsWYgvjZQgFLjw6PrD9GJnN9XO1CzafUc89DEiPaA,4194 + ipykernel/i18n/__init__.py,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/__pycache__/__init__.cpython-37.opt-1.pyc,, + ipykernel/i18n/__pycache__/__init__.cpython-37.pyc,, + ipykernel/i18n/fr_FR/LC_MESSAGES/nbjs.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/fr_FR/LC_MESSAGES/nbui.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/fr_FR/LC_MESSAGES/notebook.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ja_JP/LC_MESSAGES/nbjs.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ja_JP/LC_MESSAGES/nbui.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ja_JP/LC_MESSAGES/notebook.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/nl/LC_MESSAGES/nbjs.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/nl/LC_MESSAGES/nbui.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/nl/LC_MESSAGES/notebook.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ru_RU/LC_MESSAGES/nbjs.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ru_RU/LC_MESSAGES/nbui.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/ru_RU/LC_MESSAGES/notebook.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/zh_CN/LC_MESSAGES/nbjs.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/zh_CN/LC_MESSAGES/nbui.mo,sha256=0000000000000000000000000000000000000000000,10 + ipykernel/i18n/zh_CN/LC_MESSAGES/notebook.mo,sha256=0000000000000000000000000000000000000000000,10 ipykernel/inprocess/__init__.py,sha256=UrsfQEevAq5OZ3au4Fn9bu_7c6b_QqroRIE7vE4PB_o,211 ipykernel/inprocess/__pycache__/__init__.cpython-37.pyc,, ipykernel/inprocess/__pycache__/blocking.cpython-37.pyc,, diff --git a/SOURCES/test_pyproject_buildrequires.py b/SOURCES/test_pyproject_buildrequires.py index f9b2650..74f3ae8 100644 --- a/SOURCES/test_pyproject_buildrequires.py +++ b/SOURCES/test_pyproject_buildrequires.py @@ -71,7 +71,12 @@ def test_data(case_name, capfd, tmp_path, monkeypatch): out, err = capfd.readouterr() if 'expected' in case: - assert out == case['expected'] + expected = case['expected'] + if isinstance(expected, list): + # at least one of them needs to match + assert any(out == e for e in expected) + else: + assert out == expected # stderr_contains may be a string or list of strings stderr_contains = case.get('stderr_contains') diff --git a/SPECS/pyproject-rpm-macros.spec b/SPECS/pyproject-rpm-macros.spec index b2a1ef0..ed22fe8 100644 --- a/SPECS/pyproject-rpm-macros.spec +++ b/SPECS/pyproject-rpm-macros.spec @@ -12,11 +12,12 @@ License: MIT # Increment Y and reset Z when new macros or features are added # Increment Z when this is a bugfix or a cosmetic change # Dropping support for EOL Fedoras is *not* considered a breaking change -Version: 1.3.3 +Version: 1.6.2 Release: 1%{?dist} # Macro files Source001: macros.pyproject +Source002: macros.aaa-pyproject-srpm # Implementation files Source101: pyproject_buildrequires.py @@ -48,6 +49,7 @@ BuildArch: noarch %if %{with tests} BuildRequires: python3dist(pytest) +BuildRequires: python3dist(pytest-xdist) BuildRequires: python3dist(pyyaml) BuildRequires: python3dist(packaging) BuildRequires: python3dist(pip) @@ -64,6 +66,7 @@ BuildRequires: python3-rpm-macros Requires: python-rpm-macros Requires: python-srpm-macros Requires: python3-rpm-macros +Requires: (pyproject-srpm-macros = %{?epoch:%{epoch}:}%{version}-%{release} if pyproject-srpm-macros) # We use the following tools outside of coreutils Requires: /usr/bin/find @@ -84,6 +87,17 @@ These macros replace %%py3_build and %%py3_install, which only work with setup.py. +%package -n pyproject-srpm-macros +Summary: Minimal implementation of %%pyproject_buildrequires +Requires: (pyproject-rpm-macros = %{?epoch:%{epoch}:}%{version}-%{release} if pyproject-rpm-macros) + +%description -n pyproject-srpm-macros +This package contains a minimal implementation of %%pyproject_buildrequires. +When used in %%generate_buildrequires, it will generate BuildRequires +for pyproject-rpm-macros. When both packages are installed, the full version +takes precedence. + + %prep # Not strictly necessary but allows working on file names instead # of source numbers in install section @@ -97,6 +111,7 @@ cp -p %{sources} . mkdir -p %{buildroot}%{_rpmmacrodir} mkdir -p %{buildroot}%{_rpmconfigdir}/redhat install -pm 644 macros.pyproject %{buildroot}%{_rpmmacrodir}/ +install -pm 644 macros.aaa-pyproject-srpm %{buildroot}%{_rpmmacrodir}/ install -pm 644 pyproject_buildrequires.py %{buildroot}%{_rpmconfigdir}/redhat/ install -pm 644 pyproject_convert.py %{buildroot}%{_rpmconfigdir}/redhat/ install -pm 644 pyproject_save_files.py %{buildroot}%{_rpmconfigdir}/redhat/ @@ -108,7 +123,7 @@ install -pm 644 pyproject_wheel.py %{buildroot}%{_rpmconfigdir}/redhat/ %if %{with tests} %check export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856356 -%pytest -vv --doctest-modules +%pytest -vv --doctest-modules -n auto # brp-compress is provided as an argument to get the right directory macro expansion %{python3} compare_mandata.py -f %{_rpmconfigdir}/brp-compress @@ -128,7 +143,35 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856 %doc README.md %license LICENSE +%files -n pyproject-srpm-macros +%{_rpmmacrodir}/macros.aaa-pyproject-srpm +%license LICENSE + + %changelog +* Wed Feb 08 2023 Lumír Balhar - 1.6.2-1 +- Improve detection of lang files + +* Fri Feb 03 2023 Miro Hrončok - 1.6.1-1 +- %%pyproject_buildrequires: Avoid leaking stdout from subprocesses + +* Fri Jan 20 2023 Miro Hrončok - 1.6.0-1 +- Add pyproject-srpm-macros with a minimal %%pyproject_buildrequires macro + +* Fri Jan 13 2023 Miro Hrončok - 1.5.1-1 +- Adjusts %%pyproject_buildrequires tests for tox 4 + +* Mon Nov 28 2022 Miro Hrončok - 1.5.0-1 +- Use %%py3_test_envvars in %%tox when available + +* Mon Sep 19 2022 Python Maint - 1.4.0-1 +- %%pyproject_save_files: Support License-Files installed into the *Root License Directory* from PEP 369 +- %%pyproject_check_import: Import only the modules whose top-level names + match any of the globs provided to %%pyproject_save_files + +* Tue Aug 30 2022 Otto Liljalaakso - 1.3.4-1 +- Fix typo in internal function name + * Tue Aug 09 2022 Karolina Surma - 1.3.3-1 - Don't fail %%pyproject_save_files '*' if no modules are detected