Skip to content

Commit c31ebdc

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

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

@@ -459,50 +459,78 @@ def _finalize_license_files(self) -> None:
459459
# See https://wheel.readthedocs.io/en/stable/user_guide.html
460460
# -> 'Including license files in the generated wheel file'
461461
patterns = ['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']
462+
files = self._expand_patterns(patterns, enforce_match=False)
463+
else: # Patterns explicitly given by the user
464+
files = self._expand_patterns(patterns, enforce_match=True)
462465

463-
self.metadata.license_files = list(
464-
unique_everseen(self._expand_patterns(patterns)),
465-
)
466-
467-
if license_files and not self.metadata.license_files:
468-
# Pattern explicitly given but no file found
469-
if not self.metadata.license_files:
470-
SetuptoolsDeprecationWarning.emit(
471-
"Cannot find any license files for the given patterns.",
472-
f"The glob patterns {patterns!r} do not match any file.",
473-
due_date=(2026, 2, 20),
474-
# Warning introduced on 2025/02/18
475-
# PEP 639 requires us to error, but as a transition period
476-
# we will only issue a warning to give people time to prepare.
477-
# After the transition, this should raise an InvalidConfigError.
478-
)
466+
self.metadata.license_files = list(unique_everseen(files))
479467

480468
@classmethod
481-
def _expand_patterns(cls, patterns: list[str]) -> Iterator[str]:
469+
def _expand_patterns(
470+
cls, patterns: list[str], enforce_match: bool = True
471+
) -> Iterator[str]:
482472
"""
483473
>>> list(Distribution._expand_patterns(['LICENSE']))
484474
['LICENSE']
485475
>>> list(Distribution._expand_patterns(['pyproject.toml', 'LIC*']))
486476
['pyproject.toml', 'LICENSE']
487477
>>> list(Distribution._expand_patterns(['setuptools/**/pyprojecttoml.py']))
488478
['setuptools/config/pyprojecttoml.py']
489-
>>> list(Distribution._expand_patterns(['../LICENSE']))
490-
Traceback (most recent call last):
491-
...
492-
setuptools.errors.InvalidConfigError: Pattern '../LICENSE' cannot contain '..'
493479
"""
494480
return (
495481
path.replace(os.sep, "/")
496482
for pattern in patterns
497-
for path in sorted(cls._find_pattern(pattern))
483+
for path in sorted(cls._find_pattern(pattern, enforce_match))
498484
if not path.endswith('~') and os.path.isfile(path)
499485
)
500486

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

507535
# FIXME: 'Distribution._parse_config_files' is too complex (14)
508536
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)