omdev 0.0.0.dev181__py3-none-any.whl → 0.0.0.dev183__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- omdev/interp/inject.py +1 -1
- 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 -383
- omdev/scripts/interp.py +104 -102
- omdev/scripts/pyproject.py +225 -212
- {omdev-0.0.0.dev181.dist-info → omdev-0.0.0.dev183.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev181.dist-info → omdev-0.0.0.dev183.dist-info}/RECORD +13 -11
- {omdev-0.0.0.dev181.dist-info → omdev-0.0.0.dev183.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev181.dist-info → omdev-0.0.0.dev183.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev181.dist-info → omdev-0.0.0.dev183.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev181.dist-info → omdev-0.0.0.dev183.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/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)
|