@@ -60,6 +60,7 @@ class ConfigError(ValueError):
6060 'readme' ,
6161 'requires-python' ,
6262 'license' ,
63+ 'license-files' ,
6364 'authors' ,
6465 'maintainers' ,
6566 'keywords' ,
@@ -73,6 +74,9 @@ class ConfigError(ValueError):
7374 'dynamic' ,
7475}
7576
77+ default_license_files_globs = ['COPYING*' , 'LICEN[CS]E*' ]
78+ license_files_allowed_chars = re .compile (r'^[\w\-\.\/\*\?\[\]]+$' )
79+
7680
7781def read_flit_config (path ):
7882 """Read and check the `pyproject.toml` file with data about the package.
@@ -427,6 +431,15 @@ def _prep_metadata(md_sect, path):
427431 # For internal use, record the main requirements as a '.none' extra.
428432 res .reqs_by_extra ['.none' ] = reqs_noextra
429433
434+ if path :
435+ license_files = sorted (
436+ _license_files_from_globs (
437+ path .parent , default_license_files_globs , warn_no_files = False
438+ )
439+ )
440+ res .referenced_files .extend (license_files )
441+ md_dict ['license_files' ] = license_files
442+
430443 return res
431444
432445def _expand_requires_extra (re ):
@@ -439,6 +452,43 @@ def _expand_requires_extra(re):
439452 yield '{} ; extra == "{}"' .format (req , extra )
440453
441454
455+ def _license_files_from_globs (project_dir : Path , globs , warn_no_files = True ):
456+ license_files = set ()
457+ for pattern in globs :
458+ if isabs_ish (pattern ):
459+ raise ConfigError (
460+ "Invalid glob pattern for [project.license-files]: '{}'. "
461+ "Pattern must not start with '/'." .format (pattern )
462+ )
463+ if ".." in pattern :
464+ raise ConfigError (
465+ "Invalid glob pattern for [project.license-files]: '{}'. "
466+ "Pattern must not contain '..'" .format (pattern )
467+ )
468+ if license_files_allowed_chars .match (pattern ) is None :
469+ raise ConfigError (
470+ "Invalid glob pattern for [project.license-files]: '{}'. "
471+ "Pattern contains invalid characters. "
472+ "https://packaging.python.org/en/latest/specifications/pyproject-toml/#license-files"
473+ )
474+ try :
475+ files = [
476+ str (file .relative_to (project_dir )).replace (osp .sep , "/" )
477+ for file in project_dir .glob (pattern )
478+ if file .is_file ()
479+ ]
480+ except ValueError as ex :
481+ raise ConfigError (
482+ "Invalid glob pattern for [project.license-files]: '{}'. {}" .format (pattern , ex .args [0 ])
483+ )
484+
485+ if not files and warn_no_files :
486+ raise ConfigError (
487+ "No files found for [project.license-files]: '{}' pattern" .format (pattern )
488+ )
489+ license_files .update (files )
490+ return license_files
491+
442492def _check_type (d , field_name , cls ):
443493 if not isinstance (d [field_name ], cls ):
444494 raise ConfigError (
@@ -525,6 +575,7 @@ def read_pep621_metadata(proj, path) -> LoadedConfig:
525575 if 'requires-python' in proj :
526576 md_dict ['requires_python' ] = proj ['requires-python' ]
527577
578+ license_files = set ()
528579 if 'license' in proj :
529580 _check_type (proj , 'license' , dict )
530581 license_tbl = proj ['license' ]
@@ -543,14 +594,33 @@ def read_pep621_metadata(proj, path) -> LoadedConfig:
543594 raise ConfigError (
544595 "[project.license] should specify file or text, not both"
545596 )
546- lc . referenced_files . append (license_tbl ['file' ])
597+ license_files . add (license_tbl ['file' ])
547598 elif 'text' in license_tbl :
548599 pass
549600 else :
550601 raise ConfigError (
551602 "file or text field required in [project.license] table"
552603 )
553604
605+ if 'license-files' in proj :
606+ _check_type (proj , 'license-files' , list )
607+ globs = proj ['license-files' ]
608+ license_files = _license_files_from_globs (path .parent , globs )
609+ if isinstance (proj .get ('license' ), dict ):
610+ raise ConfigError (
611+ "license-files cannot be used with a license table, "
612+ "use 'project.license' with a license expression instead"
613+ )
614+ else :
615+ license_files .update (
616+ _license_files_from_globs (
617+ path .parent , default_license_files_globs , warn_no_files = False
618+ )
619+ )
620+ license_files_sorted = sorted (license_files )
621+ lc .referenced_files .extend (license_files_sorted )
622+ md_dict ['license_files' ] = license_files_sorted
623+
554624 if 'authors' in proj :
555625 _check_type (proj , 'authors' , list )
556626 md_dict .update (pep621_people (proj ['authors' ]))
0 commit comments