%pyproject_save_files: Support License-Files installed into the *Root License Directory* from PEP 369

Files still need to be marked as License-File to be considered %license,
but if their path in METADATA is specified relative to dist-info/licenses,
they are correctly recognised.

This makes License-Files specified by hatchling 1.9.0+ marked as %license.
This commit is contained in:
Miro Hrončok 2022-09-19 18:14:52 +02:00
parent a3e31cca28
commit 92ad52e5d4
4 changed files with 53 additions and 5 deletions

View File

@ -10,7 +10,7 @@ License: MIT
# Increment Y and reset Z when new macros or features are added # Increment Y and reset Z when new macros or features are added
# Increment Z when this is a bugfix or a cosmetic change # Increment Z when this is a bugfix or a cosmetic change
# Dropping support for EOL Fedoras is *not* considered a breaking change # Dropping support for EOL Fedoras is *not* considered a breaking change
Version: 1.3.4 Version: 1.4.0
Release: 1%{?dist} Release: 1%{?dist}
# Macro files # Macro files
@ -127,6 +127,10 @@ export HOSTNAME="rpmbuild" # to speedup tox in network-less mock, see rhbz#1856
%license LICENSE %license LICENSE
%changelog %changelog
* Mon Sep 19 2022 Miro Hrončok <mhroncok@redhat.com> - 1.4.0-1
- %%pyproject_save_files: Support License-Files installed into the *Root License Directory* from PEP 369
- Fixes: rhbz#2127946
* Tue Aug 30 2022 Otto Liljalaakso <otto.liljalaakso@iki.fi> - 1.3.4-1 * Tue Aug 30 2022 Otto Liljalaakso <otto.liljalaakso@iki.fi> - 1.3.4-1
- Fix typo in internal function name - Fix typo in internal function name

View File

@ -286,6 +286,36 @@ def module_names_from_path(path):
return {'.'.join(parts[:x+1]) for x in range(len(parts))} 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( def classify_paths(
record_path, parsed_record_content, metadata, sitedirs, python_version, prefix 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 :( "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. # 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
@ -325,7 +362,7 @@ 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
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) paths["metadata"]["licenses"].append(path)
else: else:
paths["metadata"]["files"].append(path) paths["metadata"]["files"].append(path)

View File

@ -15484,8 +15484,8 @@ metadata:
content: | content: |
Name: Django Name: Django
Version: 3.0.7 Version: 3.0.7
License-File: licenses/LICENSE License-File: LICENSE
License-File: licenses/LICENSE.python License-File: LICENSE.python
Whatever: False data Whatever: False data
records: records:

View File

@ -49,6 +49,13 @@ sed -Ei '/^(coverage)$/d' requirements-dev.txt
%check %check
%pytest %pytest
%if 0%{?fedora} > 35 || 0%{?rhel} > 9
# Internal check that license file was recognized correctly with hatchling 1.9.0+
grep '^%%license' %{pyproject_files} > tested.license
echo '%%license %{python3_sitelib}/userpath-%{version}.dist-info/licenses/LICENSE.txt' > expected.license
diff tested.license expected.license
%endif
%files -n python3-userpath -f %{pyproject_files} %files -n python3-userpath -f %{pyproject_files}
%{_bindir}/userpath %{_bindir}/userpath