Skip to content

Commit 970b829

Browse files
timfelmsimacek
andauthored
Add GraalPy support (#2859)
Co-authored-by: Michael Simacek <michael.simacek@oracle.com>
1 parent 60a6956 commit 970b829

File tree

15 files changed

+130
-14
lines changed

15 files changed

+130
-14
lines changed

.github/workflows/check.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ jobs:
3232
- pypy-3.10
3333
- pypy-3.9
3434
- pypy-3.8
35+
- graalpy-24.1
3536
os:
3637
- ubuntu-latest
3738
- macos-latest
@@ -41,6 +42,7 @@ jobs:
4142
- { os: macos-latest, py: "brew@3.10" }
4243
- { os: macos-latest, py: "brew@3.9" }
4344
exclude:
45+
- { os: windows-latest, py: "graalpy-24.1" }
4446
- { os: windows-latest, py: "pypy-3.10" }
4547
- { os: windows-latest, py: "pypy-3.9" }
4648
- { os: windows-latest, py: "pypy-3.8" }

docs/changelog/2832.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for `GraalPy <https://github.com/oracle/graalpython>`_.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ optional-dependencies.test = [
6464
"packaging>=23.1",
6565
"pytest>=7.4",
6666
"pytest-env>=0.8.2",
67-
"pytest-freezer>=0.4.8; platform_python_implementation=='PyPy' or (platform_python_implementation=='CPython' and sys_platform=='win32' and python_version>='3.13')",
67+
"pytest-freezer>=0.4.8; platform_python_implementation=='PyPy' or platform_python_implementation=='GraalVM' or (platform_python_implementation=='CPython' and sys_platform=='win32' and python_version>='3.13')",
6868
"pytest-mock>=3.11.1",
6969
"pytest-randomly>=3.12",
7070
"pytest-timeout>=2.1",
@@ -87,6 +87,8 @@ entry-points."virtualenv.create".cpython3-mac-brew = "virtualenv.create.via_glob
8787
entry-points."virtualenv.create".cpython3-mac-framework = "virtualenv.create.via_global_ref.builtin.cpython.mac_os:CPython3macOsFramework"
8888
entry-points."virtualenv.create".cpython3-posix = "virtualenv.create.via_global_ref.builtin.cpython.cpython3:CPython3Posix"
8989
entry-points."virtualenv.create".cpython3-win = "virtualenv.create.via_global_ref.builtin.cpython.cpython3:CPython3Windows"
90+
entry-points."virtualenv.create".graalpy-posix = "virtualenv.create.via_global_ref.builtin.graalpy:GraalPyPosix"
91+
entry-points."virtualenv.create".graalpy-win = "virtualenv.create.via_global_ref.builtin.graalpy:GraalPyWindows"
9092
entry-points."virtualenv.create".pypy3-posix = "virtualenv.create.via_global_ref.builtin.pypy.pypy3:PyPy3Posix"
9193
entry-points."virtualenv.create".pypy3-win = "virtualenv.create.via_global_ref.builtin.pypy.pypy3:Pypy3Windows"
9294
entry-points."virtualenv.create".venv = "virtualenv.create.via_global_ref.venv:Venv"
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
from __future__ import annotations
2+
3+
from abc import ABC
4+
from pathlib import Path
5+
6+
from virtualenv.create.describe import PosixSupports, WindowsSupports
7+
from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen
8+
from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin
9+
10+
11+
class GraalPy(ViaGlobalRefVirtualenvBuiltin, ABC):
12+
@classmethod
13+
def can_describe(cls, interpreter):
14+
return interpreter.implementation == "GraalVM" and super().can_describe(interpreter)
15+
16+
@classmethod
17+
def exe_stem(cls):
18+
return "graalpy"
19+
20+
@classmethod
21+
def exe_names(cls, interpreter):
22+
return {
23+
cls.exe_stem(),
24+
"python",
25+
f"python{interpreter.version_info.major}",
26+
f"python{interpreter.version_info.major}.{interpreter.version_info.minor}",
27+
}
28+
29+
@classmethod
30+
def _executables(cls, interpreter):
31+
host = Path(interpreter.system_executable)
32+
targets = sorted(f"{name}{cls.suffix}" for name in cls.exe_names(interpreter))
33+
yield host, targets, RefMust.NA, RefWhen.ANY
34+
35+
@classmethod
36+
def sources(cls, interpreter):
37+
yield from super().sources(interpreter)
38+
python_dir = Path(interpreter.system_executable).resolve().parent
39+
if python_dir.name in {"bin", "Scripts"}:
40+
python_dir = python_dir.parent
41+
42+
native_lib = cls._native_lib(python_dir / "lib", interpreter.platform)
43+
if native_lib.exists():
44+
yield PathRefToDest(native_lib, dest=lambda self, s: self.bin_dir.parent / "lib" / s.name)
45+
46+
for jvm_dir_name in ("jvm", "jvmlibs", "modules"):
47+
jvm_dir = python_dir / jvm_dir_name
48+
if jvm_dir.exists():
49+
yield PathRefToDest(jvm_dir, dest=lambda self, s: self.bin_dir.parent / s.name)
50+
51+
@classmethod
52+
def _shared_libs(cls, python_dir):
53+
raise NotImplementedError
54+
55+
def set_pyenv_cfg(self):
56+
super().set_pyenv_cfg()
57+
# GraalPy 24.0 and older had home without the bin
58+
version = self.interpreter.version_info
59+
if version.major == 3 and version.minor <= 10: # noqa: PLR2004
60+
home = Path(self.pyenv_cfg["home"])
61+
if home.name == "bin":
62+
self.pyenv_cfg["home"] = str(home.parent)
63+
64+
65+
class GraalPyPosix(GraalPy, PosixSupports):
66+
@classmethod
67+
def _native_lib(cls, lib_dir, platform):
68+
if platform == "darwin":
69+
return lib_dir / "libpythonvm.dylib"
70+
return lib_dir / "libpythonvm.so"
71+
72+
73+
class GraalPyWindows(GraalPy, WindowsSupports):
74+
@classmethod
75+
def _native_lib(cls, lib_dir, _platform):
76+
return lib_dir / "pythonvm.dll"
77+
78+
def set_pyenv_cfg(self):
79+
# GraalPy needs an additional entry in pyvenv.cfg on Windows
80+
super().set_pyenv_cfg()
81+
self.pyenv_cfg["venvlauncher_command"] = self.interpreter.system_executable
82+
83+
84+
__all__ = [
85+
"GraalPyPosix",
86+
"GraalPyWindows",
87+
]

src/virtualenv/discovery/py_info.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import os
1212
import platform
1313
import re
14+
import struct
1415
import sys
1516
import sysconfig
1617
import warnings
@@ -44,7 +45,10 @@ def abs_path(v):
4445

4546
# this is a tuple in earlier, struct later, unify to our own named tuple
4647
self.version_info = VersionInfo(*sys.version_info)
47-
self.architecture = 64 if sys.maxsize > 2**32 else 32
48+
# Use the same implementation as found in stdlib platform.architecture
49+
# to account for platforms where the maximum integer is not equal the
50+
# pointer size.
51+
self.architecture = 32 if struct.calcsize("P") == 4 else 64 # noqa: PLR2004
4852

4953
# Used to determine some file names.
5054
# See `CPython3Windows.python_zip()`.

src/virtualenv/info.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
IMPLEMENTATION = platform.python_implementation()
1010
IS_PYPY = IMPLEMENTATION == "PyPy"
11+
IS_GRAALPY = IMPLEMENTATION == "GraalVM"
1112
IS_CPYTHON = IMPLEMENTATION == "CPython"
1213
IS_WIN = sys.platform == "win32"
1314
IS_MAC_ARM64 = sys.platform == "darwin" and platform.machine() == "arm64"
@@ -55,6 +56,7 @@ def fs_path_id(path: str) -> str:
5556

5657
__all__ = (
5758
"IS_CPYTHON",
59+
"IS_GRAALPY",
5860
"IS_MAC_ARM64",
5961
"IS_PYPY",
6062
"IS_WIN",

tasks/pick_tox_env.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
py = sys.argv[1]
88
if py.startswith("brew@"):
99
py = py[len("brew@") :]
10+
if py.startswith("graalpy-"):
11+
py = "graalpy"
1012
env = f"TOXENV={py}"
1113
if len(sys.argv) > 2: # noqa: PLR2004
1214
env += f"\nTOX_BASEPYTHON={sys.argv[2]}"

tests/conftest.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@
1313

1414
from virtualenv.app_data import AppDataDiskFolder
1515
from virtualenv.discovery.py_info import PythonInfo
16-
from virtualenv.info import IS_PYPY, IS_WIN, fs_supports_symlink
16+
from virtualenv.info import IS_GRAALPY, IS_PYPY, IS_WIN, fs_supports_symlink
1717
from virtualenv.report import LOGGER
1818

1919

2020
def pytest_addoption(parser):
2121
parser.addoption("--int", action="store_true", default=False, help="run integration tests")
22+
parser.addoption("--skip-slow", action="store_true", default=False, help="skip slow tests")
2223

2324

2425
def pytest_configure(config):
@@ -46,6 +47,11 @@ def pytest_collection_modifyitems(config, items):
4647
if item.location[0].startswith(int_location):
4748
item.add_marker(pytest.mark.skip(reason="need --int option to run"))
4849

50+
if config.getoption("--skip-slow"):
51+
for item in items:
52+
if "slow" in [mark.name for mark in item.iter_markers()]:
53+
item.add_marker(pytest.mark.skip(reason="skipped because --skip-slow was passed"))
54+
4955

5056
@pytest.fixture(scope="session")
5157
def has_symlink_support(tmp_path_factory): # noqa: ARG001
@@ -355,7 +361,7 @@ def _skip_if_test_in_system(session_app_data):
355361
pytest.skip("test not valid if run under system")
356362

357363

358-
if IS_PYPY or (IS_WIN and sys.version_info[0:2] >= (3, 13)): # https://github.com/adamchainz/time-machine/issues/456
364+
if IS_PYPY or IS_GRAALPY:
359365

360366
@pytest.fixture
361367
def time_freeze(freezer):

tests/integration/test_zipapp.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
@pytest.fixture(scope="session")
2020
def zipapp_build_env(tmp_path_factory):
2121
create_env_path = None
22-
if CURRENT.implementation != "PyPy":
22+
if CURRENT.implementation not in {"PyPy", "GraalVM"}:
2323
exe = CURRENT.executable # guaranteed to contain a recent enough pip (tox.ini)
2424
else:
2525
create_env_path = tmp_path_factory.mktemp("zipapp-create-env")
@@ -112,6 +112,7 @@ def test_zipapp_help(call_zipapp, capsys):
112112
assert not err
113113

114114

115+
@pytest.mark.slow
115116
@pytest.mark.parametrize("seeder", ["app-data", "pip"])
116117
def test_zipapp_create(call_zipapp, seeder):
117118
call_zipapp("--seeder", seeder)

tests/unit/create/test_creator.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ def test_create_long_path(tmp_path):
398398
subprocess.check_call([str(result.creator.script("pip")), "--version"])
399399

400400

401+
@pytest.mark.slow
401402
@pytest.mark.parametrize("creator", sorted(set(PythonInfo.current_system().creators().key_to_class) - {"builtin"}))
402403
@pytest.mark.usefixtures("session_app_data")
403404
def test_create_distutils_cfg(creator, tmp_path, monkeypatch):

0 commit comments

Comments
 (0)