@@ -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,8 @@ class ConfigError(ValueError):
7374 'dynamic' ,
7475}
7576
77+ default_license_files_globs = ['COPYING*' , 'LICENSE*' ]
78+
7679
7780def read_flit_config (path ):
7881 """Read and check the `pyproject.toml` file with data about the package.
@@ -427,6 +430,13 @@ def _prep_metadata(md_sect, path):
427430 # For internal use, record the main requirements as a '.none' extra.
428431 res .reqs_by_extra ['.none' ] = reqs_noextra
429432
433+ if path :
434+ license_files = _license_files_from_globs (
435+ path .parent , default_license_files_globs , use_defaults = True
436+ )
437+ res .referenced_files .extend (license_files )
438+ md_dict ['license_files' ] = license_files
439+
430440 return res
431441
432442def _expand_requires_extra (re ):
@@ -439,6 +449,37 @@ def _expand_requires_extra(re):
439449 yield '{} ; extra == "{}"' .format (req , extra )
440450
441451
452+ def _license_files_from_globs (project_dir : Path , globs , use_defaults = False ):
453+ license_files = []
454+ for pattern in globs :
455+ if pattern .startswith ("/" ):
456+ raise ConfigError (
457+ "Invalid glob pattern for [project.license-files]: '{}'. "
458+ "Pattern must not start with '/'." .format (pattern )
459+ )
460+ if ".." in pattern :
461+ raise ConfigError (
462+ "Invalid glob pattern for [project.license-files]: '{}'. "
463+ "Pattern must not contain '..'" .format (pattern )
464+ )
465+ try :
466+ files = [
467+ str (file .relative_to (project_dir )).replace (osp .sep , "/" )
468+ for file in project_dir .glob (pattern )
469+ if file .is_file ()
470+ ]
471+ except ValueError as ex :
472+ raise ConfigError (
473+ "Invalid glob pattern for [project.license-files]: '{}'. {}" .format (pattern , ex .args [0 ])
474+ )
475+
476+ if not files and not use_defaults :
477+ raise ConfigError (
478+ "No files found for [project.license-files]: '{}' pattern" .format (pattern )
479+ )
480+ license_files .extend (files )
481+ return sorted (license_files )
482+
442483def _check_type (d , field_name , cls ):
443484 if not isinstance (d [field_name ], cls ):
444485 raise ConfigError (
@@ -551,6 +592,18 @@ def read_pep621_metadata(proj, path) -> LoadedConfig:
551592 "file or text field required in [project.license] table"
552593 )
553594
595+ license_files = []
596+ if 'license-files' in proj :
597+ _check_type (proj , 'license-files' , list )
598+ globs = proj ['license-files' ]
599+ license_files = _license_files_from_globs (path .parent , globs )
600+ else :
601+ license_files = _license_files_from_globs (
602+ path .parent , default_license_files_globs , use_defaults = True
603+ )
604+ lc .referenced_files .extend (license_files )
605+ md_dict ['license_files' ] = license_files
606+
554607 if 'authors' in proj :
555608 _check_type (proj , 'authors' , list )
556609 md_dict .update (pep621_people (proj ['authors' ]))
0 commit comments