%pyproject_save_files: Implement support for multi-package specfiles
This adds a new -D/--dist-name option for: - %pyproject_save_files - %pyproject_files - %pyproject_check_import - %pyproject_extras_subpkg And allows the use of all of those in multi-package spec files. See README.md changes for usage. Assisted-By: Claude Opus 4.6 Many useful suggestions by Maxwell G <maxwell@gtmx.me> and Karolina Surma <ksurma@redhat.com>.
This commit is contained in:
parent
b433764db6
commit
44cabdfd57
58
README.md
58
README.md
@ -190,11 +190,14 @@ Is roughly equivalent to:
|
||||
|
||||
If you use `%pyproject_wheel` to build multiple wheels, for example like this:
|
||||
|
||||
%pyproject_wheel -d project_a
|
||||
%pyproject_wheel -d project_b
|
||||
%pyproject_wheel --directory directory1/
|
||||
%pyproject_wheel --directory directory2/
|
||||
|
||||
You still only call `%pyproject_install` once and it will install all such wheels.
|
||||
Support for `%pyproject_save_files` with multiple wheels is not implemented.
|
||||
To use `%pyproject_save_files` (and optionally `%pyproject_check_import`) with multiple wheels,
|
||||
use the `-D`/`--dist-name` option to select a specific package,
|
||||
for details, see *Generating the %files section*
|
||||
(and *Performing an import check on all importable modules*).
|
||||
|
||||
If you use `%pyproject_buildrequires -d ...` for multiple co-dependent packages
|
||||
in one spec file, it will create one or more build time dependencies
|
||||
@ -413,6 +416,17 @@ If you wish to rename, remove or otherwise change the installed files of a packa
|
||||
If possible, remove/rename such files in `%prep`.
|
||||
If not possible, avoid using `%pyproject_save_files` or edit/replace `%{pyproject_files}`.
|
||||
|
||||
When processing files from multiple Python packages
|
||||
(most likely as a result of multiple `%pyproject_wheel` invocations),
|
||||
use the `-D`/`--dist-name` option to select a specific package
|
||||
in `%pyproject_save_files` and `%{pyproject_files}`:
|
||||
|
||||
%pyproject_save_files --dist-name package_a module_glob_a...
|
||||
%pyproject_save_files --dist-name package_b module_glob_b...
|
||||
|
||||
%files -n python3-package-a -f %{pyproject_files -D package_a}
|
||||
%files -n python3-package-b -f %{pyproject_files -D package_b}
|
||||
|
||||
|
||||
Performing an import check on all importable modules
|
||||
----------------------------------------------------
|
||||
@ -460,6 +474,15 @@ The `%pyproject_check_import` macro also accepts positional arguments with
|
||||
additional qualified module names to check, useful for example if some modules are installed manually.
|
||||
Note that filtering by `-t`/`--top-level-only`/`-e`/`--exclude` also applies to the positional arguments.
|
||||
|
||||
When listing files from multiple Python packages via `%pyproject_save_files -D`/`--dist-name`,
|
||||
pass `-D`/`--dist-name` to `%pyproject_check_import` as well.
|
||||
|
||||
%pyproject_save_files --dist-name package_a module_glob_a...
|
||||
%pyproject_save_files --dist-name package_b module_glob_b...
|
||||
|
||||
%pyproject_check_import --dist-name package_a
|
||||
%pyproject_check_import --dist-name package_b
|
||||
|
||||
Another macro, `%_pyproject_check_import_allow_no_modules` allows to pass the import check,
|
||||
even if no Python modules are detected in the package.
|
||||
This may be a valid case for packages containing e.g. typing stubs.
|
||||
@ -496,6 +519,13 @@ These arguments are still required:
|
||||
* Positional arguments: the extra name(s).
|
||||
Multiple subpackages are generated when multiple names are provided.
|
||||
|
||||
When processing files from multiple Python packages
|
||||
(see *Generating the %files section* for details),
|
||||
pass `-D` to `%pyproject_extras_subpkg` as well:
|
||||
|
||||
%pyproject_extras_subpkg -n python3-package-a -D package_a extra1
|
||||
%pyproject_extras_subpkg -n python3-package-b -D package_b extra2
|
||||
|
||||
|
||||
Provisional: Declarative Buildsystem (RPM 4.20+)
|
||||
------------------------------------------------
|
||||
@ -603,19 +633,21 @@ Here is a complete reference of all options:
|
||||
|
||||
### `%pyproject_save_files`
|
||||
|
||||
| Short | Long | Description |
|
||||
|-------|-----------------------|-------------------------------------------------|
|
||||
| `-l` | `--assert-license` | Fail when no License-File (PEP 639) is found |
|
||||
| `-L` | `--no-assert-license` | Don't fail on missing License-File (default) |
|
||||
| `-M` | `--allow-no-modules` | Allow no module globs |
|
||||
| `-a` | `--auto` | Include non-module files (same as `+auto`) |
|
||||
| Short | Long | Description |
|
||||
|------------|------------------------|--------------------------------------------------------------|
|
||||
| `-l` | `--assert-license` | Fail when no License-File (PEP 639) is found |
|
||||
| `-L` | `--no-assert-license` | Don't fail on missing License-File (default) |
|
||||
| `-M` | `--allow-no-modules` | Allow no module globs |
|
||||
| `-D NAME` | `--dist-name NAME` | Save files for a specific distribution package (multi-wheel) |
|
||||
| `-a` | `--auto` | Include non-module files (same as `+auto`) |
|
||||
|
||||
### `%pyproject_check_import`
|
||||
|
||||
| Short | Long | Description |
|
||||
|-----------|--------------------|------------------------------------|
|
||||
| `-e GLOB` | `--exclude GLOB` | Exclude module names matching glob |
|
||||
| `-t` | `--top-level-only` | Only check top-level modules |
|
||||
| Short | Long | Description |
|
||||
|-----------|-----------------------|---------------------------------------------------|
|
||||
| `-e GLOB` | `--exclude GLOB` | Exclude module names matching glob |
|
||||
| `-D NAME` | `--dist-name NAME` | Check imports for a specific distribution package |
|
||||
| `-t` | `--top-level-only` | Only check top-level modules |
|
||||
|
||||
### `%tox`
|
||||
|
||||
|
||||
@ -20,9 +20,28 @@
|
||||
|
||||
# %%_pyproject_files_prefix defined in srpm macros
|
||||
|
||||
%pyproject_files %{_builddir}/%{_pyproject_files_prefix}-pyproject-files
|
||||
%_pyproject_modules %{_builddir}/%{_pyproject_files_prefix}-pyproject-modules
|
||||
%_pyproject_ghost_distinfo %{_builddir}/%{_pyproject_files_prefix}-pyproject-ghost-distinfo
|
||||
# PEP 503 name normalization: lowercase, one or more [-_.] → single -
|
||||
%__pyproject_normalize_name(-) %{lua:print((rpm.expand("%1"):lower():gsub("[%-_.]+", "-")))}
|
||||
|
||||
%_pyproject_files_base %{_builddir}/%{_pyproject_files_prefix}-pyproject-files
|
||||
%_pyproject_modules_base %{_builddir}/%{_pyproject_files_prefix}-pyproject-modules
|
||||
%_pyproject_ghost_distinfo_base %{_builddir}/%{_pyproject_files_prefix}-pyproject-ghost-distinfo
|
||||
|
||||
%pyproject_files(-) %{lua:
|
||||
require("fedora.rpm.pyproject_getopt").getopt({
|
||||
{short="D", long="dist-name", value=true},
|
||||
})}%{_pyproject_files_base}%{?__pyproject_opt_D:-%{__pyproject_normalize_name %{__pyproject_opt_D}}}
|
||||
|
||||
%_pyproject_modules(-) %{lua:
|
||||
require("fedora.rpm.pyproject_getopt").getopt({
|
||||
{short="D", long="dist-name", value=true},
|
||||
})}%{_pyproject_modules_base}%{?__pyproject_opt_D:-%{__pyproject_normalize_name %{__pyproject_opt_D}}}
|
||||
|
||||
%_pyproject_ghost_distinfo(-) %{lua:
|
||||
require("fedora.rpm.pyproject_getopt").getopt({
|
||||
{short="D", long="dist-name", value=true},
|
||||
})}%{_pyproject_ghost_distinfo_base}%{?__pyproject_opt_D:-%{__pyproject_normalize_name %{__pyproject_opt_D}}}
|
||||
|
||||
%_pyproject_record %{_builddir}/%{_pyproject_files_prefix}-pyproject-record
|
||||
%_pyproject_buildrequires %{_builddir}/%{_pyproject_files_prefix}-pyproject-buildrequires
|
||||
# %%_pyproject_dep_overrides defined in srpm macros
|
||||
@ -95,7 +114,7 @@ if [ -d %{buildroot}%{_bindir} ]; then
|
||||
%py3_shebang_fix %{buildroot}%{_bindir}/*
|
||||
rm -rfv %{buildroot}%{_bindir}/__pycache__
|
||||
fi
|
||||
rm -f %{_pyproject_ghost_distinfo}
|
||||
rm -f %{_pyproject_ghost_distinfo_base}
|
||||
site_dirs=()
|
||||
# Process %%{python3_sitelib} if exists
|
||||
if [ -d %{buildroot}%{python3_sitelib} ]; then
|
||||
@ -108,7 +127,12 @@ fi
|
||||
# Process all *.dist-info dirs in sitelib/sitearch
|
||||
for site_dir in ${site_dirs[@]}; do
|
||||
for distinfo in %{buildroot}$site_dir/*.dist-info; do
|
||||
echo "%ghost %dir ${distinfo#%{buildroot}}" >> %{_pyproject_ghost_distinfo}
|
||||
echo "%ghost %dir ${distinfo#%{buildroot}}" >> %{_pyproject_ghost_distinfo_base}
|
||||
_normalized=$(PYTHONPATH=%{_rpmconfigdir}/redhat \\
|
||||
%{__python3} -sBc \\
|
||||
"import sys; from pyproject_save_files import canonical_name_from_distinfo; print(canonical_name_from_distinfo(sys.argv[1]))" \\
|
||||
"$(basename ${distinfo})")
|
||||
echo "%ghost %dir ${distinfo#%{buildroot}}" >> "%{_pyproject_ghost_distinfo_base}-${_normalized}"
|
||||
sed -i 's/pip/rpm/' ${distinfo}/INSTALLER
|
||||
PYTHONPATH=%{_rpmconfigdir}/redhat \\
|
||||
%{__python3} -B %{_rpmconfigdir}/redhat/pyproject_preprocess_record.py \\
|
||||
@ -122,16 +146,17 @@ for site_dir in ${site_dirs[@]}; do
|
||||
fi
|
||||
done
|
||||
done
|
||||
lines=$(wc -l %{_pyproject_ghost_distinfo} | cut -f1 -d" ")
|
||||
lines=$(wc -l %{_pyproject_ghost_distinfo_base} | cut -f1 -d" ")
|
||||
if [ $lines -ne 1 ]; then
|
||||
echo -e "\\n\\nWARNING: %%%%pyproject_extras_subpkg won't work without explicit -i or -F, found $lines dist-info directories.\\n\\n" >&2
|
||||
rm %{_pyproject_ghost_distinfo} # any attempt to use this will fail
|
||||
echo -e "%%%%pyproject_extras_subpkg will require -D <name> or explicit -i/-F with multiple dist-info directories (found $lines)." >&2
|
||||
rm %{_pyproject_ghost_distinfo_base} # any attempt to use this will fail
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Note: the three times nested questionmarked -i -f -F pattern means: If none of those options was used -- in that case, we inject our own -f
|
||||
%pyproject_extras_subpkg(n:i:f:FaA) %{expand:%{?python_extras_subpkg:%{python_extras_subpkg%{!?-i:%{!?-f:%{!?-F: -f %{_pyproject_ghost_distinfo}}}} %**}}}
|
||||
# -D selects the per-package ghost distinfo file for multi-wheel installs
|
||||
%pyproject_extras_subpkg(n:i:f:FaAD:) %{expand:%{?python_extras_subpkg:%{python_extras_subpkg%{!?-i:%{!?-f:%{!?-F: -f %{_pyproject_ghost_distinfo %{?-D}}}}} %{?-n} %{?-i} %{?-f} %{?-F} %{?-a} %{?-A} %*}}}
|
||||
|
||||
|
||||
# Escaping shell-globs, percentage signs and spaces was reworked in RPM 4.19+
|
||||
@ -144,12 +169,13 @@ fi
|
||||
{short="L", long="no-assert-license"},
|
||||
{short="M", long="allow-no-modules"},
|
||||
{short="a", long="auto"},
|
||||
{short="D", long="dist-name", value=true},
|
||||
})}\
|
||||
%{expand:\\\
|
||||
%{expr:v"0%{?rpmversion}" >= v"4.18.90" ? "RPM_FILES_ESCAPE=4.19" : "RPM_FILES_ESCAPE=4.18" } \\
|
||||
%{__python3} %{_rpmconfigdir}/redhat/pyproject_save_files.py \\
|
||||
--output-files "%{pyproject_files}" \\
|
||||
--output-modules "%{_pyproject_modules}" \\
|
||||
--output-files "%{pyproject_files %{?__pyproject_opt_D:-D %{__pyproject_opt_D}}}" \\
|
||||
--output-modules "%{_pyproject_modules %{?__pyproject_opt_D:-D %{__pyproject_opt_D}}}" \\
|
||||
--buildroot "%{buildroot}" \\
|
||||
--sitelib "%{python3_sitelib}" \\
|
||||
--sitearch "%{python3_sitearch}" \\
|
||||
@ -162,23 +188,32 @@ fi
|
||||
%pyproject_check_import(-) \
|
||||
%{lua:require("fedora.rpm.pyproject_getopt").getopt({
|
||||
{short="e", long="exclude", value=true, separator=" -e "},
|
||||
{short="D", long="dist-name", value=true},
|
||||
{short="t", long="top-level-only"},
|
||||
})}\
|
||||
%{expand:\\\
|
||||
if [ ! -f "%{_pyproject_modules}" ]; then
|
||||
echo 'ERROR: %%%%pyproject_check_import only works when %%%%pyproject_save_files is used' >&2
|
||||
if [ ! -f "%{_pyproject_modules %{?__pyproject_opt_D:-D %{__pyproject_opt_D}}}" ]; then
|
||||
_modules_glob=( %{_pyproject_modules_base}-* )
|
||||
if [ -e "${_modules_glob[0]}" ]; then
|
||||
_available=$(printf '%%s\n' "${_modules_glob[@]}" | sed 's|.*-pyproject-modules-||' | tr '\n' ' ')
|
||||
echo "ERROR: %%%%pyproject_check_import requires -D <name> when %%%%pyproject_save_files was called with -D" >&2
|
||||
echo "Available -D values: ${_available}" >&2
|
||||
else
|
||||
echo 'ERROR: %%%%pyproject_check_import only works when %%%%pyproject_save_files is used' >&2
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
%py3_check_import -f "%{_pyproject_modules}" %{?__pyproject_opt_e:-e %{__pyproject_opt_e}} %{?__pyproject_opt_t:-t} %{?__pyproject_positional_args}
|
||||
%py3_check_import -f "%{_pyproject_modules %{?__pyproject_opt_D:-D %{__pyproject_opt_D}}}" %{?__pyproject_opt_e:-e %{__pyproject_opt_e}} %{?__pyproject_opt_t:-t} %{?__pyproject_positional_args}
|
||||
}
|
||||
|
||||
|
||||
%_pyproject_check_import_allow_no_modules(-) \
|
||||
%{lua:require("fedora.rpm.pyproject_getopt").getopt({
|
||||
{short="e", long="exclude", value=true, separator=" -e "},
|
||||
{short="D", long="dist-name", value=true},
|
||||
{short="t", long="top-level-only"},
|
||||
})}\
|
||||
if [ -z "$(cat %{_pyproject_modules})" ]; then\
|
||||
if [ -z "$(cat %{_pyproject_modules %{?__pyproject_opt_D:-D %{__pyproject_opt_D}}})" ]; then\
|
||||
echo "No modules to check found, exiting check"\
|
||||
else\
|
||||
%pyproject_check_import %{?**}\
|
||||
|
||||
@ -14,7 +14,7 @@ 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.22.2
|
||||
Version: 1.23.0
|
||||
Release: 1%{?dist}
|
||||
|
||||
# Macro files
|
||||
@ -179,6 +179,9 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856
|
||||
|
||||
|
||||
%changelog
|
||||
* Mon May 18 2026 Miro Hrončok <mhroncok@redhat.com> - 1.23.0-1
|
||||
- %%pyproject_save_files: Implement support for multi-package specfiles
|
||||
|
||||
* Thu May 14 2026 Miro Hrončok <mhroncok@redhat.com> - 1.22.2-1
|
||||
- %%pyproject_buildrequires: Fix "Requirement satisfied/not satisfied" messages to show overridden constraints from %%pyproject_patch_dependency
|
||||
- %%pyproject_buildrequires: Stop using deprecated argparse.FileType
|
||||
|
||||
@ -8,6 +8,7 @@ from collections import defaultdict
|
||||
from keyword import iskeyword
|
||||
from pathlib import PosixPath, PurePosixPath
|
||||
from importlib.metadata import Distribution
|
||||
from packaging.utils import canonicalize_name
|
||||
|
||||
|
||||
# From RPM's build/files.c strtokWithQuotes delim argument
|
||||
@ -45,6 +46,24 @@ MANDIRS = [
|
||||
]
|
||||
|
||||
|
||||
def canonical_name_from_distinfo(distinfo_dir_name):
|
||||
"""Extract the canonicalized distribution name from a .dist-info directory name.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> canonical_name_from_distinfo('MarkupSafe-2.0.1.dist-info')
|
||||
'markupsafe'
|
||||
|
||||
>>> canonical_name_from_distinfo('tldr-0.5.dist-info')
|
||||
'tldr'
|
||||
|
||||
>>> canonical_name_from_distinfo('My.Package_name-1.0.dist-info')
|
||||
'my-package-name'
|
||||
"""
|
||||
raw = distinfo_dir_name.removesuffix('.dist-info').rsplit('-', 1)[0]
|
||||
return canonicalize_name(raw)
|
||||
|
||||
|
||||
class BuildrootPath(PurePosixPath):
|
||||
"""
|
||||
This path represents a path in a buildroot.
|
||||
@ -760,15 +779,31 @@ def parse_varargs(varargs):
|
||||
return globs, include_auto
|
||||
|
||||
|
||||
def load_parsed_record(pyproject_record):
|
||||
def load_parsed_record(pyproject_record, dist_name=None):
|
||||
parsed_record = {}
|
||||
with open(pyproject_record) as pyproject_record_file:
|
||||
content = json.load(pyproject_record_file)
|
||||
|
||||
if len(content) > 1:
|
||||
raise FileExistsError("%pyproject_install has found more than one *.dist-info/RECORD file. "
|
||||
"Currently, %pyproject_save_files supports only one wheel → one file list mapping. "
|
||||
"Feel free to open a bugzilla for pyproject-rpm-macros and describe your usecase.")
|
||||
# Map each record path to its canonical dist name
|
||||
dist_names = {rp: canonical_name_from_distinfo(BuildrootPath(rp).parent.name)
|
||||
for rp in content}
|
||||
available = sorted(dist_names.values()) # used in error messages
|
||||
|
||||
if dist_name:
|
||||
normalized_target = canonicalize_name(dist_name)
|
||||
content = {rp: files for rp, files in content.items()
|
||||
if dist_names[rp] == normalized_target}
|
||||
if not content:
|
||||
raise ValueError(
|
||||
f"No dist-info found matching dist name '{dist_name}'. "
|
||||
f"Available: {', '.join(available)}"
|
||||
)
|
||||
elif len(content) > 1:
|
||||
raise ValueError(
|
||||
"%pyproject_install has found more than one *.dist-info/RECORD file. "
|
||||
"Use %pyproject_save_files -D <name> to select a package. "
|
||||
f"Available: {', '.join(available)}"
|
||||
)
|
||||
|
||||
# Redefine strings stored in JSON to BuildRootPaths
|
||||
for record_path, files in content.items():
|
||||
@ -786,12 +821,12 @@ def dist_metadata(buildroot, record_path):
|
||||
return dist.metadata
|
||||
|
||||
|
||||
def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_version, pyproject_record, prefix, assert_license, allow_no_modules, auto, varargs):
|
||||
def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_version, pyproject_record, prefix, assert_license, allow_no_modules, auto, varargs, dist_name=None):
|
||||
"""
|
||||
Takes arguments from the %{pyproject_save_files} macro
|
||||
|
||||
Returns tuple: list of paths for the %files section and list of module names
|
||||
for the %check section
|
||||
Returns tuple: list of paths for the %files section, list of module names
|
||||
for the %check section, and list of record paths (BuildrootPaths)
|
||||
|
||||
Raises ValueError when assert_license is true and no License-File (PEP 639)
|
||||
is found.
|
||||
@ -811,7 +846,7 @@ def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_versio
|
||||
raise ValueError(
|
||||
"%pyproject_save_files -M cannot be used together with module globs."
|
||||
)
|
||||
parsed_records = load_parsed_record(pyproject_record)
|
||||
parsed_records = load_parsed_record(pyproject_record, dist_name)
|
||||
|
||||
final_file_list = []
|
||||
final_module_list = []
|
||||
@ -842,11 +877,11 @@ def pyproject_save_files_and_modules(buildroot, sitelib, sitearch, python_versio
|
||||
"and include the %license file in %files manually."
|
||||
)
|
||||
|
||||
return final_file_list, final_module_list
|
||||
return final_file_list, final_module_list, list(parsed_records)
|
||||
|
||||
|
||||
def main(cli_args):
|
||||
file_section, module_names = pyproject_save_files_and_modules(
|
||||
file_section, module_names, record_paths = pyproject_save_files_and_modules(
|
||||
cli_args.buildroot,
|
||||
cli_args.sitelib,
|
||||
cli_args.sitearch,
|
||||
@ -857,10 +892,24 @@ def main(cli_args):
|
||||
cli_args.allow_no_modules,
|
||||
cli_args.auto,
|
||||
cli_args.varargs,
|
||||
cli_args.dist_name,
|
||||
)
|
||||
|
||||
cli_args.output_files.write_text("\n".join(file_section) + "\n", encoding="utf-8")
|
||||
cli_args.output_modules.write_text("\n".join(module_names) + "\n", encoding="utf-8")
|
||||
files_text = "\n".join(file_section) + "\n"
|
||||
modules_text = "\n".join(module_names) + "\n"
|
||||
|
||||
cli_args.output_files.write_text(files_text, encoding="utf-8")
|
||||
cli_args.output_modules.write_text(modules_text, encoding="utf-8")
|
||||
|
||||
# When no -D was given, also write to the named path (derived from dist-info)
|
||||
# so that %{pyproject_files -D name} works even without -D in %pyproject_save_files
|
||||
if not cli_args.dist_name:
|
||||
record_path = record_paths[0]
|
||||
normalized = canonical_name_from_distinfo(record_path.parent.name)
|
||||
named_files = PosixPath(f"{cli_args.output_files}-{normalized}")
|
||||
named_modules = PosixPath(f"{cli_args.output_modules}-{normalized}")
|
||||
named_files.write_text(files_text, encoding="utf-8")
|
||||
named_modules.write_text(modules_text, encoding="utf-8")
|
||||
|
||||
|
||||
def argparser():
|
||||
@ -897,6 +946,10 @@ def argparser():
|
||||
"-M", "--allow-no-modules", action="store_true", default=False,
|
||||
help="Don't fail when no globs are provided, only include non-modules data in the generated filelist.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-D", "--dist-name", type=str, default=None,
|
||||
help="Save files for a specific distribution package (for multi-wheel installs).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-a", "--auto", action="store_true", default=False,
|
||||
help="Include all non-module, non-sitelib files (same as +auto).",
|
||||
|
||||
@ -6,7 +6,7 @@ from pprint import pprint
|
||||
|
||||
from pyproject_preprocess_record import parse_record, read_record, save_parsed_record
|
||||
|
||||
from pyproject_save_files import argparser, generate_file_list, BuildrootPath
|
||||
from pyproject_save_files import argparser, canonical_name_from_distinfo, generate_file_list, BuildrootPath
|
||||
from pyproject_save_files import main as save_files_main
|
||||
from pyproject_save_files import module_names_from_path
|
||||
|
||||
@ -242,12 +242,12 @@ def test_cli_no_pyproject_record(tmp_path, pyproject_record):
|
||||
def test_cli_too_many_RECORDS(tldr_root, output_files, output_modules, pyproject_record):
|
||||
# Two calls to simulate how %pyproject_install process more than one RECORD file
|
||||
prepare_pyproject_record(tldr_root,
|
||||
content=("foo/bar/dist-info/RECORD", []))
|
||||
content=("foo/bar-1.0.dist-info/RECORD", []))
|
||||
prepare_pyproject_record(tldr_root,
|
||||
content=("foo/baz/dist-info/RECORD", []))
|
||||
content=("foo/baz-2.0.dist-info/RECORD", []))
|
||||
cli_args = argparser().parse_args([*default_options(output_files, output_modules, tldr_root, pyproject_record), "tldr*"])
|
||||
|
||||
with pytest.raises(FileExistsError):
|
||||
with pytest.raises(ValueError, match="Use %pyproject_save_files -D"):
|
||||
save_files_main(cli_args)
|
||||
|
||||
|
||||
@ -277,3 +277,66 @@ def test_cli_bad_namespace(tldr_root, output_files, output_modules, pyproject_re
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
save_files_main(cli_args)
|
||||
|
||||
|
||||
def test_canonical_name_from_distinfo():
|
||||
assert canonical_name_from_distinfo("MarkupSafe-2.0.1.dist-info") == "markupsafe"
|
||||
assert canonical_name_from_distinfo("tldr-0.5.dist-info") == "tldr"
|
||||
assert canonical_name_from_distinfo("my_package-1.0.0.dist-info") == "my-package"
|
||||
assert canonical_name_from_distinfo("zope.event-1.0.dist-info") == "zope-event"
|
||||
|
||||
|
||||
def test_cli_with_dist_name(tldr_root, pyproject_record):
|
||||
output_files = tldr_root / "files"
|
||||
output_modules = tldr_root / "modules"
|
||||
cli_args = argparser().parse_args([
|
||||
*default_options(output_files, output_modules, tldr_root, pyproject_record),
|
||||
"-D", "tldr",
|
||||
"tldr*",
|
||||
])
|
||||
save_files_main(cli_args)
|
||||
assert output_files.exists()
|
||||
assert "tldr" in output_files.read_text()
|
||||
assert output_modules.exists()
|
||||
|
||||
|
||||
def test_cli_with_dist_name_case_insensitive(tldr_root, pyproject_record):
|
||||
output_files = tldr_root / "files"
|
||||
output_modules = tldr_root / "modules"
|
||||
cli_args = argparser().parse_args([
|
||||
*default_options(output_files, output_modules, tldr_root, pyproject_record),
|
||||
"-D", "TLDR",
|
||||
"tldr*",
|
||||
])
|
||||
save_files_main(cli_args)
|
||||
assert output_files.exists()
|
||||
|
||||
|
||||
def test_cli_dist_name_not_found(tldr_root, pyproject_record):
|
||||
output_files = tldr_root / "files"
|
||||
output_modules = tldr_root / "modules"
|
||||
cli_args = argparser().parse_args([
|
||||
*default_options(output_files, output_modules, tldr_root, pyproject_record),
|
||||
"-D", "nonexistent",
|
||||
"tldr*",
|
||||
])
|
||||
with pytest.raises(ValueError, match="No dist-info found matching"):
|
||||
save_files_main(cli_args)
|
||||
|
||||
|
||||
def test_cli_single_record_named_output(tldr_root, pyproject_record):
|
||||
output_files = tldr_root / "files"
|
||||
output_modules = tldr_root / "modules"
|
||||
cli_args = argparser().parse_args([
|
||||
*default_options(output_files, output_modules, tldr_root, pyproject_record),
|
||||
"tldr*",
|
||||
])
|
||||
save_files_main(cli_args)
|
||||
# Without -D, base output should exist
|
||||
assert output_files.exists()
|
||||
# Named output (derived from dist-info) should also exist
|
||||
named_files = tldr_root / "files-tldr"
|
||||
named_modules = tldr_root / "modules-tldr"
|
||||
assert named_files.exists()
|
||||
assert named_modules.exists()
|
||||
assert output_files.read_text() == named_files.read_text()
|
||||
|
||||
@ -14,7 +14,23 @@ BuildRequires: python3-devel
|
||||
%description
|
||||
This package tests that we can build and install 2 wheels at once.
|
||||
One of them is "noarch" and one has an extension module.
|
||||
This also tests the -d option for %%pyproject_buildrequires/%%pyproject_wheel.
|
||||
This also tests the -d option for %%pyproject_buildrequires/%%pyproject_wheel
|
||||
and the -D option for %%pyproject_save_files/%%pyproject_check_import.
|
||||
|
||||
|
||||
%package -n python3-double-markupsafe
|
||||
Summary: MarkupSafe from double-install test
|
||||
|
||||
%description -n python3-double-markupsafe
|
||||
MarkupSafe subpackage.
|
||||
|
||||
|
||||
%package -n python3-double-tldr
|
||||
Summary: tldr from double-install test
|
||||
BuildArch: noarch
|
||||
|
||||
%description -n python3-double-tldr
|
||||
tldr subpackage.
|
||||
|
||||
|
||||
%prep
|
||||
@ -25,7 +41,7 @@ tar xf %{SOURCE2}
|
||||
|
||||
%generate_buildrequires
|
||||
%pyproject_buildrequires --no-runtime --directory markupsafe-%{markupsafe_version}
|
||||
%pyproject_buildrequires --no-runtime --directory tldr-%{tldr_version}
|
||||
%pyproject_buildrequires --directory tldr-%{tldr_version}
|
||||
|
||||
|
||||
%build
|
||||
@ -39,7 +55,8 @@ set -o pipefail
|
||||
# This should install both the wheels:
|
||||
%pyproject_install
|
||||
) 2>&1 | tee install.log
|
||||
#pyproject_save_files is not possible with 2 dist-infos
|
||||
%pyproject_save_files --dist-name markupsafe markupsafe
|
||||
%pyproject_save_files -D tldr tldr +auto
|
||||
|
||||
|
||||
%check
|
||||
@ -56,11 +73,11 @@ cd ..
|
||||
# Internal regression check for %%pyproject_install with multiple wheels
|
||||
grep 'binary operator expected' install.log && exit 1 || true
|
||||
grep 'too many arguments' install.log && exit 1 || true
|
||||
# Check that %%pyproject_check_import -D works
|
||||
%pyproject_check_import -D markupsafe
|
||||
%pyproject_check_import -D tldr
|
||||
|
||||
|
||||
%files
|
||||
%{_bindir}/tldr*
|
||||
%pycached %{python3_sitelib}/tldr.py
|
||||
%{python3_sitelib}/tldr-%{tldr_version}.dist-info/
|
||||
%{python3_sitearch}/[Mm]arkup[Ss]afe-%{markupsafe_version}.dist-info/
|
||||
%{python3_sitearch}/markupsafe/
|
||||
%files -n python3-double-markupsafe -f %{pyproject_files -D markupsafe}
|
||||
|
||||
%files -n python3-double-tldr -f %{pyproject_files --dist-name tldr}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user