Automatically detect LICENSE files and mark them with %license macro

This commit is contained in:
Tomas Hrnciar 2021-07-20 16:07:25 +02:00
parent 5729f18ddb
commit 5169e0e340
6 changed files with 60 additions and 18 deletions

View File

@ -184,11 +184,10 @@ For example, if a package provides the modules `requests` and `_requests`, write
%pyproject_save_files requests _requests %pyproject_save_files requests _requests
To add listed files to the `%files` section, use `%files -f %{pyproject_files}`. To add listed files to the `%files` section, use `%files -f %{pyproject_files}`.
Note that you still need to add any documentation and license manually (for now). Note that you still need to add any documentation manually (for now).
%files -n python3-requests -f %{pyproject_files} %files -n python3-requests -f %{pyproject_files}
%doc README.rst %doc README.rst
%license LICENSE
You can use globs in the module names if listing them explicitly would be too tedious: You can use globs in the module names if listing them explicitly would be too tedious:
@ -214,10 +213,12 @@ However, in Fedora packages, always list executables explicitly to avoid uninten
%files -n python3-requests -f %{pyproject_files} %files -n python3-requests -f %{pyproject_files}
%doc README.rst %doc README.rst
%license LICENSE
%{_bindir}/downloader %{_bindir}/downloader
`%pyproject_save_files` also automatically recognizes language (`*.mo`) files and marks them with `%lang` macro and appropriate language code. `%pyproject_save_files` can automatically mark license files with `%license` macro
and language (`*.mo`) files with `%lang` macro and appropriate language code.
Only license files declared via [PEP 639] `License-Field` field are detected.
[PEP 639] is still a draft and can be changed in the future.
Note that `%pyproject_save_files` uses data from the [RECORD file](https://www.python.org/dev/peps/pep-0627/). Note that `%pyproject_save_files` uses data from the [RECORD file](https://www.python.org/dev/peps/pep-0627/).
If you wish to rename, remove or otherwise change the installed files of a package If you wish to rename, remove or otherwise change the installed files of a package
@ -303,6 +304,7 @@ so be prepared for problems.
[PEP 508]: https://www.python.org/dev/peps/pep-0508/ [PEP 508]: https://www.python.org/dev/peps/pep-0508/
[PEP 517]: https://www.python.org/dev/peps/pep-0517/ [PEP 517]: https://www.python.org/dev/peps/pep-0517/
[PEP 518]: https://www.python.org/dev/peps/pep-0518/ [PEP 518]: https://www.python.org/dev/peps/pep-0518/
[PEP 639]: https://www.python.org/dev/peps/pep-0639/
[pip's documentation]: https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support [pip's documentation]: https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support

View File

@ -117,6 +117,7 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856
- %%pyproject_buildrequires now fails when it encounters an invalid requirement - %%pyproject_buildrequires now fails when it encounters an invalid requirement
- Fixes: rhbz#1983053 - Fixes: rhbz#1983053
- Rename %%_pyproject_ghost_distinfo and %%_pyproject_record to indicate they are private - Rename %%_pyproject_ghost_distinfo and %%_pyproject_record to indicate they are private
- Automatically detect LICENSE files and mark them with %%license macro
* Fri Jul 23 2021 Fedora Release Engineering <releng@fedoraproject.org> - 0-45 * Fri Jul 23 2021 Fedora Release Engineering <releng@fedoraproject.org> - 0-45
- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild - Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild

View File

@ -5,6 +5,7 @@ import os
from collections import defaultdict from collections import defaultdict
from pathlib import PosixPath, PurePosixPath from pathlib import PosixPath, PurePosixPath
from importlib.metadata import Distribution
# From RPM's build/files.c strtokWithQuotes delim argument # From RPM's build/files.c strtokWithQuotes delim argument
@ -144,7 +145,7 @@ def add_lang_to_module(paths, module_name, path):
def classify_paths( def classify_paths(
record_path, parsed_record_content, sitedirs, python_version record_path, parsed_record_content, metadata, sitedirs, python_version
): ):
""" """
For each BuildrootPath in parsed_record_content classify it to a dict structure For each BuildrootPath in parsed_record_content classify it to a dict structure
@ -160,7 +161,7 @@ def classify_paths(
"files": [], # regular %file entries with dist-info content "files": [], # regular %file entries with dist-info content
"dirs": [distinfo], # %dir %file entries with dist-info directory "dirs": [distinfo], # %dir %file entries with dist-info directory
"docs": [], # to be used once there is upstream way to recognize READMEs "docs": [], # to be used once there is upstream way to recognize READMEs
"licenses": [], # to be used once there is upstream way to recognize LICENSEs "licenses": [], # %license entries parsed from dist-info METADATA file
}, },
"lang": {}, # %lang entries: [module_name or None][language_code] lists of .mo files "lang": {}, # %lang entries: [module_name or None][language_code] lists of .mo files
"modules": defaultdict(list), # each importable module (directory, .py, .so) "modules": defaultdict(list), # each importable module (directory, .py, .so)
@ -170,6 +171,7 @@ def classify_paths(
# In RECORDs generated by pip, there are no directories, only files. # In RECORDs generated by pip, there are no directories, only files.
# The example RECORD from PEP 376 does not contain directories either. # The example RECORD from PEP 376 does not contain directories either.
# Hence, we'll only assume files, but TODO get it officially documented. # 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: for path in parsed_record_content:
if path.suffix == ".pyc": if path.suffix == ".pyc":
# we handle bytecode separately # we handle bytecode separately
@ -180,8 +182,10 @@ def classify_paths(
# RECORD and REQUESTED files are removed in %pyproject_install # RECORD and REQUESTED files are removed in %pyproject_install
# See PEP 627 # See PEP 627
continue continue
# TODO is this a license/documentation? if license_files and path.name in license_files:
paths["metadata"]["files"].append(path) paths["metadata"]["licenses"].append(path)
else:
paths["metadata"]["files"].append(path)
continue continue
for sitedir in sitedirs: for sitedir in sitedirs:
@ -423,6 +427,14 @@ def load_parsed_record(pyproject_record):
return parsed_record return parsed_record
def dist_metadata(buildroot, record_path):
"""
Returns distribution metadata (email.message.EmailMessage), possibly empty
"""
real_dist_path = record_path.parent.to_real(buildroot)
dist = Distribution.at(real_dist_path)
return dist.metadata
def pyproject_save_files(buildroot, sitelib, sitearch, python_version, pyproject_record, varargs): def pyproject_save_files(buildroot, sitelib, sitearch, python_version, pyproject_record, varargs):
""" """
Takes arguments from the %{pyproject_save_files} macro Takes arguments from the %{pyproject_save_files} macro
@ -439,8 +451,9 @@ def pyproject_save_files(buildroot, sitelib, sitearch, python_version, pyproject
final_file_list = [] final_file_list = []
for record_path, files in parsed_records.items(): for record_path, files in parsed_records.items():
metadata = dist_metadata(buildroot, record_path)
paths_dict = classify_paths( paths_dict = classify_paths(
record_path, files, sitedirs, python_version record_path, files, metadata, sitedirs, python_version
) )
final_file_list.extend( final_file_list.extend(

View File

@ -50,11 +50,11 @@ classified:
docs: [] docs: []
files: files:
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/INSTALLER - /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/INSTALLER
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/LICENSE
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/METADATA - /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/METADATA
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/WHEEL - /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/WHEEL
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/top_level.txt - /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/top_level.txt
licenses: [] licenses:
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/LICENSE
modules: modules:
requests: requests:
- files: - files:
@ -415,13 +415,13 @@ classified:
files: files:
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/AUTHORS - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/AUTHORS
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/INSTALLER - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/INSTALLER
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE.python
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/METADATA - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/METADATA
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/WHEEL - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/WHEEL
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/entry_points.txt - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/entry_points.txt
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/top_level.txt - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/top_level.txt
licenses: [] licenses:
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE.python
lang: lang:
django: django:
af: af:
@ -7434,8 +7434,8 @@ dumped:
- - '%dir /usr/lib/python3.7/site-packages/requests' - - '%dir /usr/lib/python3.7/site-packages/requests'
- '%dir /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info' - '%dir /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info'
- '%dir /usr/lib/python3.7/site-packages/requests/__pycache__' - '%dir /usr/lib/python3.7/site-packages/requests/__pycache__'
- '%license /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/LICENSE'
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/INSTALLER - /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/INSTALLER
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/LICENSE
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/METADATA - /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/METADATA
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/WHEEL - /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/WHEEL
- /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/top_level.txt - /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/top_level.txt
@ -11252,12 +11252,12 @@ dumped:
- '%lang(zh) /usr/lib/python3.7/site-packages/django/contrib/sessions/locale/zh_Hant/LC_MESSAGES/django.mo' - '%lang(zh) /usr/lib/python3.7/site-packages/django/contrib/sessions/locale/zh_Hant/LC_MESSAGES/django.mo'
- '%lang(zh) /usr/lib/python3.7/site-packages/django/contrib/sites/locale/zh_Hans/LC_MESSAGES/django.mo' - '%lang(zh) /usr/lib/python3.7/site-packages/django/contrib/sites/locale/zh_Hans/LC_MESSAGES/django.mo'
- '%lang(zh) /usr/lib/python3.7/site-packages/django/contrib/sites/locale/zh_Hant/LC_MESSAGES/django.mo' - '%lang(zh) /usr/lib/python3.7/site-packages/django/contrib/sites/locale/zh_Hant/LC_MESSAGES/django.mo'
- '%license /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE'
- '%license /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE.python'
- /usr/bin/django-admin - /usr/bin/django-admin
- /usr/bin/django-admin.py - /usr/bin/django-admin.py
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/AUTHORS - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/AUTHORS
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/INSTALLER - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/INSTALLER
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/LICENSE.python
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/METADATA - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/METADATA
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/WHEEL - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/WHEEL
- /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/entry_points.txt - /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/entry_points.txt
@ -14487,6 +14487,23 @@ dumped:
- /usr/share/pronterface/zoom_in.png - /usr/share/pronterface/zoom_in.png
- /usr/share/pronterface/zoom_out.png - /usr/share/pronterface/zoom_out.png
metadata:
requests:
path: /usr/lib/python3.7/site-packages/requests-2.22.0.dist-info/METADATA
content: |
Name: requests
Version: 2.22.0
License-File: LICENSE
Whatever: False data
django:
path: /usr/lib/python3.7/site-packages/Django-3.0.7.dist-info/METADATA
content: |
Name: Django
Version: 3.0.7
License-File: LICENSE
License-File: LICENSE.python
Whatever: False data
records: records:
kerberos: kerberos:
path: /usr/lib64/python3.7/site-packages/kerberos-1.3.0.dist-info/RECORD path: /usr/lib64/python3.7/site-packages/kerberos-1.3.0.dist-info/RECORD

View File

@ -20,6 +20,7 @@ yaml_data = yaml.safe_load(yaml_file.read_text())
EXPECTED_DICT = yaml_data["classified"] EXPECTED_DICT = yaml_data["classified"]
EXPECTED_FILES = yaml_data["dumped"] EXPECTED_FILES = yaml_data["dumped"]
TEST_RECORDS = yaml_data["records"] TEST_RECORDS = yaml_data["records"]
TEST_METADATAS = yaml_data["metadata"]
@pytest.fixture @pytest.fixture
@ -47,6 +48,10 @@ def prepare_pyproject_record(tmp_path, package=None, content=None):
# Get test data and write dist-info/RECORD file # Get test data and write dist-info/RECORD file
record_path = BuildrootPath(TEST_RECORDS[package]["path"]) record_path = BuildrootPath(TEST_RECORDS[package]["path"])
record_file.write_text(TEST_RECORDS[package]["content"]) record_file.write_text(TEST_RECORDS[package]["content"])
if package in TEST_METADATAS:
metadata_path = BuildrootPath(TEST_METADATAS[package]["path"]).to_real(tmp_path)
metadata_path.parent.mkdir(parents=True, exist_ok=True)
metadata_path.write_text(TEST_METADATAS[package]["content"])
# Parse RECORD file # Parse RECORD file
parsed_record = parse_record(record_path, read_record(record_file)) parsed_record = parse_record(record_path, read_record(record_file))
# Save JSON content to pyproject-record # Save JSON content to pyproject-record

View File

@ -67,8 +67,12 @@ rm pyproject.toml
# We only run a subset of tests to speed things up and be less fragile # We only run a subset of tests to speed things up and be less fragile
PYTHONPATH=$(pwd) %pytest --ignore=pavement.py -k "sdist" PYTHONPATH=$(pwd) %pytest --ignore=pavement.py -k "sdist"
# Internal check that license file was recognized correctly
grep '^%%license' %{pyproject_files} > tested.license
echo '%%license %{python3_sitelib}/setuptools-%{version}.dist-info/LICENSE' > expected.license
diff tested.license expected.license
%files -n python3-setuptools -f %{pyproject_files} %files -n python3-setuptools -f %{pyproject_files}
%license LICENSE
%doc docs/* CHANGES.rst README.rst %doc docs/* CHANGES.rst README.rst
%{python3_sitelib}/distutils-precedence.pth %{python3_sitelib}/distutils-precedence.pth