Skip to content

Commit f0d4ebe

Browse files
committed
[GR-60609] Inject graalpy-virtualenv dependency into virtualenv
PullRequest: graalpython/3621
2 parents 0c2eca6 + 1149956 commit f0d4ebe

File tree

5 files changed

+120
-86
lines changed

5 files changed

+120
-86
lines changed

graalpy_virtualenv/graalpy_virtualenv/graalpy.py

Lines changed: 89 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -42,84 +42,99 @@
4242
from pathlib import Path
4343
from subprocess import check_output as subprocess_check_output
4444

45-
from virtualenv.create.creator import Creator
46-
from virtualenv.create.describe import PosixSupports, WindowsSupports
4745
from virtualenv.seed.embed.pip_invoke import PipInvoke
4846
from virtualenv.seed.wheels import get_wheel
4947
from virtualenv.seed.wheels.bundle import from_dir
5048

51-
52-
class AbstractGraalPyCreator(Creator):
53-
"""
54-
Describe and fake Creator service for GraalPy.
55-
56-
For the time being, we expect users to just use the builtin 'venv' creator
57-
with GraalPy, but the virtualenv package cannot just support GraalPy as a
58-
generic "venv capable python", because it needs a Describe service for the
59-
Seeder and Activator services.
60-
61-
Note: that even if we provided GraalPy specific Creator service, the builtin
62-
'venv' Creator takes precedence as the default. Therefore, the users would
63-
have to enable the GraalPy Creator via explicit option `--creator graalpy`.
64-
65-
Note: GraalPy cannot just simply pretend that it is CPython, because then the
66-
virtualenv Creator would not set up the toolchain executables in the new virtual
67-
environment.
68-
"""
69-
70-
def __init__(self, options, interpreter):
71-
super().__init__(options, interpreter)
72-
self.options = options
73-
74-
@classmethod
75-
def can_create(cls, interpreter):
76-
# We are fake creator, we actually do not want to be used as Creator
77-
return None
78-
79-
@classmethod
80-
def can_describe(cls, interpreter):
81-
if not (interpreter.implementation == "GraalVM" and super().can_describe(interpreter)):
82-
return False
83-
84-
# We monkey patch SeederSelector to use our ensurepip, but only if we know that there
85-
# is a chance that we are creating virtualenv for GraalPy, so that we do not mess up
86-
# with virtualenv if not necessary. This relies on the fact that virtualenv calls this
87-
# because it calls SeederSelector._get_default, but even if we monkey patched SeederSelector
88-
# eagerly, that code would still be executed only when virtualenv loads our plugin, which
89-
# happens to be very close to the point when it calls can_describe
90-
try:
91-
from virtualenv.run import SeederSelector
92-
_get_default_orig = SeederSelector._get_default
93-
94-
def _seeder_selector_get_default_override(self):
95-
if self.interpreter.implementation == "GraalVM":
96-
return "graalpy"
97-
else:
98-
return _get_default_orig()
99-
100-
SeederSelector._get_default = _seeder_selector_get_default_override
101-
except ImportError:
102-
pass
103-
except AttributeError:
104-
pass
105-
return True
106-
107-
@classmethod
108-
def exe_stem(cls):
109-
return "graalpy"
110-
111-
def create(self):
112-
raise RuntimeError("Please use the 'venv' creator with GraalPy. "
113-
"It should be the default and can be explicitly requested "
114-
"with command line option `--creator venv`, "
115-
"or environment variable VIRTUALENV_CREATOR=venv")
116-
117-
118-
class GraalPyCreatorPosix(AbstractGraalPyCreator, PosixSupports):
119-
pass
120-
121-
122-
class GraalPyCreatorWindows(AbstractGraalPyCreator, WindowsSupports):
49+
try:
50+
from virtualenv.create.via_global_ref.builtin.graalpy import GraalPyPosix, GraalPyWindows
51+
except ImportError:
52+
from abc import ABC
53+
from pathlib import Path
54+
55+
from virtualenv.create.describe import PosixSupports, WindowsSupports
56+
from virtualenv.create.via_global_ref.builtin.ref import PathRefToDest, RefMust, RefWhen
57+
from virtualenv.create.via_global_ref.builtin.via_global_self_do import ViaGlobalRefVirtualenvBuiltin
58+
59+
class GraalPy(ViaGlobalRefVirtualenvBuiltin, ABC):
60+
@classmethod
61+
def can_describe(cls, interpreter):
62+
return interpreter.implementation == "GraalVM" and super().can_describe(interpreter)
63+
64+
@classmethod
65+
def exe_stem(cls):
66+
return "graalpy"
67+
68+
@classmethod
69+
def exe_names(cls, interpreter):
70+
return {
71+
cls.exe_stem(),
72+
"python",
73+
f"python{interpreter.version_info.major}",
74+
f"python{interpreter.version_info.major}.{interpreter.version_info.minor}",
75+
}
76+
77+
@classmethod
78+
def _executables(cls, interpreter):
79+
host = Path(interpreter.system_executable)
80+
targets = sorted(f"{name}{cls.suffix}" for name in cls.exe_names(interpreter))
81+
yield host, targets, RefMust.NA, RefWhen.ANY
82+
83+
@classmethod
84+
def sources(cls, interpreter):
85+
yield from super().sources(interpreter)
86+
python_dir = Path(interpreter.system_executable).resolve().parent
87+
if python_dir.name in {"bin", "Scripts"}:
88+
python_dir = python_dir.parent
89+
90+
native_lib = cls._native_lib(python_dir / "lib", interpreter.platform)
91+
if native_lib.exists():
92+
yield PathRefToDest(native_lib, dest=lambda self, s: self.bin_dir.parent / "lib" / s.name)
93+
94+
for jvm_dir_name in ("jvm", "jvmlibs", "modules"):
95+
jvm_dir = python_dir / jvm_dir_name
96+
if jvm_dir.exists():
97+
yield PathRefToDest(jvm_dir, dest=lambda self, s: self.bin_dir.parent / s.name)
98+
99+
@classmethod
100+
def _shared_libs(cls, python_dir):
101+
raise NotImplementedError
102+
103+
104+
class GraalPyPosix(GraalPy, PosixSupports):
105+
@classmethod
106+
def _native_lib(cls, lib_dir, platform):
107+
if platform == "darwin":
108+
return lib_dir / "libpythonvm.dylib"
109+
return lib_dir / "libpythonvm.so"
110+
111+
112+
class GraalPyWindows(GraalPy, WindowsSupports):
113+
@classmethod
114+
def _native_lib(cls, lib_dir, _platform):
115+
return lib_dir / "pythonvm.dll"
116+
117+
def set_pyenv_cfg(self):
118+
# GraalPy needs an additional entry in pyvenv.cfg on Windows
119+
super().set_pyenv_cfg()
120+
self.pyenv_cfg["venvlauncher_command"] = self.interpreter.system_executable
121+
122+
123+
124+
# We monkey patch SeederSelector to use our seeder by default when creating
125+
# a GraalPy virtualenv.
126+
try:
127+
from virtualenv.run import SeederSelector
128+
_get_default_orig = SeederSelector._get_default
129+
130+
def _seeder_selector_get_default_override(self):
131+
if self.interpreter.implementation == "GraalVM":
132+
return "graalpy"
133+
else:
134+
return _get_default_orig()
135+
136+
SeederSelector._get_default = _seeder_selector_get_default_override
137+
except Exception:
123138
pass
124139

