Skip to content

Commit dc30bf8

Browse files
authored
Merge pull request #705 from cdce8p/license-files
Add initial support for license-files
2 parents 432042e + 3d7895a commit dc30bf8

File tree

13 files changed

+185
-9
lines changed

13 files changed

+185
-9
lines changed

doc/pyproject_toml.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ requires-python
9898
license
9999
A table with either a ``file`` key (a relative path to a license file) or a
100100
``text`` key (the license text).
101+
license-files
102+
A list of glob patterns for license files to include.
103+
Defaults to ``['COPYING*', 'LICEN[CS]E*']``.
101104
authors
102105
A list of tables with ``name`` and ``email`` keys (both optional) describing
103106
the authors of the project.

flit_core/flit_core/common.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,7 @@ class Metadata(object):
354354
obsoletes_dist = ()
355355
requires_external = ()
356356
provides_extra = ()
357+
license_files = ()
357358
dynamic = ()
358359

359360
metadata_version = "2.3"
@@ -425,6 +426,10 @@ def write_metadata_file(self, fp):
425426
for clsfr in self.classifiers:
426427
fp.write(u'Classifier: {}\n'.format(clsfr))
427428

429+
# TODO: License-File requires Metadata-Version '2.4'
430+
# for file in self.license_files:
431+
# fp.write(u'License-File: {}\n'.format(file))
432+
428433
for req in self.requires_dist:
429434
normalised_req = self._normalise_requires_dist(req)
430435
fp.write(u'Requires-Dist: {}\n'.format(normalised_req))

flit_core/flit_core/config.py

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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

7781
def 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

432445
def _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+
442492
def _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']))

flit_core/flit_core/wheel.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,8 @@ def write_metadata(self):
183183
with self._write_to_zip(self.dist_info + '/entry_points.txt') as f:
184184
common.write_entry_points(self.entrypoints, f)
185185

186-
for base in ('COPYING', 'LICENSE'):
187-
for path in sorted(self.directory.glob(base + '*')):
188-
if path.is_file():
189-
self._add_file(path, '%s/%s' % (self.dist_info, path.name))
186+
for file in self.metadata.license_files:
187+
self._add_file(self.directory / file, '%s/licenses/%s' % (self.dist_info, file))
190188

191189
with self._write_to_zip(self.dist_info + '/WHEEL') as f:
192190
_write_wheel_file(f, supports_py2=self.metadata.supports_py2)

flit_core/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ description = "Distribution-building parts of Flit. See flit package for more in
1212
dependencies = []
1313
requires-python = '>=3.6'
1414
readme = "README.rst"
15-
license = {file = "LICENSE"}
15+
license-files = ["LICENSE*", "flit_core/vendor/**/LICENSE*"]
1616
classifiers = [
1717
"License :: OSI Approved :: BSD License",
1818
"Topic :: Software Development :: Libraries :: Python Modules",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This file should be added to wheels
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Readme
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This file should be added to wheels
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""Example module"""
2+
3+
__version__ = '0.1'
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
[build-system]
2+
requires = ["flit_core >=3.2,<4"]
3+
build-backend = "flit_core.buildapi"
4+
5+
[project]
6+
name = "module1"
7+
authors = [
8+
{name = "Sir Röbin", email = "robin@camelot.uk"}
9+
]
10+
maintainers = [
11+
{name = "Sir Galahad"}
12+
]
13+
readme = "README.rst"
14+
license-files = ["**/LICENSE*"]
15+
requires-python = ">=3.7"
16+
dependencies = [
17+
"requests >= 2.18",
18+
"docutils",
19+
]
20+
keywords = ["example", "test"]
21+
dynamic = [
22+
"version",
23+
"description",
24+
]
25+
26+
[project.optional-dependencies]
27+
test = [
28+
"pytest",
29+
"mock; python_version<'3.6'"
30+
]
31+
32+
[project.urls]
33+
homepage = "http://github.com/sirrobin/module1"
34+
35+
[project.entry-points.flit_test_example]
36+
foo = "module1:main"
37+
38+
[tool.flit.module]
39+
name = "module1a"

0 commit comments

Comments
 (0)