|
7 | 7 | import re |
8 | 8 | import sys |
9 | 9 | from collections.abc import Iterable, Iterator, MutableMapping, Sequence |
10 | | -from glob import iglob |
| 10 | +from glob import glob |
11 | 11 | from pathlib import Path |
12 | 12 | from typing import TYPE_CHECKING, Any, Union |
13 | 13 |
|
@@ -448,50 +448,78 @@ def _finalize_license_files(self) -> None: |
448 | 448 | # See https://wheel.readthedocs.io/en/stable/user_guide.html |
449 | 449 | # -> 'Including license files in the generated wheel file' |
450 | 450 | 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) |
451 | 454 |
|
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)) |
468 | 456 |
|
469 | 457 | @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]: |
471 | 461 | """ |
472 | 462 | >>> list(Distribution._expand_patterns(['LICENSE'])) |
473 | 463 | ['LICENSE'] |
474 | 464 | >>> list(Distribution._expand_patterns(['pyproject.toml', 'LIC*'])) |
475 | 465 | ['pyproject.toml', 'LICENSE'] |
476 | 466 | >>> list(Distribution._expand_patterns(['setuptools/**/pyprojecttoml.py'])) |
477 | 467 | ['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 '..' |
482 | 468 | """ |
483 | 469 | return ( |
484 | 470 | path.replace(os.sep, "/") |
485 | 471 | for pattern in patterns |
486 | | - for path in sorted(cls._find_pattern(pattern)) |
| 472 | + for path in sorted(cls._find_pattern(pattern, enforce_match)) |
487 | 473 | if not path.endswith('~') and os.path.isfile(path) |
488 | 474 | ) |
489 | 475 |
|
490 | 476 | @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: |
493 | 495 | 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 |
495 | 523 |
|
496 | 524 | # FIXME: 'Distribution._parse_config_files' is too complex (14) |
497 | 525 | def _parse_config_files(self, filenames=None): # noqa: C901 |
|
0 commit comments