Skip to content

Commit 33fad77

Browse files
committed
Use PEP 639 license metadata
1 parent d5c40a4 commit 33fad77

File tree

3 files changed

+134
-18
lines changed

3 files changed

+134
-18
lines changed

.github/workflows/buildwheel.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ jobs:
167167
# We don't need to specify ninja as a requirement in pyproject.toml
168168
# because without --no-build-isolation meson-python handles it
169169
# automatically in get_requirements_for_build_wheel().
170-
- run: 'pip install "cython==3.0.11" "meson-python==0.13" "ninja<1.11"'
170+
- run: 'pip install "cython==3.0.11" "meson-python==0.18" "ninja<1.11"'
171171
- run: pip install --no-build-isolation .
172172
- run: python -m flint.test --verbose
173173

bin/cibw_repair_wheel_licenses.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#!/usr/bin/env python
2+
3+
import argparse
4+
from pathlib import Path
5+
from subprocess import run
6+
from tempfile import TemporaryDirectory
7+
from shutil import copyfile
8+
from os import makedirs
9+
10+
11+
def main(*args: str):
12+
"""
13+
Update license information in wheels after running auditwheel etc.
14+
15+
Usage:
16+
cibw_repair_wheel_licenses.py <dest_dir> <wheel_file> \
17+
--license MIT --license-file licenses/license_foo_MIT.txt \
18+
--license BSD-3-Clause --license-file licenses/license_bar_BSD-3-Clause.txt \
19+
...
20+
21+
CWD should be at project root (containing pyproject.toml) and license-file
22+
paths must be relative.
23+
"""
24+
parser = argparse.ArgumentParser()
25+
parser.add_argument("dest_dir")
26+
parser.add_argument("wheel_file")
27+
parser.add_argument("--license", action="append", default=[])
28+
parser.add_argument("--license-file", action="append", default=[])
29+
30+
parsed = parser.parse_args(args)
31+
dest_dir = Path(parsed.dest_dir)
32+
wheel_file = Path(parsed.wheel_file)
33+
license_files = [Path(f) for f in parsed.license_file]
34+
35+
if any(f.is_absolute() for f in license_files):
36+
#
37+
# PEP 639 says:
38+
#
39+
# Inside the root license directory, packaging tools MUST reproduce the
40+
# directory structure under which the source license files are located
41+
# relative to the project root.
42+
#
43+
raise ValueError("license-file paths must be relative to project root")
44+
45+
update_licenses_wheel(dest_dir, wheel_file, parsed.license, license_files)
46+
47+
48+
def update_licenses_wheel(
49+
dest_dir: Path, wheel_file: Path, licenses: list[str], license_files: list[Path]
50+
):
51+
# foo/bar-1.0-cp310-cp310-linux_x86_64.whl -> bar-1.0
52+
name, version = wheel_file.stem.split('-')[:2]
53+
base = f"{name}-{version}"
54+
55+
with TemporaryDirectory() as tmpdir:
56+
run(["wheel", "unpack", "--dest", tmpdir, wheel_file], check=True)
57+
dist_info = Path(tmpdir) / base / f"{base}.dist-info"
58+
print(f"Adding licenses in {dist_info}")
59+
update_license_dist_info(dist_info, licenses, license_files)
60+
run(["wheel", "pack", "--dest-dir", dest_dir, Path(tmpdir) / base], check=True)
61+
62+
63+
def update_license_dist_info(
64+
dist_info: Path, licenses: list[str], license_files: list[Path]
65+
):
66+
for license_file in license_files:
67+
makedirs(dist_info / license_file.parent, exist_ok=True)
68+
copyfile(license_file, dist_info / license_file)
69+
print(f"Added license file {license_file}")
70+
71+
metadata_file = dist_info / "METADATA"
72+
73+
with open(metadata_file, "r") as f:
74+
lines = f.readlines()
75+
76+
for n, line in enumerate(lines):
77+
if line.startswith("License-Expression: "):
78+
base_license = line[len("License-Expression: ") :].strip()
79+
all_licenses = [base_license, *licenses]
80+
expression = ' AND '.join([f"({license})" for license in all_licenses])
81+
lines[n] = f"License-Expression: {expression}\n"
82+
break
83+
else:
84+
raise ValueError("Could not find License-Expression in METADATA")
85+
86+
print("Updated License-Expression from")
87+
print(" " + base_license)
88+
print("to")
89+
print(" " + expression)
90+
91+
license_files_lines = [line for line in lines if line.startswith("License-File: ")]
92+
93+
if not license_files_lines:
94+
raise ValueError("Could not find License-File in METADATA")
95+
96+
last_index = lines.index(license_files_lines[-1])
97+
new_lines = [f"License-File: {f}\n" for f in license_files]
98+
lines = lines[:last_index] + new_lines + lines[last_index:]
99+
100+
with open(metadata_file, "w") as f:
101+
f.writelines(lines)
102+
103+
104+
if __name__ == "__main__":
105+
import sys
106+
sys.exit(main(*sys.argv[1:]))

