public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
To: git-commits@fedoraproject.org
Subject: [rpms/pyproject-rpm-macros] rawhide: %pyproject_save_files: Implement support for multi-package specfiles
Date: Fri, 10 Jul 2026 10:49:50 GMT [thread overview]
Message-ID: <178368059077.1.267742575603383319.rpms-pyproject-rpm-macros-44cabdfd5727@fedoraproject.org> (raw)
A new commit has been pushed.
Repo : rpms/pyproject-rpm-macros
Branch : rawhide
Commit : 44cabdfd5727635e77b75ca331b98023f7674493
Author : Miro Hrončok <miro@hroncok.cz>
Date : 2026-07-09T12:18:58+02:00
Stats : +258/-55 in 6 file(s)
URL : https://src.fedoraproject.org/rpms/pyproject-rpm-macros/c/44cabdfd5727635e77b75ca331b98023f7674493?branch=rawhide
Log:
%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>.
---
diff --git a/README.md b/README.md
index 812f959..a654d83 100644
--- a/README.md
+++ b/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`
diff --git a/macros.pyproject b/macros.pyproject
index 4fcfde3..ce3065d 100644
--- a/macros.pyproject
+++ b/macros.pyproject
@@ -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 %{?**}\
diff --git a/pyproject-rpm-macros.spec b/pyproject-rpm-macros.spec
index 7c22dc4..58ce702 100644
--- a/pyproject-rpm-macros.spec
+++ b/pyproject-rpm-macros.spec
@@ -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
diff --git a/pyproject_save_files.py b/pyproject_save_files.py
index 46fbbda..f5df963 100644
--- a/pyproject_save_files.py
+++ b/pyproject_save_files.py
@@ -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():
@@ -898,6 +947,10 @@ def argparser():
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).",
)
diff --git a/test_pyproject_save_files.py b/test_pyproject_save_files.py
index 5ee5893..0486201 100755
--- a/test_pyproject_save_files.py
+++ b/test_pyproject_save_files.py
@@ -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()
diff --git a/tests/double-install.spec b/tests/double-install.spec
index 13fe33d..3bef819 100644
--- a/tests/double-install.spec
+++ b/tests/double-install.spec
@@ -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 -n python3-double-markupsafe -f %{pyproject_files -D markupsafe}
-%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-tldr -f %{pyproject_files --dist-name tldr}
reply other threads:[~2026-07-10 10:49 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178368059077.1.267742575603383319.rpms-pyproject-rpm-macros-44cabdfd5727@fedoraproject.org \
--to=git-commits@fedoraproject.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox