Skip to content

Commit e82453f

Browse files
committed
Add License-File field to Python package info
To follow the source layout required by PEP 639, we need to move the licenses directory within the wheel to *.dist-info/licenses/licenses. We also need to add COPYING.LESSER, since it's included in the sdist. (This is a bugfix in any case, since that license governs the artifacts in artifacts/python.) Signed-off-by: Benjamin Gilbert <[email protected]>
1 parent 9381cce commit e82453f

File tree

5 files changed

+55
-7
lines changed

5 files changed

+55
-7
lines changed

artifacts/python/meson.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ py_artifacts = [
1616
build_always_stale : true,
1717
env : env,
1818
),
19-
files('py.typed'),
19+
files(meson.project_source_root() / 'COPYING.LESSER', 'py.typed'),
2020
libopenslide_postprocessed,
2121
licenses,
2222
]

artifacts/python/pyproject.in.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ maintainers = [
77
description = "Binary build of OpenSlide"
88
readme = "artifacts/python/README.md"
99
license = "@spdx@"
10+
license-files = "@license-files@"
1011
keywords = ["OpenSlide", "whole-slide image", "virtual slide", "library"]
1112
classifiers = [
1213
"Development Status :: 5 - Production/Stable",

artifacts/write-wheel.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,16 @@ class Args(TypedArgs):
7070
whl.metadir / 'METADATA', BytesIO(meta.as_bytes())
7171
)
7272
)
73-
elif path.name == 'licenses':
74-
whl.add_tree(whl.metadir, path)
73+
elif path.name in ('COPYING.LESSER', 'licenses'):
74+
# Assume the file or dir is in the root of the sdist.
75+
# Write licenses directory to {metadir}/licenses/licenses,
76+
# as required by PEP 639.
77+
if path.is_dir():
78+
whl.add_tree(whl.metadir / 'licenses', path)
79+
else:
80+
whl.add(
81+
FileMember(whl.metadir / 'licenses' / path.name, fh)
82+
)
7583
else:
7684
name = re.sub('(\\.so\\.[0-9]+)\\.[0-9.]+', '\\1', path.name)
7785
whl.add(FileMember(whl.datadir / name, fh))

common/python.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#
22
# Tools for building OpenSlide and its dependencies
33
#
4-
# Copyright (c) 2023 Benjamin Gilbert
4+
# Copyright (c) 2023-2025 Benjamin Gilbert
55
# All rights reserved.
66
#
77
# This script is free software: you can redistribute it and/or modify it
@@ -21,6 +21,8 @@
2121

2222
from email.message import Message
2323
from email.policy import Compat32
24+
from pathlib import PurePath
25+
import re
2426
import tomllib
2527

2628
from .meson import meson_introspect, meson_source_root
@@ -29,8 +31,30 @@
2931

3032
def pyproject_fill_template(tmpl: str) -> str:
3133
version: str = meson_introspect('projectinfo')['version']
32-
return tmpl.replace('@version@', version).replace(
33-
'@spdx@', get_spdx(Project.get_enabled())
34+
projs = Project.get_enabled()
35+
license_relpaths = [PurePath('COPYING.LESSER')] + [
36+
PurePath('licenses') / relpath
37+
for proj in projs
38+
for relpath in proj.license_relpaths
39+
]
40+
for relpath in license_relpaths:
41+
# we don't do proper toml assembly
42+
if '"' in relpath.as_posix():
43+
raise ValueError(f'Invalid license file path: {relpath}')
44+
return (
45+
tmpl.replace('@version@', version)
46+
.replace('@spdx@', get_spdx(projs))
47+
.replace(
48+
'"@license-files@"',
49+
'["'
50+
+ '", "'.join(
51+
sorted(
52+
(relpath.as_posix() for relpath in license_relpaths),
53+
key=lambda s: s.lower(),
54+
)
55+
)
56+
+ '"]',
57+
)
3458
)
3559

3660

@@ -71,6 +95,13 @@ def pyproject_to_message(pyproject: str) -> Message:
7195
out['Maintainer-Email'] = f'{item["name"]} <{item["email"]}>'
7296
elif k == 'license':
7397
out['License-Expression'] = v
98+
elif k == 'license-files':
99+
for vv in v:
100+
if re.search(r'\*|\?|\[|\.\.', vv):
101+
raise ValueError(
102+
f'Glob or path traversal in license file path: {vv}'
103+
)
104+
out['License-File'] = vv
74105
elif k == 'classifiers':
75106
for vv in v:
76107
out['Classifier'] = vv

common/software.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
from itertools import count
2929
import json
3030
import os
31-
from pathlib import Path
31+
from pathlib import Path, PurePath
3232
import shlex
3333
import shutil
3434
import subprocess
@@ -235,6 +235,14 @@ def spdx(self) -> str:
235235
else:
236236
raise ValueError(f'SPDX override needed for {self.id}')
237237

238+
@property
239+
def license_relpaths(self) -> list[PurePath]:
240+
base = PurePath(self.display)
241+
return [
242+
base / (f(self)[0] if callable(f) else Path(f).name)
243+
for f in self.license_files
244+
]
245+
238246
def write_license_files(self, base: Path) -> None:
239247
dir = base / self.display
240248
dir.mkdir(parents=True)

0 commit comments

Comments
 (0)