|
6 | 6 | import os |
7 | 7 | import re |
8 | 8 | import sys |
9 | | -from collections.abc import Iterable, MutableMapping, Sequence |
| 9 | +from collections.abc import Iterable, Iterator, MutableMapping, Sequence |
10 | 10 | from glob import iglob |
11 | 11 | from pathlib import Path |
12 | 12 | from typing import TYPE_CHECKING, Any, Union |
@@ -450,27 +450,51 @@ def _finalize_license_files(self) -> None: |
450 | 450 | patterns = ['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*'] |
451 | 451 |
|
452 | 452 | self.metadata.license_files = list( |
453 | | - map( |
454 | | - lambda path: path.replace(os.sep, "/"), |
455 | | - unique_everseen(self._expand_patterns(patterns)), |
456 | | - ) |
| 453 | + unique_everseen(self._expand_patterns(patterns)), |
457 | 454 | ) |
458 | 455 |
|
459 | | - @staticmethod |
460 | | - def _expand_patterns(patterns): |
| 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 | + ) |
| 468 | + |
| 469 | + @classmethod |
| 470 | + def _expand_patterns(cls, patterns: list[str]) -> Iterator[str]: |
461 | 471 | """ |
462 | 472 | >>> list(Distribution._expand_patterns(['LICENSE'])) |
463 | 473 | ['LICENSE'] |
464 | 474 | >>> list(Distribution._expand_patterns(['pyproject.toml', 'LIC*'])) |
465 | 475 | ['pyproject.toml', 'LICENSE'] |
466 | 476 | """ |
467 | 477 | return ( |
468 | | - path |
| 478 | + path.replace(os.sep, "/") |
469 | 479 | for pattern in patterns |
470 | | - for path in sorted(iglob(pattern, recursive=True)) |
| 480 | + for path in sorted(cls._find_pattern(pattern)) |
471 | 481 | if not path.endswith('~') and os.path.isfile(path) |
472 | 482 | ) |
473 | 483 |
|
| 484 | + @staticmethod |
| 485 | + def _find_pattern(pattern: str) -> Iterator[str]: |
| 486 | + """ |
| 487 | + >>> list(Distribution._find_pattern("setuptools/**/pyprojecttoml.py")) |
| 488 | + ['setuptools/config/pyprojecttoml.py'] |
| 489 | + >>> list(Distribution._find_pattern("../LICENSE")) |
| 490 | + Traceback (most recent call last): |
| 491 | + ... |
| 492 | + setuptools.errors.InvalidConfigError: Pattern '../LICENSE' cannot contain '..' |
| 493 | + """ |
| 494 | + if ".." in pattern: # XXX: Any other invalid character? |
| 495 | + raise InvalidConfigError(f"Pattern {pattern!r} cannot contain '..'") |
| 496 | + return iglob(pattern, recursive=True) |
| 497 | + |
474 | 498 | # FIXME: 'Distribution._parse_config_files' is too complex (14) |
475 | 499 | def _parse_config_files(self, filenames=None): # noqa: C901 |
476 | 500 | """ |
|
0 commit comments