omdev 0.0.0.dev180__py3-none-any.whl → 0.0.0.dev182__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omdev/interp/inject.py +1 -1
- omdev/interp/inspect.py +9 -4
- omdev/interp/providers/system.py +5 -2
- omdev/interp/pyenv/inject.py +1 -1
- omdev/interp/pyenv/install.py +251 -0
- omdev/interp/pyenv/provider.py +144 -0
- omdev/interp/pyenv/pyenv.py +0 -380
- omdev/interp/venvs.py +114 -0
- omdev/pyproject/configs.py +3 -4
- omdev/pyproject/inject.py +12 -0
- omdev/pyproject/venvs.py +16 -52
- omdev/scripts/interp.py +121 -114
- omdev/scripts/pyproject.py +477 -343
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/RECORD +19 -15
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev180.dist-info → omdev-0.0.0.dev182.dist-info}/top_level.txt +0 -0
omdev/interp/inject.py
CHANGED
@@ -11,7 +11,7 @@ from .providers.inject import bind_interp_providers
|
|
11
11
|
from .providers.running import RunningInterpProvider
|
12
12
|
from .providers.system import SystemInterpProvider
|
13
13
|
from .pyenv.inject import bind_interp_pyenv
|
14
|
-
from .pyenv.
|
14
|
+
from .pyenv.provider import PyenvInterpProvider
|
15
15
|
from .resolvers import InterpResolver
|
16
16
|
from .resolvers import InterpResolverProviders
|
17
17
|
from .uv.inject import bind_interp_uv
|
omdev/interp/inspect.py
CHANGED
@@ -6,7 +6,6 @@ import sys
|
|
6
6
|
import typing as ta
|
7
7
|
|
8
8
|
from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
|
9
|
-
from omlish.lite.logs import log
|
10
9
|
|
11
10
|
from ..packaging.versions import Version
|
12
11
|
from .types import InterpOpts
|
@@ -43,9 +42,15 @@ class InterpInspection:
|
|
43
42
|
|
44
43
|
|
45
44
|
class InterpInspector:
|
46
|
-
def __init__(
|
45
|
+
def __init__(
|
46
|
+
self,
|
47
|
+
*,
|
48
|
+
log: ta.Optional[logging.Logger] = None,
|
49
|
+
) -> None:
|
47
50
|
super().__init__()
|
48
51
|
|
52
|
+
self._log = log
|
53
|
+
|
49
54
|
self._cache: ta.Dict[str, ta.Optional[InterpInspection]] = {}
|
50
55
|
|
51
56
|
_RAW_INSPECTION_CODE = """
|
@@ -94,8 +99,8 @@ class InterpInspector:
|
|
94
99
|
try:
|
95
100
|
ret = await self._inspect(exe)
|
96
101
|
except Exception as e: # noqa
|
97
|
-
if
|
98
|
-
|
102
|
+
if self._log is not None and self._log.isEnabledFor(logging.DEBUG):
|
103
|
+
self._log.exception('Failed to inspect interp: %s', exe)
|
99
104
|
ret = None
|
100
105
|
self._cache[exe] = ret
|
101
106
|
return ret
|
omdev/interp/providers/system.py
CHANGED
@@ -5,13 +5,13 @@ TODO:
|
|
5
5
|
- check if path py's are venvs: sys.prefix != sys.base_prefix
|
6
6
|
"""
|
7
7
|
import dataclasses as dc
|
8
|
+
import logging
|
8
9
|
import os
|
9
10
|
import re
|
10
11
|
import typing as ta
|
11
12
|
|
12
13
|
from omlish.lite.cached import cached_nullary
|
13
14
|
from omlish.lite.check import check
|
14
|
-
from omlish.lite.logs import log
|
15
15
|
|
16
16
|
from ...packaging.versions import InvalidVersion
|
17
17
|
from ..inspect import InterpInspector
|
@@ -37,12 +37,14 @@ class SystemInterpProvider(InterpProvider):
|
|
37
37
|
options: Options = Options(),
|
38
38
|
*,
|
39
39
|
inspector: ta.Optional[InterpInspector] = None,
|
40
|
+
log: ta.Optional[logging.Logger] = None,
|
40
41
|
) -> None:
|
41
42
|
super().__init__()
|
42
43
|
|
43
44
|
self._options = options
|
44
45
|
|
45
46
|
self._inspector = inspector
|
47
|
+
self._log = log
|
46
48
|
|
47
49
|
#
|
48
50
|
|
@@ -116,7 +118,8 @@ class SystemInterpProvider(InterpProvider):
|
|
116
118
|
lst = []
|
117
119
|
for e in self.exes():
|
118
120
|
if (ev := await self.get_exe_version(e)) is None:
|
119
|
-
|
121
|
+
if self._log is not None:
|
122
|
+
self._log.debug('Invalid system version: %s', e)
|
120
123
|
continue
|
121
124
|
lst.append((e, ev))
|
122
125
|
return lst
|
omdev/interp/pyenv/inject.py
CHANGED
@@ -6,8 +6,8 @@ from omlish.lite.inject import InjectorBindings
|
|
6
6
|
from omlish.lite.inject import inj
|
7
7
|
|
8
8
|
from ..providers.base import InterpProvider
|
9
|
+
from .provider import PyenvInterpProvider
|
9
10
|
from .pyenv import Pyenv
|
10
|
-
from .pyenv import PyenvInterpProvider
|
11
11
|
|
12
12
|
|
13
13
|
def bind_interp_pyenv() -> InjectorBindings:
|
@@ -0,0 +1,251 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import abc
|
3
|
+
import dataclasses as dc
|
4
|
+
import itertools
|
5
|
+
import os.path
|
6
|
+
import shutil
|
7
|
+
import sys
|
8
|
+
import typing as ta
|
9
|
+
|
10
|
+
from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
|
11
|
+
from omlish.lite.cached import async_cached_nullary
|
12
|
+
from omlish.lite.cached import cached_nullary
|
13
|
+
from omlish.lite.check import check
|
14
|
+
|
15
|
+
from ..types import InterpOpts
|
16
|
+
from .pyenv import Pyenv
|
17
|
+
|
18
|
+
|
19
|
+
##
|
20
|
+
|
21
|
+
|
22
|
+
@dc.dataclass(frozen=True)
|
23
|
+
class PyenvInstallOpts:
|
24
|
+
opts: ta.Sequence[str] = ()
|
25
|
+
conf_opts: ta.Sequence[str] = ()
|
26
|
+
cflags: ta.Sequence[str] = ()
|
27
|
+
ldflags: ta.Sequence[str] = ()
|
28
|
+
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
29
|
+
|
30
|
+
def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
|
31
|
+
return PyenvInstallOpts(
|
32
|
+
opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
|
33
|
+
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
34
|
+
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
35
|
+
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
36
|
+
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
37
|
+
)
|
38
|
+
|
39
|
+
|
40
|
+
# TODO: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-for-maximum-performance
|
41
|
+
DEFAULT_PYENV_INSTALL_OPTS = PyenvInstallOpts(
|
42
|
+
opts=[
|
43
|
+
'-s',
|
44
|
+
'-v',
|
45
|
+
'-k',
|
46
|
+
],
|
47
|
+
conf_opts=[
|
48
|
+
# FIXME: breaks on mac for older py's
|
49
|
+
'--enable-loadable-sqlite-extensions',
|
50
|
+
|
51
|
+
# '--enable-shared',
|
52
|
+
|
53
|
+
'--enable-optimizations',
|
54
|
+
'--with-lto',
|
55
|
+
|
56
|
+
# '--enable-profiling', # ?
|
57
|
+
|
58
|
+
# '--enable-ipv6', # ?
|
59
|
+
],
|
60
|
+
cflags=[
|
61
|
+
# '-march=native',
|
62
|
+
# '-mtune=native',
|
63
|
+
],
|
64
|
+
)
|
65
|
+
|
66
|
+
DEBUG_PYENV_INSTALL_OPTS = PyenvInstallOpts(opts=['-g'])
|
67
|
+
|
68
|
+
THREADED_PYENV_INSTALL_OPTS = PyenvInstallOpts(conf_opts=['--disable-gil'])
|
69
|
+
|
70
|
+
|
71
|
+
#
|
72
|
+
|
73
|
+
|
74
|
+
class PyenvInstallOptsProvider(abc.ABC):
|
75
|
+
@abc.abstractmethod
|
76
|
+
def opts(self) -> ta.Awaitable[PyenvInstallOpts]:
|
77
|
+
raise NotImplementedError
|
78
|
+
|
79
|
+
|
80
|
+
class LinuxPyenvInstallOpts(PyenvInstallOptsProvider):
|
81
|
+
async def opts(self) -> PyenvInstallOpts:
|
82
|
+
return PyenvInstallOpts()
|
83
|
+
|
84
|
+
|
85
|
+
class DarwinPyenvInstallOpts(PyenvInstallOptsProvider):
|
86
|
+
@cached_nullary
|
87
|
+
def framework_opts(self) -> PyenvInstallOpts:
|
88
|
+
return PyenvInstallOpts(conf_opts=['--enable-framework'])
|
89
|
+
|
90
|
+
@cached_nullary
|
91
|
+
def has_brew(self) -> bool:
|
92
|
+
return shutil.which('brew') is not None
|
93
|
+
|
94
|
+
BREW_DEPS: ta.Sequence[str] = [
|
95
|
+
'openssl',
|
96
|
+
'readline',
|
97
|
+
'sqlite3',
|
98
|
+
'zlib',
|
99
|
+
]
|
100
|
+
|
101
|
+
@async_cached_nullary
|
102
|
+
async def brew_deps_opts(self) -> PyenvInstallOpts:
|
103
|
+
cflags = []
|
104
|
+
ldflags = []
|
105
|
+
for dep in self.BREW_DEPS:
|
106
|
+
dep_prefix = await asyncio_subprocesses.check_output_str('brew', '--prefix', dep)
|
107
|
+
cflags.append(f'-I{dep_prefix}/include')
|
108
|
+
ldflags.append(f'-L{dep_prefix}/lib')
|
109
|
+
return PyenvInstallOpts(
|
110
|
+
cflags=cflags,
|
111
|
+
ldflags=ldflags,
|
112
|
+
)
|
113
|
+
|
114
|
+
@async_cached_nullary
|
115
|
+
async def brew_tcl_opts(self) -> PyenvInstallOpts:
|
116
|
+
if await asyncio_subprocesses.try_output('brew', '--prefix', 'tcl-tk') is None:
|
117
|
+
return PyenvInstallOpts()
|
118
|
+
|
119
|
+
tcl_tk_prefix = await asyncio_subprocesses.check_output_str('brew', '--prefix', 'tcl-tk')
|
120
|
+
tcl_tk_ver_str = await asyncio_subprocesses.check_output_str('brew', 'ls', '--versions', 'tcl-tk')
|
121
|
+
tcl_tk_ver = '.'.join(tcl_tk_ver_str.split()[1].split('.')[:2])
|
122
|
+
|
123
|
+
return PyenvInstallOpts(conf_opts=[
|
124
|
+
f"--with-tcltk-includes='-I{tcl_tk_prefix}/include'",
|
125
|
+
f"--with-tcltk-libs='-L{tcl_tk_prefix}/lib -ltcl{tcl_tk_ver} -ltk{tcl_tk_ver}'",
|
126
|
+
])
|
127
|
+
|
128
|
+
# @cached_nullary
|
129
|
+
# def brew_ssl_opts(self) -> PyenvInstallOpts:
|
130
|
+
# pkg_config_path = subprocess_check_output_str('brew', '--prefix', 'openssl')
|
131
|
+
# if 'PKG_CONFIG_PATH' in os.environ:
|
132
|
+
# pkg_config_path += ':' + os.environ['PKG_CONFIG_PATH']
|
133
|
+
# return PyenvInstallOpts(env={'PKG_CONFIG_PATH': pkg_config_path})
|
134
|
+
|
135
|
+
async def opts(self) -> PyenvInstallOpts:
|
136
|
+
return PyenvInstallOpts().merge(
|
137
|
+
self.framework_opts(),
|
138
|
+
await self.brew_deps_opts(),
|
139
|
+
await self.brew_tcl_opts(),
|
140
|
+
# self.brew_ssl_opts(),
|
141
|
+
)
|
142
|
+
|
143
|
+
|
144
|
+
PLATFORM_PYENV_INSTALL_OPTS: ta.Dict[str, PyenvInstallOptsProvider] = {
|
145
|
+
'darwin': DarwinPyenvInstallOpts(),
|
146
|
+
'linux': LinuxPyenvInstallOpts(),
|
147
|
+
}
|
148
|
+
|
149
|
+
|
150
|
+
##
|
151
|
+
|
152
|
+
|
153
|
+
class PyenvVersionInstaller:
|
154
|
+
"""
|
155
|
+
Messy: can install freethreaded build with a 't' suffixed version str _or_ by THREADED_PYENV_INSTALL_OPTS - need
|
156
|
+
latter to build custom interp with ft, need former to use canned / blessed interps. Muh.
|
157
|
+
"""
|
158
|
+
|
159
|
+
def __init__(
|
160
|
+
self,
|
161
|
+
version: str,
|
162
|
+
opts: ta.Optional[PyenvInstallOpts] = None,
|
163
|
+
interp_opts: InterpOpts = InterpOpts(),
|
164
|
+
*,
|
165
|
+
pyenv: Pyenv,
|
166
|
+
|
167
|
+
install_name: ta.Optional[str] = None,
|
168
|
+
no_default_opts: bool = False,
|
169
|
+
) -> None:
|
170
|
+
super().__init__()
|
171
|
+
|
172
|
+
self._version = version
|
173
|
+
self._given_opts = opts
|
174
|
+
self._interp_opts = interp_opts
|
175
|
+
self._given_install_name = install_name
|
176
|
+
|
177
|
+
self._no_default_opts = no_default_opts
|
178
|
+
self._pyenv = pyenv
|
179
|
+
|
180
|
+
@property
|
181
|
+
def version(self) -> str:
|
182
|
+
return self._version
|
183
|
+
|
184
|
+
@async_cached_nullary
|
185
|
+
async def opts(self) -> PyenvInstallOpts:
|
186
|
+
opts = self._given_opts
|
187
|
+
if self._no_default_opts:
|
188
|
+
if opts is None:
|
189
|
+
opts = PyenvInstallOpts()
|
190
|
+
else:
|
191
|
+
lst = [self._given_opts if self._given_opts is not None else DEFAULT_PYENV_INSTALL_OPTS]
|
192
|
+
if self._interp_opts.debug:
|
193
|
+
lst.append(DEBUG_PYENV_INSTALL_OPTS)
|
194
|
+
if self._interp_opts.threaded:
|
195
|
+
lst.append(THREADED_PYENV_INSTALL_OPTS)
|
196
|
+
lst.append(await PLATFORM_PYENV_INSTALL_OPTS[sys.platform].opts())
|
197
|
+
opts = PyenvInstallOpts().merge(*lst)
|
198
|
+
return opts
|
199
|
+
|
200
|
+
@cached_nullary
|
201
|
+
def install_name(self) -> str:
|
202
|
+
if self._given_install_name is not None:
|
203
|
+
return self._given_install_name
|
204
|
+
return self._version + ('-debug' if self._interp_opts.debug else '')
|
205
|
+
|
206
|
+
@async_cached_nullary
|
207
|
+
async def install_dir(self) -> str:
|
208
|
+
return str(os.path.join(check.not_none(await self._pyenv.root()), 'versions', self.install_name()))
|
209
|
+
|
210
|
+
@async_cached_nullary
|
211
|
+
async def install(self) -> str:
|
212
|
+
opts = await self.opts()
|
213
|
+
env = {**os.environ, **opts.env}
|
214
|
+
for k, l in [
|
215
|
+
('CFLAGS', opts.cflags),
|
216
|
+
('LDFLAGS', opts.ldflags),
|
217
|
+
('PYTHON_CONFIGURE_OPTS', opts.conf_opts),
|
218
|
+
]:
|
219
|
+
v = ' '.join(l)
|
220
|
+
if k in os.environ:
|
221
|
+
v += ' ' + os.environ[k]
|
222
|
+
env[k] = v
|
223
|
+
|
224
|
+
conf_args = [
|
225
|
+
*opts.opts,
|
226
|
+
self._version,
|
227
|
+
]
|
228
|
+
|
229
|
+
full_args: ta.List[str]
|
230
|
+
if self._given_install_name is not None:
|
231
|
+
full_args = [
|
232
|
+
os.path.join(check.not_none(await self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'), # noqa
|
233
|
+
*conf_args,
|
234
|
+
await self.install_dir(),
|
235
|
+
]
|
236
|
+
else:
|
237
|
+
full_args = [
|
238
|
+
await self._pyenv.exe(),
|
239
|
+
'install',
|
240
|
+
*conf_args,
|
241
|
+
]
|
242
|
+
|
243
|
+
await asyncio_subprocesses.check_call(
|
244
|
+
*full_args,
|
245
|
+
env=env,
|
246
|
+
)
|
247
|
+
|
248
|
+
exe = os.path.join(await self.install_dir(), 'bin', 'python')
|
249
|
+
if not os.path.isfile(exe):
|
250
|
+
raise RuntimeError(f'Interpreter not found: {exe}')
|
251
|
+
return exe
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import dataclasses as dc
|
3
|
+
import logging
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
from omlish.lite.check import check
|
7
|
+
|
8
|
+
from ...packaging.versions import InvalidVersion
|
9
|
+
from ...packaging.versions import Version
|
10
|
+
from ..inspect import InterpInspector
|
11
|
+
from ..providers.base import InterpProvider
|
12
|
+
from ..types import Interp
|
13
|
+
from ..types import InterpOpts
|
14
|
+
from ..types import InterpSpecifier
|
15
|
+
from ..types import InterpVersion
|
16
|
+
from .install import PyenvVersionInstaller
|
17
|
+
from .pyenv import Pyenv
|
18
|
+
|
19
|
+
|
20
|
+
class PyenvInterpProvider(InterpProvider):
|
21
|
+
@dc.dataclass(frozen=True)
|
22
|
+
class Options:
|
23
|
+
inspect: bool = False
|
24
|
+
|
25
|
+
try_update: bool = False
|
26
|
+
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
options: Options = Options(),
|
30
|
+
*,
|
31
|
+
pyenv: Pyenv,
|
32
|
+
inspector: InterpInspector,
|
33
|
+
log: ta.Optional[logging.Logger] = None,
|
34
|
+
) -> None:
|
35
|
+
super().__init__()
|
36
|
+
|
37
|
+
self._options = options
|
38
|
+
|
39
|
+
self._pyenv = pyenv
|
40
|
+
self._inspector = inspector
|
41
|
+
self._log = log
|
42
|
+
|
43
|
+
#
|
44
|
+
|
45
|
+
@staticmethod
|
46
|
+
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
47
|
+
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
48
|
+
if s.endswith(sfx):
|
49
|
+
return s[:-len(sfx)], True
|
50
|
+
return s, False
|
51
|
+
ok = {}
|
52
|
+
s, ok['debug'] = strip_sfx(s, '-debug')
|
53
|
+
s, ok['threaded'] = strip_sfx(s, 't')
|
54
|
+
try:
|
55
|
+
v = Version(s)
|
56
|
+
except InvalidVersion:
|
57
|
+
return None
|
58
|
+
return InterpVersion(v, InterpOpts(**ok))
|
59
|
+
|
60
|
+
class Installed(ta.NamedTuple):
|
61
|
+
name: str
|
62
|
+
exe: str
|
63
|
+
version: InterpVersion
|
64
|
+
|
65
|
+
async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
66
|
+
iv: ta.Optional[InterpVersion]
|
67
|
+
if self._options.inspect:
|
68
|
+
try:
|
69
|
+
iv = check.not_none(await self._inspector.inspect(ep)).iv
|
70
|
+
except Exception as e: # noqa
|
71
|
+
return None
|
72
|
+
else:
|
73
|
+
iv = self.guess_version(vn)
|
74
|
+
if iv is None:
|
75
|
+
return None
|
76
|
+
return PyenvInterpProvider.Installed(
|
77
|
+
name=vn,
|
78
|
+
exe=ep,
|
79
|
+
version=iv,
|
80
|
+
)
|
81
|
+
|
82
|
+
async def installed(self) -> ta.Sequence[Installed]:
|
83
|
+
ret: ta.List[PyenvInterpProvider.Installed] = []
|
84
|
+
for vn, ep in await self._pyenv.version_exes():
|
85
|
+
if (i := await self._make_installed(vn, ep)) is None:
|
86
|
+
if self._log is not None:
|
87
|
+
self._log.debug('Invalid pyenv version: %s', vn)
|
88
|
+
continue
|
89
|
+
ret.append(i)
|
90
|
+
return ret
|
91
|
+
|
92
|
+
#
|
93
|
+
|
94
|
+
async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
95
|
+
return [i.version for i in await self.installed()]
|
96
|
+
|
97
|
+
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
98
|
+
for i in await self.installed():
|
99
|
+
if i.version == version:
|
100
|
+
return Interp(
|
101
|
+
exe=i.exe,
|
102
|
+
version=i.version,
|
103
|
+
)
|
104
|
+
raise KeyError(version)
|
105
|
+
|
106
|
+
#
|
107
|
+
|
108
|
+
async def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
109
|
+
lst = []
|
110
|
+
|
111
|
+
for vs in await self._pyenv.installable_versions():
|
112
|
+
if (iv := self.guess_version(vs)) is None:
|
113
|
+
continue
|
114
|
+
if iv.opts.debug:
|
115
|
+
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
116
|
+
for d in [False, True]:
|
117
|
+
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
118
|
+
|
119
|
+
return lst
|
120
|
+
|
121
|
+
async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
122
|
+
lst = await self._get_installable_versions(spec)
|
123
|
+
|
124
|
+
if self._options.try_update and not any(v in spec for v in lst):
|
125
|
+
if self._pyenv.update():
|
126
|
+
lst = await self._get_installable_versions(spec)
|
127
|
+
|
128
|
+
return lst
|
129
|
+
|
130
|
+
async def install_version(self, version: InterpVersion) -> Interp:
|
131
|
+
inst_version = str(version.version)
|
132
|
+
inst_opts = version.opts
|
133
|
+
if inst_opts.threaded:
|
134
|
+
inst_version += 't'
|
135
|
+
inst_opts = dc.replace(inst_opts, threaded=False)
|
136
|
+
|
137
|
+
installer = PyenvVersionInstaller(
|
138
|
+
inst_version,
|
139
|
+
interp_opts=inst_opts,
|
140
|
+
pyenv=self._pyenv,
|
141
|
+
)
|
142
|
+
|
143
|
+
exe = await installer.install()
|
144
|
+
return Interp(exe, version)
|