pyproject.toml

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22
name = "python-flint"
33
description = "Bindings for FLINT"
44
version = "0.8.0"
5+
56
# This needs to be in sync with README, and CI config.
67
requires-python = ">= 3.11"
78
authors = [
89
{name = "Fredrik Johansson", email = "[email protected]"},
910
{name = "Oscar Benjamin", email = "[email protected]"},
1011
]
11-
license = {file = "LICENSE"}
12+
13+
license = "MIT"
14+
license-files = ["LICENSE"]
15+
1216
classifiers = [
1317
"Topic :: Scientific/Engineering :: Mathematics",
1418
]
@@ -37,7 +41,7 @@ content-type = "text/markdown"
3741
# fine. It is not possible to have a separate version constraint here for the
3842
# freethreading build only though.
3943
#
40-
requires = ["meson-python >= 0.13", "cython >=3.1,<3.2"]
44+
requires = ["meson-python >= 0.18", "cython >=3.1,<3.2"]
4145
build-backend = "mesonpy"
4246

4347
[tool.cython-lint]
@@ -108,24 +112,30 @@ PKG_CONFIG_PATH = "$(pwd)/.local/lib/pkgconfig"
108112

109113
[tool.cibuildwheel.linux]
110114
before-all = "bin/cibw_before_all_linux_$(uname -m).sh"
115+
before-build = "pip install wheel auditwheel"
116+
repair-wheel-command = [
117+
"auditwheel repair -w {dest_dir} {wheel}",
118+
"""bin/cibw_repair_wheel_licenses.py {dest_dir} {wheel} \
119+
--license-files wheels/LICENSE_linux_wheels.txt \
120+
""",
121+
]
111122

112123
[tool.cibuildwheel.macos]
113124
before-all = "bin/cibw_before_all_macosx_$(uname -m).sh"
125+
before-build = "pip install wheel delocate"
126+
repair-wheel-command = [
127+
"delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel}",
128+
"""bin/cibw_repair_wheel_licenses.py {dest_dir} {wheel} \
129+
--license-files wheels/LICENSE_macosx_wheels.txt \
130+
""",
131+
]
114132

115133
[tool.cibuildwheel.windows]
116134
before-all = "C:\\msys64\\usr\\bin\\bash bin/cibw_before_all_windows.sh"
117-
before-build = "pip install delvewheel"
118-
repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --add-path .local/bin"
119-
120-
# Previously with setuptools and MinGW it was necessary to run
121-
# bin/cibw_before_build_windows.sh before building the wheel to create the
122-
# libpython*.a files. This is no longer necessary now meson is used:
123-
#
124-
# before-build = "pip install delvewheel && C:\\msys64\\usr\\bin\\bash bin/cibw_before_build_windows.sh"
125-
#
126-
# Previously a custom delvewheel command was needed because delvewheel would
127-
# reject binaries created with MinGW unless they had been stripped. This is not
128-
# needed any more with newer versions of delvewheel:
129-
#
130-
# repair-wheel-command = "bin\\cibw_repair_wheel_command_windows.bat {dest_dir} {wheel}"
131-
#
135+
before-build = "pip install wheel delvewheel"
136+
repair-wheel-command = [
137+
"delvewheel repair -w {dest_dir} {wheel} --add-path .local/bin",
138+
"""bin/cibw_repair_wheel_licenses.py {dest_dir} {wheel} \
139+
--license-files wheels/LICENSE_windows_wheels.txt \
140+
""",
141+
]

0 commit comments

Comments
 (0)