Skip to content

Commit 095990f

Browse files
abravalhericdce8p
andcommitted
Validate license-files glob patterns individually
Co-authored-by: Marc Mueller <[email protected]>
1 parent ea772e2 commit 095990f

File tree

2 files changed

+55
-27
lines changed

2 files changed

+55
-27
lines changed

setuptools/dist.py

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import re
88
import sys
99
from collections.abc import Iterable, Iterator, MutableMapping, Sequence
10-
from glob import iglob
10+
from glob import glob
1111
from pathlib import Path
1212
from typing import TYPE_CHECKING, Any, Union
1313

@@ -448,50 +448,78 @@ def _finalize_license_files(self) -> None:
448448
# See https://wheel.readthedocs.io/en/stable/user_guide.html
449449
# -> 'Including license files in the generated wheel file'
450450
patterns = ['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']
451+
files = self._expand_patterns(patterns, enforce_match=False)
452+
else: # Patterns explicitly given by the user
453+
files = self._expand_patterns(patterns, enforce_match=True)
451454

452-
self.metadata.license_files = list(
453-
unique_everseen(self._expand_patterns(patterns)),
454-
)
455-
456-
if license_files and not self.metadata.license_files:
457-
# Pattern explicitly given but no file found
458-
if not self.metadata.license_files:
459-
SetuptoolsDeprecationWarning.emit(
460-
"Cannot find any license files for the given patterns.",
461-
f"The glob patterns {patterns!r} do not match any file.",
462-
due_date=(2026, 2, 20),
463-
# Warning introduced on 2025/02/18
464-
# PEP 639 requires us to error, but as a transition period
465-
# we will only issue a warning to give people time to prepare.
466-
# After the transition, this should raise an InvalidConfigError.
467-
)
455+
self.metadata.license_files = list(unique_everseen(files))
468456

469457
@classmethod
470-
def _expand_patterns(cls, patterns: list[str]) -> Iterator[str]:
458+
def _expand_patterns(
459+
cls, patterns: list[str], enforce_match: bool = True
460+
) -> Iterator[str]:
471461
"""
472462
>>> list(Distribution._expand_patterns(['LICENSE']))
473463
['LICENSE']
474464
>>> list(Distribution._expand_patterns(['pyproject.toml', 'LIC*']))
475465
['pyproject.toml', 'LICENSE']
476466
>>> list(Distribution._expand_patterns(['setuptools/**/pyprojecttoml.py']))
477467
['setuptools/config/pyprojecttoml.py']
478-
>>> list(Distribution._expand_patterns(['../LICENSE']))
479-
Traceback (most recent call last):
480-
...
481-
setuptools.errors.InvalidConfigError: Pattern '../LICENSE' cannot contain '..'
482468
"""
483469
return (
484470
path.replace(os.sep, "/")
485471
for pattern in patterns
486-
for path in sorted(cls._find_pattern(pattern))
472+
for path in sorted(cls._find_pattern(pattern, enforce_match))
487473
if not path.endswith('~') and os.path.isfile(path)
488474
)
489475

490476
@staticmethod
491-
def _find_pattern(pattern: str) -> Iterator[str]:
492-
if ".." in pattern: # XXX: Any other invalid character?
477+
def _find_pattern(pattern: str, enforce_match: bool = True) -> list[str]:
478+
r"""
479+
>>> Distribution._find_pattern("LICENSE")
480+
['LICENSE']
481+
>>> Distribution._find_pattern("/LICENSE.MIT")
482+
Traceback (most recent call last):
483+
...
484+
setuptools.errors.InvalidConfigError: Pattern '/LICENSE.MIT' should be relative...
485+
>>> Distribution._find_pattern("../LICENSE.MIT")
486+
Traceback (most recent call last):
487+
...
488+
setuptools.errors.InvalidConfigError: ...Pattern '../LICENSE.MIT' cannot contain '..'
489+
>>> Distribution._find_pattern("LICEN{CSE*")
490+
Traceback (most recent call last):
491+
...
492+
setuptools.warnings.SetuptoolsDeprecationWarning: ...Pattern 'LICEN{CSE*' contains invalid characters...
493+
"""
494+
if ".." in pattern:
493495
raise InvalidConfigError(f"Pattern {pattern!r} cannot contain '..'")
494-
return iglob(pattern, recursive=True)
496+
if pattern.startswith((os.sep, "/")) or ":\\" in pattern:
497+
raise InvalidConfigError(
498+
f"Pattern {pattern!r} should be relative and must not start with '/'"
499+
)
500+
if re.match(r'^[\w\-\.\/\*\?\[\]]+$', pattern) is None:
501+
pypa_guides = "specifications/pyproject-toml/#license-files"
502+
SetuptoolsDeprecationWarning.emit(
503+
"Please provide a valid glob pattern.",
504+
"Pattern {pattern!r} contains invalid characters.",
505+
pattern=pattern,
506+
see_url=f"https://packaging.python.org/en/latest/{pypa_guides}",
507+
due_date=(2025, 2, 20), # Introduced in 2024-02-20
508+
)
509+
510+
found = glob(pattern, recursive=True)
511+
512+
if enforce_match and not found:
513+
SetuptoolsDeprecationWarning.emit(
514+
"Cannot find any files for the given pattern.",
515+
"Pattern {pattern!r} did not match any files.",
516+
pattern=pattern,
517+
due_date=(2025, 2, 20), # Introduced in 2024-02-20
518+
# PEP 639 requires us to error, but as a transition period
519+
# we will only issue a warning to give people time to prepare.
520+
# After the transition, this should raise an InvalidConfigError.
521+
)
522+
return found
495523

496524
# FIXME: 'Distribution._parse_config_files' is too complex (14)
497525
def _parse_config_files(self, filenames=None): # noqa: C901

setuptools/tests/config/test_apply_pyprojecttoml.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ def test_missing_patterns(self, tmp_path):
465465
pyproject = self.base_pyproject_license_pep639(tmp_path)
466466
assert list(tmp_path.glob("_FILE*")) == [] # sanity check
467467

468-
msg = "Cannot find any license files for the given patterns."
468+
msg = "Cannot find any files for the given pattern.*"
469469
with pytest.warns(SetuptoolsDeprecationWarning, match=msg):
470470
pyprojecttoml.apply_configuration(makedist(tmp_path), pyproject)
471471

0 commit comments

Comments
 (0)