Skip to content

Commit b2b6314

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

File tree

3 files changed

+146
-18
lines changed

3 files changed

+146
-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: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
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 <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("wheel_file")
26+
parser.add_argument("--license", action="append", default=[])
27+
parser.add_argument("--license-file", action="append", default=[])
28+
29+
parsed = parser.parse_args(args)
30+
wheel_file = Path(parsed.wheel_file)
31+
licenses = parsed.license
32+
license_files = [Path(f) for f in parsed.license_file]
33+
34+
if any(f.is_absolute() for f in license_files):
35+
#
36+
# PEP 639 says:
37+
#
38+
# Inside the root license directory, packaging tools MUST reproduce the
39+
# directory structure under which the source license files are located
40+
# relative to the project root.
41+
#
42+
raise ValueError("license-file paths must be relative to project root")
43+
44+
update_licenses_wheel(wheel_file, licenses, license_files)
45+
46+
47+
def update_licenses_wheel(
48+
wheel_file: Path, licenses: list[str], license_files: list[Path]
49+
):
50+
# foo/bar-1.0-cp310-cp310-linux_x86_64.whl -> bar-1.0
51+
name, version = wheel_file.stem.split('-')[:2]
52+
base = f"{name}-{version}"
53+
54+
with TemporaryDirectory() as tmpdir:
55+
56+
run(["wheel", "unpack", "--dest", tmpdir, wheel_file], check=True)
57+
dist_info = Path(tmpdir) / base / f"{base}.dist-info"
58+
59+
print(f"Adding licenses in {dist_info}")
60+
update_license_dist_info(dist_info, licenses, license_files)
61+
62+
run(["wheel", "pack", Path(tmpdir) / base], check=True)
63+
64+
# glob for *.whl in tmpdir
65+
wheels = list(Path(tmpdir).glob(f"{base}-*.whl"))
66+
if len(wheels) != 1:
67+
raise ValueError(f"Expected one wheel in {tmpdir}, got {wheels}")
68+
new_wheel_file = wheels[0]
69+
70+
print(f"Repaired wheel: {new_wheel_file}")
71+
print(f"Copying to {wheel_file}")
72+
copyfile(new_wheel_file, wheel_file)
73+
74+
75+
def update_license_dist_info(
76+
dist_info: Path, licenses: list[str], license_files: list[Path]
77+
):
78+
for license_file in license_files:
79+
makedirs(dist_info / license_file.parent, exist_ok=True)
80+
copyfile(license_file, dist_info / license_file)
81+
print(f"Added license file {license_file}")
82+
83+
metadata_file = dist_info / "METADATA"
84+
85+
with open(metadata_file, "r") as f:
86+
lines = f.readlines()
87+
88+
for n, line in enumerate(lines):
89+
if line.startswith("License-Expression: "):
90+
base_license = line[len("License-Expression: ") :].strip()
91+
all_licenses = [base_license, *licenses]
92+
expression = ' AND '.join([f"({license})" for license in all_licenses])
93+
lines[n] = f"License-Expression: {expression}\n"
94+
break
95+
else:
96+
raise ValueError("Could not find License-Expression in METADATA")
97+
98+
print("Updated License-Expression from")
99+
print(" " + base_license)
100+
print("to")
101+
print(" " + expression)
102+
103+
license_files_lines = [line for line in lines if line.startswith("License-File: ")]
104+
105+
if not license_files_lines:
106+
raise ValueError("Could not find License-File in METADATA")
107+
108+
last_index = lines.index(license_files_lines[-1])
109+
new_lines = [f"License-File: {f}\n" for f in license_files]
110+
lines = lines[:last_index] + new_lines + lines[last_index:]
111+
112+
with open(metadata_file, "w") as f:
113+
f.writelines(lines)
114+
115+
116+
if __name__ == "__main__":
117+
import sys
118+
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-file 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-file wheels/LICENSE_macos_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-file wheels/LICENSE_windows_wheels.txt \
140+
""",
141+
]

0 commit comments

Comments
 (0)