Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 80668a0

Browse files
committedJul 16, 2024·
Add support for GraalPy
1 parent de84624 commit 80668a0

10 files changed

+130
-18
lines changed
 

‎README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ What does it do?
3636
| PyPy 3.8 v7.3 |||| N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
3737
| PyPy 3.9 v7.3 |||| N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
3838
| PyPy 3.10 v7.3 |||| N/A | N/A | ✅¹ | ✅¹ | ✅¹ | N/A | N/A | N/A |
39+
| GraalPy 24.0 |||| N/A | N/A | ✅¹ | N/A | ✅¹ | N/A | N/A | N/A |
3940

40-
<sup>¹ PyPy is only supported for manylinux wheels.</sup><br>
41+
<sup>¹ PyPy & GraalPy are only supported for manylinux wheels.</sup><br>
4142
<sup>² Windows arm64 support is experimental.</sup><br>
4243
<sup>³ CPython 3.13 is available using the [`CIBW_PRERELEASE_PYTHONS`](https://cibuildwheel.pypa.io/en/stable/options/#prerelease-pythons) option. Free-threaded mode requires opt-in.</sup><br>
4344
<sup>⁴ Experimental, not yet supported on PyPI, but can be used directly in web deployment. Use `--platform pyodide` to build.</sup><br>
4445

45-
- Builds manylinux, musllinux, macOS 10.9+, and Windows wheels for CPython and PyPy
46+
- Builds manylinux, musllinux, macOS 10.9+, and Windows wheels for CPython, PyPy, and GraalPy
4647
- Works on GitHub Actions, Azure Pipelines, Travis CI, AppVeyor, CircleCI, GitLab CI, and Cirrus CI
4748
- Bundles shared library dependencies on Linux and macOS through [auditwheel](https://github.com/pypa/auditwheel) and [delocate](https://github.com/matthew-brett/delocate)
4849
- Runs your library's tests against the wheel-installed version of your library

‎bin/update_pythons.py

+80-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import copy
66
import difflib
77
import logging
8+
import re
89
from collections.abc import Mapping, MutableMapping
910
from pathlib import Path
1011
from typing import Any, Final, Literal, TypedDict, Union
@@ -44,13 +45,19 @@ class ConfigWinPP(TypedDict):
4445
url: str
4546

4647

48+
class ConfigWinGP(TypedDict):
49+
identifier: str
50+
version: str
51+
url: str
52+
53+
4754
class ConfigMacOS(TypedDict):
4855
identifier: str
4956
version: str
5057
url: str
5158

5259

53-
AnyConfig = Union[ConfigWinCP, ConfigWinPP, ConfigMacOS]
60+
AnyConfig = Union[ConfigWinCP, ConfigWinPP, ConfigWinGP, ConfigMacOS]
5461

5562

5663
# The following set of "Versions" classes allow the initial call to the APIs to
@@ -106,6 +113,72 @@ def update_version_windows(self, spec: Specifier) -> ConfigWinCP | None:
106113
)
107114

108115

116+
class GraalPyVersions:
117+
def __init__(self):
118+
response = requests.get("https://api.github.com/repos/oracle/graalpython/releases")
119+
response.raise_for_status()
120+
121+
releases = response.json()
122+
gp_version_re = re.compile(r"-(\d+\.\d+\.\d+)$")
123+
cp_version_re = re.compile(r"Python (\d+\.\d+(?:\.\d+)?)")
124+
for release in releases:
125+
m = gp_version_re.search(release["tag_name"])
126+
if m:
127+
release["graalpy_version"] = Version(m.group(1))
128+
m = cp_version_re.search(release["body"])
129+
if m:
130+
release["python_version"] = Version(m.group(1))
131+
132+
self.releases = [r for r in releases if "graalpy_version" in r and "python_version" in r]
133+
134+
def update_version(self, identifier: str, spec: Specifier) -> AnyConfig:
135+
if "x86_64" in identifier or "amd64" in identifier:
136+
arch = "x86_64"
137+
elif "arm64" in identifier or "aarch64" in identifier:
138+
arch = "aarch64"
139+
else:
140+
msg = f"{identifier} not supported yet on GraalPy"
141+
raise RuntimeError(msg)
142+
143+
releases = [r for r in self.releases if spec.contains(r["python_version"])]
144+
releases = sorted(releases, key=lambda r: r["graalpy_version"])
145+
146+
if not releases:
147+
msg = f"GraalPy {arch} not found for {spec}!"
148+
raise RuntimeError(msg)
149+
150+
release = releases[-1]
151+
version = release["python_version"]
152+
gpversion = release["graalpy_version"]
153+
154+
if "macosx" in identifier:
155+
arch = "x86_64" if "x86_64" in identifier else "arm64"
156+
identifier = f"gp{gpversion.major}{gpversion.minor}-macosx_{arch}"
157+
config = ConfigMacOS
158+
platform = "macos"
159+
elif "win" in identifier:
160+
arch = "amd64" if "x86_64" in identifier else "arm64"
161+
identifier = f"gp{gpversion.major}{gpversion.minor}-win_{arch}"
162+
config = ConfigWinGP
163+
platform = "linux"
164+
else:
165+
msg = "GraalPy provides downloads for macOS and Windows and is included for manylinux"
166+
raise RuntimeError(msg)
167+
168+
arch = "amd64" if arch == "x86_64" else "aarch64"
169+
(url,) = (
170+
rf["browser_download_url"]
171+
for rf in release["assets"]
172+
if rf["name"].endswith(f"{platform}-{arch}.tar.gz")
173+
)
174+
175+
return config(
176+
identifier=identifier,
177+
version=f"{version.major}.{version.minor}",
178+
url=url,
179+
)
180+
181+
109182
class PyPyVersions:
110183
def __init__(self, arch_str: ArchStr):
111184
response = requests.get("https://downloads.python.org/pypy/versions.json")
@@ -250,6 +323,8 @@ def __init__(self) -> None:
250323
self.macos_pypy = PyPyVersions("64")
251324
self.macos_pypy_arm64 = PyPyVersions("ARM64")
252325

326+
self.graalpy = GraalPyVersions()
327+
253328
def update_config(self, config: MutableMapping[str, str]) -> None:
254329
identifier = config["identifier"]
255330
version = Version(config["version"])
@@ -267,6 +342,8 @@ def update_config(self, config: MutableMapping[str, str]) -> None:
267342
config_update = self.macos_pypy.update_version_macos(spec)
268343
elif "macosx_arm64" in identifier:
269344
config_update = self.macos_pypy_arm64.update_version_macos(spec)
345+
elif identifier.startswith("gp"):
346+
config_update = self.graalpy.update_version(identifier, spec)
270347
elif "t-win32" in identifier and identifier.startswith("cp"):
271348
config_update = self.windows_t_32.update_version_windows(spec)
272349
elif "win32" in identifier and identifier.startswith("cp"):
@@ -278,6 +355,8 @@ def update_config(self, config: MutableMapping[str, str]) -> None:
278355
config_update = self.windows_64.update_version_windows(spec)
279356
elif identifier.startswith("pp"):
280357
config_update = self.windows_pypy_64.update_version_windows(spec)
358+
elif identifier.startswith("gp"):
359+
config_update = self.graalpy.update_version(identifier, spec)
281360
elif "t-win_arm64" in identifier and identifier.startswith("cp"):
282361
config_update = self.windows_t_arm64.update_version_windows(spec)
283362
elif "win_arm64" in identifier and identifier.startswith("cp"):

‎cibuildwheel/logger.py

+2
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ def build_description_from_identifier(identifier: str) -> str:
212212
build_description += "CPython"
213213
elif python_interpreter == "pp":
214214
build_description += "PyPy"
215+
elif python_interpreter == "gp":
216+
build_description += "GraalPy"
215217
else:
216218
msg = f"unknown python {python_interpreter!r}"
217219
raise Exception(msg)

‎cibuildwheel/macos.py

+18
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,22 @@ def install_pypy(tmp: Path, url: str) -> Path:
193193
return installation_path / "bin" / "pypy3"
194194

195195

196+
def install_graalpy(tmp: Path, url: str) -> Path:
197+
graalpy_archive = url.rsplit("/", 1)[-1]
198+
extension = ".tar.gz"
199+
assert graalpy_archive.endswith(extension)
200+
installation_path = CIBW_CACHE_PATH / graalpy_archive[: -len(extension)]
201+
with FileLock(str(installation_path) + ".lock"):
202+
if not installation_path.exists():
203+
downloaded_archive = tmp / graalpy_archive
204+
download(url, downloaded_archive)
205+
installation_path.mkdir(parents=True)
206+
# GraalPy top-folder name is inconsistent with archive name
207+
call("tar", "-C", installation_path, "--strip-components=1", "-xzf", downloaded_archive)
208+
downloaded_archive.unlink()
209+
return installation_path / "bin" / "graalpy"
210+
211+
196212
def setup_python(
197213
tmp: Path,
198214
python_configuration: PythonConfiguration,
@@ -219,6 +235,8 @@ def setup_python(
219235

220236
elif implementation_id.startswith("pp"):
221237
base_python = install_pypy(tmp, python_configuration.url)
238+
elif implementation_id.startswith("gp"):
239+
base_python = install_graalpy(tmp, python_configuration.url)
222240
else:
223241
msg = "Unknown Python implementation"
224242
raise ValueError(msg)

‎cibuildwheel/options.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ def globals(self) -> GlobalOptions:
512512
package_dir = args.package_dir
513513
output_dir = args.output_dir
514514

515-
build_config = self.reader.get("build", env_plat=False, list_sep=" ") or "*"
515+
build_config = self.reader.get("build", env_plat=False, list_sep=" ") or "[!g]*"
516516
skip_config = self.reader.get("skip", env_plat=False, list_sep=" ")
517517
test_skip = self.reader.get("test-skip", env_plat=False, list_sep=" ")
518518

‎cibuildwheel/resources/build-platforms.toml

+5
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ python_configurations = [
2222
{ identifier = "pp38-manylinux_x86_64", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" },
2323
{ identifier = "pp39-manylinux_x86_64", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" },
2424
{ identifier = "pp310-manylinux_x86_64", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" },
25+
{ identifier = "gp240-manylinux_x86_64", version = "3.10", path_str = "/opt/python/graalpy310-graalpy240_310_native" },
2526
{ identifier = "cp36-manylinux_aarch64", version = "3.6", path_str = "/opt/python/cp36-cp36m" },
2627
{ identifier = "cp37-manylinux_aarch64", version = "3.7", path_str = "/opt/python/cp37-cp37m" },
2728
{ identifier = "cp38-manylinux_aarch64", version = "3.8", path_str = "/opt/python/cp38-cp38" },
@@ -53,6 +54,7 @@ python_configurations = [
5354
{ identifier = "pp38-manylinux_aarch64", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" },
5455
{ identifier = "pp39-manylinux_aarch64", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" },
5556
{ identifier = "pp310-manylinux_aarch64", version = "3.10", path_str = "/opt/python/pp310-pypy310_pp73" },
57+
{ identifier = "gp240-manylinux_aarch64", version = "3.10", path_str = "/opt/python/graalpy310-graalpy240_310_native" },
5658
{ identifier = "pp37-manylinux_i686", version = "3.7", path_str = "/opt/python/pp37-pypy37_pp73" },
5759
{ identifier = "pp38-manylinux_i686", version = "3.8", path_str = "/opt/python/pp38-pypy38_pp73" },
5860
{ identifier = "pp39-manylinux_i686", version = "3.9", path_str = "/opt/python/pp39-pypy39_pp73" },
@@ -136,6 +138,8 @@ python_configurations = [
136138
{ identifier = "pp39-macosx_arm64", version = "3.9", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-macos_arm64.tar.bz2" },
137139
{ identifier = "pp310-macosx_x86_64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.16-macos_x86_64.tar.bz2" },
138140
{ identifier = "pp310-macosx_arm64", version = "3.10", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.16-macos_arm64.tar.bz2" },
141+
{ identifier = "gp240-macosx_x86_64", version = "3.10", url = "https://github.com/oracle/graalpython/releases/download/graal-24.0.1/graalpy-24.0.1-macos-amd64.tar.gz" },
142+
{ identifier = "gp240-macosx_arm64", version = "3.10", url = "https://github.com/oracle/graalpython/releases/download/graal-24.0.1/graalpy-24.0.1-macos-aarch64.tar.gz" },
139143
]
140144

141145
[windows]
@@ -168,6 +172,7 @@ python_configurations = [
168172
{ identifier = "pp38-win_amd64", version = "3.8", arch = "64", url = "https://downloads.python.org/pypy/pypy3.8-v7.3.11-win64.zip" },
169173
{ identifier = "pp39-win_amd64", version = "3.9", arch = "64", url = "https://downloads.python.org/pypy/pypy3.9-v7.3.16-win64.zip" },
170174
{ identifier = "pp310-win_amd64", version = "3.10", arch = "64", url = "https://downloads.python.org/pypy/pypy3.10-v7.3.16-win64.zip" },
175+
{ identifier = "gp240-win_amd64", version = "3.10", arch = "64", url = "https://github.com/oracle/graalpython/releases/download/graal-24.0.1/graalpy-24.0.1-windows-amd64.tar.gz" },
171176
]
172177

173178
[pyodide]

‎test/test_abi_variants.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ def test_abi3(tmp_path):
5454
actual_wheels = utils.cibuildwheel_run(
5555
project_dir,
5656
add_env={
57-
# free_threaded and PyPy do not have a Py_LIMITED_API equivalent, just build one of those
57+
# free_threaded, GraalPy, and PyPy do not have a Py_LIMITED_API equivalent, just build one of those
5858
# also limit the number of builds for test performance reasons
59-
"CIBW_BUILD": f"cp39-* cp310-* pp310-* {single_python_tag}-* cp313t-*"
59+
"CIBW_BUILD": f"cp39-* cp310-* pp310-* gp240-* {single_python_tag}-* cp313t-*"
6060
},
6161
)
6262

‎test/utils.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ def expected_wheels(
205205
"pp38-pypy38_pp73",
206206
"pp39-pypy39_pp73",
207207
"pp310-pypy310_pp73",
208+
"graalpy310-graalpy240_310_native",
208209
]
209210

210211
if platform == "macos" and machine_arch == "arm64":
@@ -220,6 +221,7 @@ def expected_wheels(
220221
"pp38-pypy38_pp73",
221222
"pp39-pypy39_pp73",
222223
"pp310-pypy310_pp73",
224+
"graalpy310-graalpy240_310_native",
223225
]
224226

225227
if single_python:
@@ -246,7 +248,7 @@ def expected_wheels(
246248
if platform == "linux":
247249
architectures = [arch_name_for_linux(machine_arch)]
248250

249-
if machine_arch == "x86_64" and not single_arch:
251+
if machine_arch == "x86_64" and not single_arch and not python_abi_tag.startswith("graalpy"):
250252
architectures.append("i686")
251253

252254
if len(manylinux_versions) > 0:
@@ -257,7 +259,7 @@ def expected_wheels(
257259
)
258260
for architecture in architectures
259261
]
260-
if len(musllinux_versions) > 0 and not python_abi_tag.startswith("pp"):
262+
if len(musllinux_versions) > 0 and not python_abi_tag.startswith(("pp", "graalpy")):
261263
platform_tags.extend(
262264
[
263265
".".join(

‎unit_test/linux_build_steps_test.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def test_linux_container_split(tmp_path: Path, monkeypatch):
2424
manylinux-x86_64-image = "normal_container_image"
2525
manylinux-i686-image = "normal_container_image"
2626
build = "*-manylinux_x86_64"
27-
skip = "pp*"
27+
skip = "[gp]p*"
2828
archs = "x86_64 i686"
2929
3030
[[tool.cibuildwheel.overrides]]

‎unit_test/option_prepare_test.py

+14-9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"pp38",
2626
"pp39",
2727
"pp310",
28+
"gp240",
2829
}
2930

3031

@@ -83,7 +84,7 @@ def test_build_default_launches(monkeypatch):
8384
assert kwargs["container"]["enforce_32_bit"]
8485

8586
identifiers = {x.identifier for x in kwargs["platform_configs"]}
86-
assert identifiers == {f"{x}-manylinux_i686" for x in ALL_IDS}
87+
assert identifiers == {f"{x}-manylinux_i686" for x in ALL_IDS if "gp" not in x}
8788

8889
kwargs = build_in_container.call_args_list[2][1]
8990
assert "quay.io/pypa/musllinux_1_2_x86_64" in kwargs["container"]["image"]
@@ -92,7 +93,7 @@ def test_build_default_launches(monkeypatch):
9293

9394
identifiers = {x.identifier for x in kwargs["platform_configs"]}
9495
assert identifiers == {
95-
f"{x}-musllinux_x86_64" for x in ALL_IDS for x in ALL_IDS if "pp" not in x
96+
f"{x}-musllinux_x86_64" for x in ALL_IDS for x in ALL_IDS if "pp" not in x and "gp" not in x
9697
}
9798

9899
kwargs = build_in_container.call_args_list[3][1]
@@ -101,7 +102,9 @@ def test_build_default_launches(monkeypatch):
101102
assert kwargs["container"]["enforce_32_bit"]
102103

103104
identifiers = {x.identifier for x in kwargs["platform_configs"]}
104-
assert identifiers == {f"{x}-musllinux_i686" for x in ALL_IDS if "pp" not in x}
105+
assert identifiers == {
106+
f"{x}-musllinux_i686" for x in ALL_IDS if "pp" not in x and "gp" not in x
107+
}
105108

106109

107110
@pytest.mark.usefixtures("mock_build_container")
@@ -155,7 +158,7 @@ def test_build_with_override_launches(monkeypatch, tmp_path):
155158
identifiers = {x.identifier for x in kwargs["platform_configs"]}
156159
assert identifiers == {
157160
f"{x}-manylinux_x86_64"
158-
for x in ALL_IDS - {"cp36", "cp310", "cp311", "cp312", "pp37", "pp38", "pp39", "pp310"}
161+
for x in ALL_IDS - {"cp36", "cp310", "cp311", "cp312", "pp37", "pp38", "pp39", "pp310", "gp240"}
159162
}
160163
assert kwargs["options"].build_options("cp37-manylinux_x86_64").before_all == ""
161164

@@ -166,7 +169,7 @@ def test_build_with_override_launches(monkeypatch, tmp_path):
166169
identifiers = {x.identifier for x in kwargs["platform_configs"]}
167170
assert identifiers == {
168171
f"{x}-manylinux_x86_64"
169-
for x in ["cp310", "cp311", "cp312", "pp37", "pp38", "pp39", "pp310"]
172+
for x in ["cp310", "cp311", "cp312", "pp37", "pp38", "pp39", "pp310", "gp240"]
170173
}
171174

172175
kwargs = build_in_container.call_args_list[3][1]
@@ -175,7 +178,7 @@ def test_build_with_override_launches(monkeypatch, tmp_path):
175178
assert kwargs["container"]["enforce_32_bit"]
176179

177180
identifiers = {x.identifier for x in kwargs["platform_configs"]}
178-
assert identifiers == {f"{x}-manylinux_i686" for x in ALL_IDS}
181+
assert identifiers == {f"{x}-manylinux_i686" for x in ALL_IDS if "gp" not in x}
179182

180183
kwargs = build_in_container.call_args_list[4][1]
181184
assert "quay.io/pypa/musllinux_1_1_x86_64" in kwargs["container"]["image"]
@@ -184,7 +187,7 @@ def test_build_with_override_launches(monkeypatch, tmp_path):
184187

185188
identifiers = {x.identifier for x in kwargs["platform_configs"]}
186189
assert identifiers == {
187-
f"{x}-musllinux_x86_64" for x in ALL_IDS & {"cp36", "cp37", "cp38", "cp39"} if "pp" not in x
190+
f"{x}-musllinux_x86_64" for x in ALL_IDS & {"cp36", "cp37", "cp38", "cp39"} if "pp" not in x and "gp" not in x
188191
}
189192

190193
kwargs = build_in_container.call_args_list[5][1]
@@ -193,7 +196,7 @@ def test_build_with_override_launches(monkeypatch, tmp_path):
193196
assert not kwargs["container"]["enforce_32_bit"]
194197
identifiers = {x.identifier for x in kwargs["platform_configs"]}
195198
assert identifiers == {
196-
f"{x}-musllinux_x86_64" for x in ALL_IDS - {"cp36", "cp37", "cp38", "cp39"} if "pp" not in x
199+
f"{x}-musllinux_x86_64" for x in ALL_IDS - {"cp36", "cp37", "cp38", "cp39"} if "pp" not in x and "gp" not in x
197200
}
198201

199202
kwargs = build_in_container.call_args_list[6][1]
@@ -202,4 +205,6 @@ def test_build_with_override_launches(monkeypatch, tmp_path):
202205
assert kwargs["container"]["enforce_32_bit"]
203206

204207
identifiers = {x.identifier for x in kwargs["platform_configs"]}
205-
assert identifiers == {f"{x}-musllinux_i686" for x in ALL_IDS if "pp" not in x}
208+
assert identifiers == {
209+
f"{x}-musllinux_i686" for x in ALL_IDS if "pp" not in x and "gp" not in x
210+
}

0 commit comments

Comments
 (0)
Please sign in to comment.