diff --git a/macros.python-srpm b/macros.python-srpm index d5cd4e5..7ad62d1 100644 --- a/macros.python-srpm +++ b/macros.python-srpm @@ -230,7 +230,7 @@ end } -%python_extras_subpkg(n:i:f:FaAv:) %{expand:%{lua: +%python_extras_subpkg(n:i:f:FaA) %{expand:%{lua: local option_n = '-n (name of the base package)' local option_i = '-i (buildroot path to metadata)' local option_f = '-f (builddir path to a filelist)' @@ -243,7 +243,6 @@ local value_F = rpm.expand('%{-F}') local value_a = rpm.expand('%{-a}') local value_A = rpm.expand('%{-A}') - local value_v = rpm.expand('%{-v}') local args = rpm.expand('%{*}') if value_n == '' then rpm.expand('%{error:%%%0: missing option ' .. option_n .. '}') @@ -266,8 +265,7 @@ if args == '' then rpm.expand('%{error:%%%0 requires at least one argument with "extras" name}') end - local verrel = rpm.expand('%{?-v*}%{!?-v:%{version}-%{release}}') - local requires = 'Requires: ' .. value_n .. ' = %{?epoch:%{epoch}:}' .. verrel + local requires = 'Requires: ' .. value_n .. ' = %{?epoch:%{epoch}:}%{version}-%{release}' for extras in args:gmatch('[^%s,]+') do local rpmname = value_n .. '+' .. extras local pkgdef = '%package -n ' .. rpmname @@ -287,7 +285,7 @@ 'It makes sure the dependencies are installed.\\\n' local files = '' if value_i ~= '' then - files = '%files -n ' .. rpmname .. '\\\n' .. '%ghost %dir ' .. value_i + files = '%files -n ' .. rpmname .. '\\\n' .. '%ghost ' .. value_i elseif value_f ~= '' then files = '%files -n ' .. rpmname .. ' -f ' .. value_f end diff --git a/macros.python-wheel-sbom b/macros.python-wheel-sbom deleted file mode 100644 index 41389f9..0000000 --- a/macros.python-wheel-sbom +++ /dev/null @@ -1,124 +0,0 @@ -# The macros in this file are used to add SBOM to wheel files that we ship. -# Majority of Python packages will not need to do that, -# as they only use wheels as an intermediate artifact. -# The macros will be used by packages installing wheel to %%python_wheel_dir -# or by Python interpreters bundling their own (patched) wheels. -# -# The runtime dependencies are not Required by the python-rpm-macros package, -# users of this macro need to specify them on their own or rely on the fact that -# they are all available in the default buildroot. -# -# Usage: %%python_wheel_inject_sbom PATHS_TO_WHEELS -# -# The wheels are modified in-place. - - -# Path of the SBOM file in the PEP 770 .dist-info/sboms directory -# This filename is explicitly mentioned in https://cyclonedx.org/specification/overview/ -# section Recognized file patterns -%__python_wheel_sbom_filename bom.json - - -# The SBOM content to put to the file -# This is a CycloneDX component as recommended in https://discuss.python.org/t/97436/7 -%__python_wheel_sbom_content %{expand:{ - "bomFormat": "CycloneDX", - "specVersion": "1.6", - "components": [ - { - "type": "library", - "name": "%{name}", - "version": "%{version}-%{release}", - "purl": "%{__python_wheel_purl}" - } - ] -}} - - -# The purl used above -# We use the src package name (which is easier to get and more useful to consumers). -# Note that epoch needs special handling, see https://github.com/package-url/purl-spec/issues/69 -# and https://redhatproductsecurity.github.io/security-data-guidelines/purl/ -%__python_wheel_purl pkg:rpm/%{__python_wheel_dist_purl_namespace}/%{name}@%{version}-%{release}?%{?epoch:epoch=%{epoch}&}arch=src - - -# The purl namespace used above -# https://lists.fedoraproject.org/archives/list/packaging@lists.fedoraproject.org/thread/GTRCTAF3R3SSBVEJYFCATKNRT7RYVFQI/ -# Distributors, define %%dist_purl_namespace to set this. -# The rest of the code is fallback for distributions without it (relying on %%dist_name). -%__python_wheel_dist_purl_namespace %{?dist_purl_namespace}%{!?dist_purl_namespace:%{lua: - if macros.epel then - -- being epel beats the %%dist_name value - -- added in https://src.fedoraproject.org/rpms/epel-rpm-macros/pull-request/86 - print("epel") - else - local dist_map = { - -- fedora is in the purl-spec examples https://github.com/package-url/purl-spec/blob/main/PURL-TYPES.rst#rpm - -- added in https://src.fedoraproject.org/rpms/fedora-release/pull-request/385 - ["Fedora Linux"] = "fedora", - -- added in https://gitlab.com/redhat/centos-stream/rpms/centos-stream-release/-/merge_requests/7 - ["CentOS Stream"] = "centos", - -- documented at https://redhatproductsecurity.github.io/security-data-guidelines/purl/ - ["Red Hat Enterprise Linux"] = "redhat", - -- documented at https://wiki.almalinux.org/documentation/sbom-guide.html - ["AlmaLinux"] = "almalinux", - -- from https://github.com/google/osv.dev/pull/2939 - ["Rocky Linux"] = "rocky-linux", - } - print(dist_map[macros.dist_name] or "unknown") - end -}} - - -# A Bash scriptlet to inject the SBOM file into the wheel(s) -# The macro takes positional nargs+ with wheel paths -# For each wheel, it -# 1. aborts if the SBOM file is already there (it won't override) -# 2. inserts the SBOM file to .dist-info/sboms -# 3. amends .dist-info/RECORD with the added SBOM file -%python_wheel_inject_sbom() %{expand:( - %[%# ? "" : "%{error:%%%0: At least one argument (wheel path) is required}"] - - set -eu -o pipefail - export LANG=C.utf-8 - - tmpdir=$(mktemp -d) - trap 'rm -rf "$tmpdir"' EXIT - pwd0=$(pwd) - ret=0 - - for whl in %{*}; do - cd "$tmpdir" - if [[ "$whl" != /* ]]; then - whl="$pwd0/$whl" - fi - - record=$(zipinfo -1 "$whl" | grep -E '^[^/]+-[^/]+\.dist-info/RECORD$') - distinfo="${record%%/RECORD}" - bom="$distinfo/sboms/%{__python_wheel_sbom_filename}" - - if zipinfo -1 "$whl" | grep -qFx "$bom"; then - echo -e "\\n\\nERROR %%%%%0: $whl already has $bom, aborting\\n\\n" >&2 - ret=1 - continue - fi - - unzip "$whl" "$record" - mkdir "$distinfo/sboms" - echo '%{__python_wheel_sbom_content}' > "$bom" - checksum="sha256=$(sha256sum "$bom" | cut -f1 -d' ')" - size="$(wc --bytes "$bom" | cut -f1 -d' ')" - echo "$bom,$checksum,$size" >> "$record" - - if [[ -n "${SOURCE_DATE_EPOCH:-}" ]]; then - touch --date="@$SOURCE_DATE_EPOCH" "$bom" "$record" - fi - - zip -r "$whl" "$record" "$bom" - rm -rf "$distinfo" - cd "$pwd0" - done - - exit $ret -)} - diff --git a/plan.fmf b/plan.fmf index 88850dd..a681cdf 100644 --- a/plan.fmf +++ b/plan.fmf @@ -17,9 +17,6 @@ discover: test: rpmlint ~/rpmbuild/RPMS/x86_64/pythontest-0-0.clamp0.x86_64.rpm | grep python-bytecode-inconsistent-mtime || exit 0 && exit 1 - name: rpmlint_clamp_mtime_on test: rpmlint ~/rpmbuild/RPMS/x86_64/pythontest-0-0.clamp1.x86_64.rpm | grep python-bytecode-inconsistent-mtime || exit 0 && exit 1 - - name: python_wheel_inject_sbom - path: /tests - test: rpmbuild -ba testwheel.spec prepare: - name: Install dependencies @@ -30,8 +27,6 @@ prepare: - python-rpm-macros - python3-rpm-macros - python3-devel - - python3-setuptools - - python3-pip - python3-pytest - python3.6 - dnf diff --git a/python-rpm-macros.spec b/python-rpm-macros.spec index 263853a..d536935 100644 --- a/python-rpm-macros.spec +++ b/python-rpm-macros.spec @@ -8,7 +8,6 @@ Source101: macros.python Source102: macros.python-srpm Source104: macros.python3 Source105: macros.pybytecompile -Source106: macros.python-wheel-sbom # Lua files Source201: python.lua @@ -56,7 +55,7 @@ elseif posix.stat('macros.python-srpm') then end } Version: %{__default_python3_version} -Release: 9%{?dist} +Release: 5%{?dist} BuildArch: noarch @@ -150,7 +149,6 @@ grep -E '^#[^%%]*%%[^%%]' %{buildroot}%{rpmmacrodir}/macros.* && exit 1 || true %files %{rpmmacrodir}/macros.python %{rpmmacrodir}/macros.pybytecompile -%{rpmmacrodir}/macros.python-wheel-sbom %{_rpmconfigdir}/redhat/import_all_modules.py %{_rpmconfigdir}/redhat/pathfix.py @@ -169,20 +167,6 @@ grep -E '^#[^%%]*%%[^%%]' %{buildroot}%{rpmmacrodir}/macros.* && exit 1 || true %changelog -* Thu Oct 16 2025 Miro Hrončok - 3.14-9 -- %%python_extras_subpkg: Only %%ghost the egg-info/dist-info directory, not the content -- That way, accidentally unpackaged files within are reported as errors - -* Tue Sep 09 2025 Miro Hrončok - 3.14-8 -- %%python_extras_subpkg: Add -v option to specify the required version(-release) -- This is useful when the extras are built from a different specfile (e.g. in EPEL for a RHEL base package) - -* Fri Aug 29 2025 Miro Hrončok - 3.14-7 -- %%python_wheel_inject_sbom: Don't accidentally alter nested .dist-infos - -* Wed Aug 13 2025 Miro Hrončok - 3.14-6 -- Introduce %%python_wheel_inject_sbom - * Mon Aug 11 2025 Lumír Balhar - 3.14-5 - import_all_modules: Add error handling for import failures diff --git a/tests/test_evals.py b/tests/test_evals.py index e6cc8f7..b26ff29 100644 --- a/tests/test_evals.py +++ b/tests/test_evals.py @@ -551,7 +551,7 @@ def test_python_extras_subpkg_i(): It makes sure the dependencies are installed. %files -n python3-setuptools_scm+toml - %ghost %dir /usr/lib/python{X_Y}/site-packages/*.egg-info + %ghost /usr/lib/python{X_Y}/site-packages/*.egg-info %package -n python3-setuptools_scm+yaml Summary: Metapackage for python3-setuptools_scm: yaml extras @@ -562,7 +562,7 @@ def test_python_extras_subpkg_i(): It makes sure the dependencies are installed. %files -n python3-setuptools_scm+yaml - %ghost %dir /usr/lib/python{X_Y}/site-packages/*.egg-info + %ghost /usr/lib/python{X_Y}/site-packages/*.egg-info """).lstrip().splitlines() assert lines == expected @@ -658,21 +658,6 @@ def test_python_extras_subpkg_aA(): 'BuildArch: noarch (default)) options are not possible') -def test_python_extras_subpkg_v(): - lines = rpm_eval('%python_extras_subpkg -n python3-setuptools_scm -A -v 1.2.3 -F toml', - version='6', release='7') - expected = textwrap.dedent(f""" - %package -n python3-setuptools_scm+toml - Summary: Metapackage for python3-setuptools_scm: toml extras - Requires: python3-setuptools_scm = 1.2.3 - %description -n python3-setuptools_scm+toml - This is a metapackage bringing in toml extras requires for - python3-setuptools_scm. - It makes sure the dependencies are installed. - """).lstrip().splitlines() - assert lines == expected - - def test_python_extras_subpkg_underscores(): lines = rpm_eval('%python_extras_subpkg -n python3-webscrapbook -F adhoc_ssl', version='0.33.3', release='1.fc33') diff --git a/tests/testwheel.spec b/tests/testwheel.spec deleted file mode 100644 index 461d7f3..0000000 --- a/tests/testwheel.spec +++ /dev/null @@ -1,116 +0,0 @@ -Name: testwheel -Epoch: 42 -Version: 1 -Release: 0%{?dist} -Summary: ... -License: MIT -BuildArch: noarch -BuildRequires: python3-devel -BuildRequires: python3-setuptools >= 61 -BuildRequires: python3-pip - -%description -This builds and installs a wheel which we can then use as a test for -%%python_wheel_inject_sbom. - - -%prep -cat > pyproject.toml << EOF -[project] -name = "testwheel" -version = "1" - -[build-system] -requires = ["setuptools >= 61"] -build-backend = "setuptools.build_meta" - -[tool.setuptools] -include-package-data = true - -[tool.setuptools.packages.find] -include = ["testwheel*"] -EOF -# create a secondary dist-info folder in the project -# we need to ensure this file is not altered -mkdir -p testwheel/_vendor/dependency-2.2.2.dist-info -touch testwheel/_vendor/dependency-2.2.2.dist-info/RECORD -echo 'recursive-include testwheel/_vendor *' > MANIFEST.in - - -%build -export PIP_CONFIG_FILE=/dev/null -%{python3} -m pip wheel . --no-build-isolation - -# The macro should happily alter multiple wheels, let's make more -for i in {1..5}; do - mkdir ${i} - cp -a *.whl ${i} -done - -# using relative paths should succeed -%python_wheel_inject_sbom {1..5}/*.whl - -# repetitive use should bail out and fail (SBOM is already there) -%{python_wheel_inject_sbom {1..5}/*.whl} && exit 1 || true - -# each wheel should already have it, all should fail individually as well -for i in {1..5}; do - %{python_wheel_inject_sbom ${i}/*.whl} && exit 1 || true -done - - -%install -mkdir -p %{buildroot}%{python_wheel_dir} -cp -a *.whl %{buildroot}%{python_wheel_dir} - -# using absolute paths should work -%python_wheel_inject_sbom %{buildroot}%{python_wheel_dir}/*.whl - -# and fail when repeated -%{python_wheel_inject_sbom %{buildroot}%{python_wheel_dir}/*.whl} && exit 1 || true - - -%check -%define venvsite venv/lib/python%{python3_version}/site-packages -%{python3} -m venv venv -venv/bin/pip install --no-index --no-cache-dir %{buildroot}%{python_wheel_dir}/*.whl - -test -f %{venvsite}/testwheel-1.dist-info/RECORD -test -f %{venvsite}/testwheel-1.dist-info/sboms/bom.json -grep '^testwheel-1.dist-info/sboms/bom.json,' %{venvsite}/testwheel-1.dist-info/RECORD -# a more specific grep. we don't care about CRLF line ends (pip uses those? without the sed the $ doesn't match line end) -sed 's/\r//g' %{venvsite}/testwheel-1.dist-info/RECORD | grep -E '^testwheel-1.dist-info/sboms/bom.json,sha256=[a-f0-9]{64},[0-9]+$' - -test -f %{venvsite}/testwheel/_vendor/dependency-2.2.2.dist-info/RECORD -test -f %{venvsite}/testwheel/_vendor/dependency-2.2.2.dist-info/sboms/bom.json && exit 1 || true - -# this deliberately uses a different mechanism than the macro -# if you are running this test on a different distro, adjust it -%define ns %{?fedora:fedora}%{?eln:fedora}%{?epel:epel}%{!?eln:%{!?epel:%{?rhel:redhat}}} - -PYTHONOPTIMIZE=0 %{python3} -c " -import json -with open('%{venvsite}/testwheel-1.dist-info/sboms/bom.json') as fp: - sbom = json.load(fp) -assert len(sbom['components']) == 1 -assert sbom['components'][0]['type'] == 'library' -assert sbom['components'][0]['name'] == 'testwheel' -assert sbom['components'][0]['version'] == '1-0%{?dist}' -assert sbom['components'][0]['purl'] == 'pkg:rpm/%{ns}/testwheel@1-0%{?dist}?epoch=42&arch=src' -" - -# replace the installation with the original unaltered wheel -venv/bin/pip install --force-reinstall --no-index --no-cache-dir *.whl -test -f %{venvsite}/testwheel-1.dist-info/RECORD -# no SBOM -test ! -e %{venvsite}/testwheel-1.dist-info/sboms/bom.json -grep '^testwheel-1.dist-info/sboms/bom.json,' %{venvsite}/testwheel-1.dist-info/RECORD && exit 1 || true - - -%files -%{python_wheel_dir}/*.whl - - -%changelog -* Wed Aug 13 2025 Miro Hrončok - 42:1-0 -- A static changelog with a date, so we can clamp mtimes