125140

graalpy_virtualenv/pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ dependencies = [
5858
]
5959

6060
[project.entry-points."virtualenv.create"]
61-
graalpy-posix = "graalpy_virtualenv.graalpy:GraalPyCreatorPosix"
62-
graalpy-windows = "graalpy_virtualenv.graalpy:GraalPyCreatorWindows"
61+
graalpy-posix = "graalpy_virtualenv.graalpy:GraalPyPosix"
62+
graalpy-win = "graalpy_virtualenv.graalpy:GraalPyWindows"
6363

6464
[project.entry-points."virtualenv.seed"]
65-
graalpy = "graalpy_virtualenv:graalpy.GraalPySeeder"
65+
graalpy = "graalpy_virtualenv.graalpy:GraalPySeeder"
6666

6767
[project.urls]
6868
Homepage = "https://graalvm.org/python"

graalpython/lib-graalpython/patches/pip-23.2.1.patch

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,16 +125,38 @@ index a8cd133..20dd1e6 100644
125125
def pyc_source_file_paths() -> Generator[str, None, None]:
126126
# We de-duplicate installation paths, since there can be overlap (e.g.
127127
# file in .data maps to same location as file in wheel root).
128+
diff --git a/pip/_internal/resolution/resolvelib/candidates.py b/pip/_internal/resolution/resolvelib/candidates.py
129+
index de04e1d..d5bd355 100644
130+
--- a/pip/_internal/resolution/resolvelib/candidates.py
131+
+++ b/pip/_internal/resolution/resolvelib/candidates.py
132+
@@ -20,6 +20,7 @@ from pip._internal.req.constructors import (
133+
from pip._internal.req.req_install import InstallRequirement
134+
from pip._internal.utils.direct_url_helpers import direct_url_from_link
135+
from pip._internal.utils.misc import normalize_version_info
136+
+from pip._internal.utils import graalpy
137+
138+
from .base import Candidate, CandidateVersion, Requirement, format_name
139+
140+
@@ -242,6 +243,8 @@ class _InstallRequirementBackedCandidate(Candidate):
141+
for r in requires:
142+
yield self._factory.make_requirement_from_spec(str(r), self._ireq)
143+
yield self._factory.make_requires_python_requirement(self.dist.requires_python)
144+
+ if self.name == 'virtualenv' and not graalpy.DISABLE_PATCHING:
145+
+ yield self._factory.make_requirement_from_spec('graalpy-virtualenv', self._ireq)
146+
147+
def get_install_requirement(self) -> Optional[InstallRequirement]:
148+
return self._ireq
128149
diff --git a/pip/_internal/utils/graalpy.py b/pip/_internal/utils/graalpy.py
129150
new file mode 100644
130-
index 0000000..5b35102
151+
index 0000000..53dbe96
131152
--- /dev/null
132153
+++ b/pip/_internal/utils/graalpy.py
133-
@@ -0,0 +1,330 @@
154+
@@ -0,0 +1,334 @@
134155
+import abc
135156
+import logging
136157
+import os
137158
+import re
159+
+import sys
138160
+import tempfile
139161
+import zipfile
140162
+from contextlib import contextmanager
@@ -144,7 +166,7 @@ index 0000000..5b35102
144166
+
145167
+from pip._internal.models.candidate import InstallationCandidate
146168
+from pip._internal.models.link import Link
147-
+from pip._internal.utils.urls import url_to_path
169+
+from pip._internal.utils.urls import url_to_path, path_to_url
148170
+from pip._vendor import tomli, requests
149171
+from pip._vendor.packaging.specifiers import SpecifierSet
150172
+from pip._vendor.packaging.utils import canonicalize_name
@@ -440,6 +462,9 @@ index 0000000..5b35102
440462
+ # We need to force the filename to match the usual convention, otherwise we won't find a patch
441463
+ link = AddedSourceLink(url, f'{name}-{version}.{suffix}')
442464
+ candidates.append(InstallationCandidate(name=name, version=version, link=link))
465+
+ if name == 'graalpy-virtualenv':
466+
+ link = Link(path_to_url(os.path.join(sys.base_prefix, 'graalpy_virtualenv')))
467+
+ candidates.append(InstallationCandidate(name=name, version='0.0.1', link=link))
443468
+ return candidates
444469
+
445470
+
Binary file not shown.

mx.graalpython/suite.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,16 +1330,10 @@
13301330
"fileListEntry": "META-INF/resources/libgraalpy.files",
13311331
"type": "dir",
13321332
"description": "GraalVM Python lib-graalpython resources",
1333-
"buildDependencies": [
1334-
"graalpy_virtualenv",
1335-
],
13361333
"layout": {
13371334
"./META-INF/resources/libgraalpy/": [
13381335
"file:graalpython/lib-graalpython/*",
13391336
],
1340-
"./META-INF/resources/libgraalpy/modules/graalpy_virtualenv": [
1341-
"file:graalpy_virtualenv/graalpy_virtualenv",
1342-
],
13431337
},
13441338
"maven": False,
13451339
},

0 commit comments

Comments
 (0)