omdev 0.0.0.dev180__py3-none-any.whl → 0.0.0.dev182__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/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/pyenv/pyenv.py
CHANGED
@@ -10,31 +10,13 @@ TODO:
|
|
10
10
|
- optionally install / upgrade pyenv itself
|
11
11
|
- new vers dont need these custom mac opts, only run on old vers
|
12
12
|
"""
|
13
|
-
import abc
|
14
|
-
import dataclasses as dc
|
15
|
-
import itertools
|
16
13
|
import os.path
|
17
14
|
import shutil
|
18
|
-
import sys
|
19
15
|
import typing as ta
|
20
16
|
|
21
17
|
from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
|
22
18
|
from omlish.lite.cached import async_cached_nullary
|
23
|
-
from omlish.lite.cached import cached_nullary
|
24
19
|
from omlish.lite.check import check
|
25
|
-
from omlish.lite.logs import log
|
26
|
-
|
27
|
-
from ...packaging.versions import InvalidVersion
|
28
|
-
from ...packaging.versions import Version
|
29
|
-
from ..inspect import InterpInspector
|
30
|
-
from ..providers.base import InterpProvider
|
31
|
-
from ..types import Interp
|
32
|
-
from ..types import InterpOpts
|
33
|
-
from ..types import InterpSpecifier
|
34
|
-
from ..types import InterpVersion
|
35
|
-
|
36
|
-
|
37
|
-
##
|
38
20
|
|
39
21
|
|
40
22
|
class Pyenv:
|
@@ -102,365 +84,3 @@ class Pyenv:
|
|
102
84
|
return False
|
103
85
|
await asyncio_subprocesses.check_call('git', 'pull', cwd=root)
|
104
86
|
return True
|
105
|
-
|
106
|
-
|
107
|
-
##
|
108
|
-
|
109
|
-
|
110
|
-
@dc.dataclass(frozen=True)
|
111
|
-
class PyenvInstallOpts:
|
112
|
-
opts: ta.Sequence[str] = ()
|
113
|
-
conf_opts: ta.Sequence[str] = ()
|
114
|
-
cflags: ta.Sequence[str] = ()
|
115
|
-
ldflags: ta.Sequence[str] = ()
|
116
|
-
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
117
|
-
|
118
|
-
def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
|
119
|
-
return PyenvInstallOpts(
|
120
|
-
opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
|
121
|
-
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
122
|
-
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
123
|
-
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
124
|
-
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
125
|
-
)
|
126
|
-
|
127
|
-
|
128
|
-
# TODO: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-for-maximum-performance
|
129
|
-
DEFAULT_PYENV_INSTALL_OPTS = PyenvInstallOpts(
|
130
|
-
opts=[
|
131
|
-
'-s',
|
132
|
-
'-v',
|
133
|
-
'-k',
|
134
|
-
],
|
135
|
-
conf_opts=[
|
136
|
-
# FIXME: breaks on mac for older py's
|
137
|
-
'--enable-loadable-sqlite-extensions',
|
138
|
-
|
139
|
-
# '--enable-shared',
|
140
|
-
|
141
|
-
'--enable-optimizations',
|
142
|
-
'--with-lto',
|
143
|
-
|
144
|
-
# '--enable-profiling', # ?
|
145
|
-
|
146
|
-
# '--enable-ipv6', # ?
|
147
|
-
],
|
148
|
-
cflags=[
|
149
|
-
# '-march=native',
|
150
|
-
# '-mtune=native',
|
151
|
-
],
|
152
|
-
)
|
153
|
-
|
154
|
-
DEBUG_PYENV_INSTALL_OPTS = PyenvInstallOpts(opts=['-g'])
|
155
|
-
|
156
|
-
THREADED_PYENV_INSTALL_OPTS = PyenvInstallOpts(conf_opts=['--disable-gil'])
|
157
|
-
|
158
|
-
|
159
|
-
#
|
160
|
-
|
161
|
-
|
162
|
-
class PyenvInstallOptsProvider(abc.ABC):
|
163
|
-
@abc.abstractmethod
|
164
|
-
def opts(self) -> ta.Awaitable[PyenvInstallOpts]:
|
165
|
-
raise NotImplementedError
|
166
|
-
|
167
|
-
|
168
|
-
class LinuxPyenvInstallOpts(PyenvInstallOptsProvider):
|
169
|
-
async def opts(self) -> PyenvInstallOpts:
|
170
|
-
return PyenvInstallOpts()
|
171
|
-
|
172
|
-
|
173
|
-
class DarwinPyenvInstallOpts(PyenvInstallOptsProvider):
|
174
|
-
@cached_nullary
|
175
|
-
def framework_opts(self) -> PyenvInstallOpts:
|
176
|
-
return PyenvInstallOpts(conf_opts=['--enable-framework'])
|
177
|
-
|
178
|
-
@cached_nullary
|
179
|
-
def has_brew(self) -> bool:
|
180
|
-
return shutil.which('brew') is not None
|
181
|
-
|
182
|
-
BREW_DEPS: ta.Sequence[str] = [
|
183
|
-
'openssl',
|
184
|
-
'readline',
|
185
|
-
'sqlite3',
|
186
|
-
'zlib',
|
187
|
-
]
|
188
|
-
|
189
|
-
@async_cached_nullary
|
190
|
-
async def brew_deps_opts(self) -> PyenvInstallOpts:
|
191
|
-
cflags = []
|
192
|
-
ldflags = []
|
193
|
-
for dep in self.BREW_DEPS:
|
194
|
-
dep_prefix = await asyncio_subprocesses.check_output_str('brew', '--prefix', dep)
|
195
|
-
cflags.append(f'-I{dep_prefix}/include')
|
196
|
-
ldflags.append(f'-L{dep_prefix}/lib')
|
197
|
-
return PyenvInstallOpts(
|
198
|
-
cflags=cflags,
|
199
|
-
ldflags=ldflags,
|
200
|
-
)
|
201
|
-
|
202
|
-
@async_cached_nullary
|
203
|
-
async def brew_tcl_opts(self) -> PyenvInstallOpts:
|
204
|
-
if await asyncio_subprocesses.try_output('brew', '--prefix', 'tcl-tk') is None:
|
205
|
-
return PyenvInstallOpts()
|
206
|
-
|
207
|
-
tcl_tk_prefix = await asyncio_subprocesses.check_output_str('brew', '--prefix', 'tcl-tk')
|
208
|
-
tcl_tk_ver_str = await asyncio_subprocesses.check_output_str('brew', 'ls', '--versions', 'tcl-tk')
|
209
|
-
tcl_tk_ver = '.'.join(tcl_tk_ver_str.split()[1].split('.')[:2])
|
210
|
-
|
211
|
-
return PyenvInstallOpts(conf_opts=[
|
212
|
-
f"--with-tcltk-includes='-I{tcl_tk_prefix}/include'",
|
213
|
-
f"--with-tcltk-libs='-L{tcl_tk_prefix}/lib -ltcl{tcl_tk_ver} -ltk{tcl_tk_ver}'",
|
214
|
-
])
|
215
|
-
|
216
|
-
# @cached_nullary
|
217
|
-
# def brew_ssl_opts(self) -> PyenvInstallOpts:
|
218
|
-
# pkg_config_path = subprocess_check_output_str('brew', '--prefix', 'openssl')
|
219
|
-
# if 'PKG_CONFIG_PATH' in os.environ:
|
220
|
-
# pkg_config_path += ':' + os.environ['PKG_CONFIG_PATH']
|
221
|
-
# return PyenvInstallOpts(env={'PKG_CONFIG_PATH': pkg_config_path})
|
222
|
-
|
223
|
-
async def opts(self) -> PyenvInstallOpts:
|
224
|
-
return PyenvInstallOpts().merge(
|
225
|
-
self.framework_opts(),
|
226
|
-
await self.brew_deps_opts(),
|
227
|
-
await self.brew_tcl_opts(),
|
228
|
-
# self.brew_ssl_opts(),
|
229
|
-
)
|
230
|
-
|
231
|
-
|
232
|
-
PLATFORM_PYENV_INSTALL_OPTS: ta.Dict[str, PyenvInstallOptsProvider] = {
|
233
|
-
'darwin': DarwinPyenvInstallOpts(),
|
234
|
-
'linux': LinuxPyenvInstallOpts(),
|
235
|
-
}
|
236
|
-
|
237
|
-
|
238
|
-
##
|
239
|
-
|
240
|
-
|
241
|
-
class PyenvVersionInstaller:
|
242
|
-
"""
|
243
|
-
Messy: can install freethreaded build with a 't' suffixed version str _or_ by THREADED_PYENV_INSTALL_OPTS - need
|
244
|
-
latter to build custom interp with ft, need former to use canned / blessed interps. Muh.
|
245
|
-
"""
|
246
|
-
|
247
|
-
def __init__(
|
248
|
-
self,
|
249
|
-
version: str,
|
250
|
-
opts: ta.Optional[PyenvInstallOpts] = None,
|
251
|
-
interp_opts: InterpOpts = InterpOpts(),
|
252
|
-
*,
|
253
|
-
pyenv: Pyenv,
|
254
|
-
|
255
|
-
install_name: ta.Optional[str] = None,
|
256
|
-
no_default_opts: bool = False,
|
257
|
-
) -> None:
|
258
|
-
super().__init__()
|
259
|
-
|
260
|
-
self._version = version
|
261
|
-
self._given_opts = opts
|
262
|
-
self._interp_opts = interp_opts
|
263
|
-
self._given_install_name = install_name
|
264
|
-
|
265
|
-
self._no_default_opts = no_default_opts
|
266
|
-
self._pyenv = pyenv
|
267
|
-
|
268
|
-
@property
|
269
|
-
def version(self) -> str:
|
270
|
-
return self._version
|
271
|
-
|
272
|
-
@async_cached_nullary
|
273
|
-
async def opts(self) -> PyenvInstallOpts:
|
274
|
-
opts = self._given_opts
|
275
|
-
if self._no_default_opts:
|
276
|
-
if opts is None:
|
277
|
-
opts = PyenvInstallOpts()
|
278
|
-
else:
|
279
|
-
lst = [self._given_opts if self._given_opts is not None else DEFAULT_PYENV_INSTALL_OPTS]
|
280
|
-
if self._interp_opts.debug:
|
281
|
-
lst.append(DEBUG_PYENV_INSTALL_OPTS)
|
282
|
-
if self._interp_opts.threaded:
|
283
|
-
lst.append(THREADED_PYENV_INSTALL_OPTS)
|
284
|
-
lst.append(await PLATFORM_PYENV_INSTALL_OPTS[sys.platform].opts())
|
285
|
-
opts = PyenvInstallOpts().merge(*lst)
|
286
|
-
return opts
|
287
|
-
|
288
|
-
@cached_nullary
|
289
|
-
def install_name(self) -> str:
|
290
|
-
if self._given_install_name is not None:
|
291
|
-
return self._given_install_name
|
292
|
-
return self._version + ('-debug' if self._interp_opts.debug else '')
|
293
|
-
|
294
|
-
@async_cached_nullary
|
295
|
-
async def install_dir(self) -> str:
|
296
|
-
return str(os.path.join(check.not_none(await self._pyenv.root()), 'versions', self.install_name()))
|
297
|
-
|
298
|
-
@async_cached_nullary
|
299
|
-
async def install(self) -> str:
|
300
|
-
opts = await self.opts()
|
301
|
-
env = {**os.environ, **opts.env}
|
302
|
-
for k, l in [
|
303
|
-
('CFLAGS', opts.cflags),
|
304
|
-
('LDFLAGS', opts.ldflags),
|
305
|
-
('PYTHON_CONFIGURE_OPTS', opts.conf_opts),
|
306
|
-
]:
|
307
|
-
v = ' '.join(l)
|
308
|
-
if k in os.environ:
|
309
|
-
v += ' ' + os.environ[k]
|
310
|
-
env[k] = v
|
311
|
-
|
312
|
-
conf_args = [
|
313
|
-
*opts.opts,
|
314
|
-
self._version,
|
315
|
-
]
|
316
|
-
|
317
|
-
full_args: ta.List[str]
|
318
|
-
if self._given_install_name is not None:
|
319
|
-
full_args = [
|
320
|
-
os.path.join(check.not_none(await self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'), # noqa
|
321
|
-
*conf_args,
|
322
|
-
await self.install_dir(),
|
323
|
-
]
|
324
|
-
else:
|
325
|
-
full_args = [
|
326
|
-
await self._pyenv.exe(),
|
327
|
-
'install',
|
328
|
-
*conf_args,
|
329
|
-
]
|
330
|
-
|
331
|
-
await asyncio_subprocesses.check_call(
|
332
|
-
*full_args,
|
333
|
-
env=env,
|
334
|
-
)
|
335
|
-
|
336
|
-
exe = os.path.join(await self.install_dir(), 'bin', 'python')
|
337
|
-
if not os.path.isfile(exe):
|
338
|
-
raise RuntimeError(f'Interpreter not found: {exe}')
|
339
|
-
return exe
|
340
|
-
|
341
|
-
|
342
|
-
##
|
343
|
-
|
344
|
-
|
345
|
-
class PyenvInterpProvider(InterpProvider):
|
346
|
-
@dc.dataclass(frozen=True)
|
347
|
-
class Options:
|
348
|
-
inspect: bool = False
|
349
|
-
|
350
|
-
try_update: bool = False
|
351
|
-
|
352
|
-
def __init__(
|
353
|
-
self,
|
354
|
-
options: Options = Options(),
|
355
|
-
*,
|
356
|
-
pyenv: Pyenv,
|
357
|
-
inspector: InterpInspector,
|
358
|
-
) -> None:
|
359
|
-
super().__init__()
|
360
|
-
|
361
|
-
self._options = options
|
362
|
-
|
363
|
-
self._pyenv = pyenv
|
364
|
-
self._inspector = inspector
|
365
|
-
|
366
|
-
#
|
367
|
-
|
368
|
-
@staticmethod
|
369
|
-
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
370
|
-
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
371
|
-
if s.endswith(sfx):
|
372
|
-
return s[:-len(sfx)], True
|
373
|
-
return s, False
|
374
|
-
ok = {}
|
375
|
-
s, ok['debug'] = strip_sfx(s, '-debug')
|
376
|
-
s, ok['threaded'] = strip_sfx(s, 't')
|
377
|
-
try:
|
378
|
-
v = Version(s)
|
379
|
-
except InvalidVersion:
|
380
|
-
return None
|
381
|
-
return InterpVersion(v, InterpOpts(**ok))
|
382
|
-
|
383
|
-
class Installed(ta.NamedTuple):
|
384
|
-
name: str
|
385
|
-
exe: str
|
386
|
-
version: InterpVersion
|
387
|
-
|
388
|
-
async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
389
|
-
iv: ta.Optional[InterpVersion]
|
390
|
-
if self._options.inspect:
|
391
|
-
try:
|
392
|
-
iv = check.not_none(await self._inspector.inspect(ep)).iv
|
393
|
-
except Exception as e: # noqa
|
394
|
-
return None
|
395
|
-
else:
|
396
|
-
iv = self.guess_version(vn)
|
397
|
-
if iv is None:
|
398
|
-
return None
|
399
|
-
return PyenvInterpProvider.Installed(
|
400
|
-
name=vn,
|
401
|
-
exe=ep,
|
402
|
-
version=iv,
|
403
|
-
)
|
404
|
-
|
405
|
-
async def installed(self) -> ta.Sequence[Installed]:
|
406
|
-
ret: ta.List[PyenvInterpProvider.Installed] = []
|
407
|
-
for vn, ep in await self._pyenv.version_exes():
|
408
|
-
if (i := await self._make_installed(vn, ep)) is None:
|
409
|
-
log.debug('Invalid pyenv version: %s', vn)
|
410
|
-
continue
|
411
|
-
ret.append(i)
|
412
|
-
return ret
|
413
|
-
|
414
|
-
#
|
415
|
-
|
416
|
-
async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
417
|
-
return [i.version for i in await self.installed()]
|
418
|
-
|
419
|
-
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
420
|
-
for i in await self.installed():
|
421
|
-
if i.version == version:
|
422
|
-
return Interp(
|
423
|
-
exe=i.exe,
|
424
|
-
version=i.version,
|
425
|
-
)
|
426
|
-
raise KeyError(version)
|
427
|
-
|
428
|
-
#
|
429
|
-
|
430
|
-
async def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
431
|
-
lst = []
|
432
|
-
|
433
|
-
for vs in await self._pyenv.installable_versions():
|
434
|
-
if (iv := self.guess_version(vs)) is None:
|
435
|
-
continue
|
436
|
-
if iv.opts.debug:
|
437
|
-
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
438
|
-
for d in [False, True]:
|
439
|
-
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
440
|
-
|
441
|
-
return lst
|
442
|
-
|
443
|
-
async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
444
|
-
lst = await self._get_installable_versions(spec)
|
445
|
-
|
446
|
-
if self._options.try_update and not any(v in spec for v in lst):
|
447
|
-
if self._pyenv.update():
|
448
|
-
lst = await self._get_installable_versions(spec)
|
449
|
-
|
450
|
-
return lst
|
451
|
-
|
452
|
-
async def install_version(self, version: InterpVersion) -> Interp:
|
453
|
-
inst_version = str(version.version)
|
454
|
-
inst_opts = version.opts
|
455
|
-
if inst_opts.threaded:
|
456
|
-
inst_version += 't'
|
457
|
-
inst_opts = dc.replace(inst_opts, threaded=False)
|
458
|
-
|
459
|
-
installer = PyenvVersionInstaller(
|
460
|
-
inst_version,
|
461
|
-
interp_opts=inst_opts,
|
462
|
-
pyenv=self._pyenv,
|
463
|
-
)
|
464
|
-
|
465
|
-
exe = await installer.install()
|
466
|
-
return Interp(exe, version)
|
omdev/interp/venvs.py
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import dataclasses as dc
|
3
|
+
import logging
|
4
|
+
import os.path
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
|
8
|
+
from omlish.lite.cached import async_cached_nullary
|
9
|
+
from omlish.lite.cached import cached_nullary
|
10
|
+
from omlish.lite.check import check
|
11
|
+
from omlish.lite.typing import Func2
|
12
|
+
|
13
|
+
from .default import get_default_interp_resolver
|
14
|
+
from .types import InterpSpecifier
|
15
|
+
|
16
|
+
|
17
|
+
##
|
18
|
+
|
19
|
+
|
20
|
+
@dc.dataclass(frozen=True)
|
21
|
+
class InterpVenvConfig:
|
22
|
+
interp: ta.Optional[str] = None
|
23
|
+
requires: ta.Optional[ta.Sequence[str]] = None
|
24
|
+
use_uv: ta.Optional[bool] = None
|
25
|
+
|
26
|
+
|
27
|
+
class InterpVenvRequirementsProcessor(Func2['InterpVenv', ta.Sequence[str], ta.Sequence[str]]):
|
28
|
+
pass
|
29
|
+
|
30
|
+
|
31
|
+
class InterpVenv:
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
path: str,
|
35
|
+
cfg: InterpVenvConfig,
|
36
|
+
*,
|
37
|
+
requirements_processor: ta.Optional[InterpVenvRequirementsProcessor] = None,
|
38
|
+
log: ta.Optional[logging.Logger] = None,
|
39
|
+
) -> None:
|
40
|
+
super().__init__()
|
41
|
+
|
42
|
+
self._path = path
|
43
|
+
self._cfg = cfg
|
44
|
+
|
45
|
+
self._requirements_processor = requirements_processor
|
46
|
+
self._log = log
|
47
|
+
|
48
|
+
@property
|
49
|
+
def path(self) -> str:
|
50
|
+
return self._path
|
51
|
+
|
52
|
+
@property
|
53
|
+
def cfg(self) -> InterpVenvConfig:
|
54
|
+
return self._cfg
|
55
|
+
|
56
|
+
@async_cached_nullary
|
57
|
+
async def interp_exe(self) -> str:
|
58
|
+
i = InterpSpecifier.parse(check.not_none(self._cfg.interp))
|
59
|
+
return check.not_none(await get_default_interp_resolver().resolve(i, install=True)).exe
|
60
|
+
|
61
|
+
@cached_nullary
|
62
|
+
def exe(self) -> str:
|
63
|
+
ve = os.path.join(self._path, 'bin/python')
|
64
|
+
if not os.path.isfile(ve):
|
65
|
+
raise Exception(f'venv exe {ve} does not exist or is not a file!')
|
66
|
+
return ve
|
67
|
+
|
68
|
+
@async_cached_nullary
|
69
|
+
async def create(self) -> bool:
|
70
|
+
if os.path.exists(dn := self._path):
|
71
|
+
if not os.path.isdir(dn):
|
72
|
+
raise Exception(f'{dn} exists but is not a directory!')
|
73
|
+
return False
|
74
|
+
|
75
|
+
ie = await self.interp_exe()
|
76
|
+
|
77
|
+
if self._log is not None:
|
78
|
+
self._log.info('Using interpreter %s', ie)
|
79
|
+
|
80
|
+
await asyncio_subprocesses.check_call(ie, '-m', 'venv', dn)
|
81
|
+
|
82
|
+
ve = self.exe()
|
83
|
+
uv = self._cfg.use_uv
|
84
|
+
|
85
|
+
await asyncio_subprocesses.check_call(
|
86
|
+
ve,
|
87
|
+
'-m', 'pip',
|
88
|
+
'install', '-v', '--upgrade',
|
89
|
+
'pip',
|
90
|
+
'setuptools',
|
91
|
+
'wheel',
|
92
|
+
*(['uv'] if uv else []),
|
93
|
+
)
|
94
|
+
|
95
|
+
if sr := self._cfg.requires:
|
96
|
+
reqs = list(sr)
|
97
|
+
if self._requirements_processor is not None:
|
98
|
+
reqs = list(self._requirements_processor(self, reqs))
|
99
|
+
|
100
|
+
# TODO: automatically try slower uv download when it fails? lol
|
101
|
+
# Caused by: Failed to download distribution due to network timeout. Try increasing UV_HTTP_TIMEOUT (current value: 30s). # noqa
|
102
|
+
# UV_CONCURRENT_DOWNLOADS=4 UV_HTTP_TIMEOUT=3600
|
103
|
+
|
104
|
+
await asyncio_subprocesses.check_call(
|
105
|
+
ve,
|
106
|
+
'-m',
|
107
|
+
*(['uv'] if uv else []),
|
108
|
+
'pip',
|
109
|
+
'install',
|
110
|
+
*([] if uv else ['-v']),
|
111
|
+
*reqs,
|
112
|
+
)
|
113
|
+
|
114
|
+
return True
|
omdev/pyproject/configs.py
CHANGED
@@ -4,15 +4,14 @@ import typing as ta
|
|
4
4
|
|
5
5
|
from omlish.lite.marshal import unmarshal_obj
|
6
6
|
|
7
|
+
from ..interp.venvs import InterpVenvConfig
|
8
|
+
|
7
9
|
|
8
10
|
@dc.dataclass(frozen=True)
|
9
|
-
class VenvConfig:
|
11
|
+
class VenvConfig(InterpVenvConfig):
|
10
12
|
inherits: ta.Optional[ta.Sequence[str]] = None
|
11
|
-
interp: ta.Optional[str] = None
|
12
|
-
requires: ta.Optional[ta.List[str]] = None
|
13
13
|
docker: ta.Optional[str] = None
|
14
14
|
srcs: ta.Optional[ta.List[str]] = None
|
15
|
-
use_uv: ta.Optional[bool] = None
|
16
15
|
|
17
16
|
|
18
17
|
@dc.dataclass(frozen=True)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from omlish.lite.inject import InjectorBindingOrBindings
|
5
|
+
from omlish.lite.inject import InjectorBindings
|
6
|
+
from omlish.lite.inject import inj
|
7
|
+
|
8
|
+
|
9
|
+
def bind_pyproject() -> InjectorBindings:
|
10
|
+
lst: ta.List[InjectorBindingOrBindings] = []
|
11
|
+
|
12
|
+
return inj.as_bindings(*lst)
|
omdev/pyproject/venvs.py
CHANGED
@@ -3,14 +3,12 @@ import glob
|
|
3
3
|
import os.path
|
4
4
|
import typing as ta
|
5
5
|
|
6
|
-
from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
|
7
6
|
from omlish.lite.cached import async_cached_nullary
|
8
7
|
from omlish.lite.cached import cached_nullary
|
9
|
-
from omlish.lite.check import check
|
10
8
|
from omlish.lite.logs import log
|
11
9
|
|
12
|
-
from ..interp.
|
13
|
-
from ..interp.
|
10
|
+
from ..interp.venvs import InterpVenv
|
11
|
+
from ..interp.venvs import InterpVenvRequirementsProcessor
|
14
12
|
from .configs import VenvConfig
|
15
13
|
from .reqs import RequirementsRewriter
|
16
14
|
|
@@ -38,60 +36,26 @@ class Venv:
|
|
38
36
|
def dir_name(self) -> str:
|
39
37
|
return os.path.join(self.DIR_NAME, self._name)
|
40
38
|
|
41
|
-
@
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
@cached_nullary
|
40
|
+
def _iv(self) -> InterpVenv:
|
41
|
+
rr = RequirementsRewriter(self._name)
|
42
|
+
|
43
|
+
return InterpVenv(
|
44
|
+
self.dir_name,
|
45
|
+
self._cfg,
|
46
|
+
requirements_processor=InterpVenvRequirementsProcessor(
|
47
|
+
lambda iv, reqs: [rr.rewrite(req) for req in reqs] # noqa
|
48
|
+
),
|
49
|
+
log=log,
|
50
|
+
)
|
45
51
|
|
46
52
|
@cached_nullary
|
47
53
|
def exe(self) -> str:
|
48
|
-
|
49
|
-
if not os.path.isfile(ve):
|
50
|
-
raise Exception(f'venv exe {ve} does not exist or is not a file!')
|
51
|
-
return ve
|
54
|
+
return self._iv().exe()
|
52
55
|
|
53
56
|
@async_cached_nullary
|
54
57
|
async def create(self) -> bool:
|
55
|
-
|
56
|
-
if not os.path.isdir(dn):
|
57
|
-
raise Exception(f'{dn} exists but is not a directory!')
|
58
|
-
return False
|
59
|
-
|
60
|
-
log.info('Using interpreter %s', (ie := await self.interp_exe()))
|
61
|
-
await asyncio_subprocesses.check_call(ie, '-m', 'venv', dn)
|
62
|
-
|
63
|
-
ve = self.exe()
|
64
|
-
uv = self._cfg.use_uv
|
65
|
-
|
66
|
-
await asyncio_subprocesses.check_call(
|
67
|
-
ve,
|
68
|
-
'-m', 'pip',
|
69
|
-
'install', '-v', '--upgrade',
|
70
|
-
'pip',
|
71
|
-
'setuptools',
|
72
|
-
'wheel',
|
73
|
-
*(['uv'] if uv else []),
|
74
|
-
)
|
75
|
-
|
76
|
-
if sr := self._cfg.requires:
|
77
|
-
rr = RequirementsRewriter(self._name)
|
78
|
-
reqs = [rr.rewrite(req) for req in sr]
|
79
|
-
|
80
|
-
# TODO: automatically try slower uv download when it fails? lol
|
81
|
-
# Caused by: Failed to download distribution due to network timeout. Try increasing UV_HTTP_TIMEOUT (current value: 30s). # noqa
|
82
|
-
# UV_CONCURRENT_DOWNLOADS=4 UV_HTTP_TIMEOUT=3600
|
83
|
-
|
84
|
-
await asyncio_subprocesses.check_call(
|
85
|
-
ve,
|
86
|
-
'-m',
|
87
|
-
*(['uv'] if uv else []),
|
88
|
-
'pip',
|
89
|
-
'install',
|
90
|
-
*([] if uv else ['-v']),
|
91
|
-
*reqs,
|
92
|
-
)
|
93
|
-
|
94
|
-
return True
|
58
|
+
return await self._iv().create()
|
95
59
|
|
96
60
|
@staticmethod
|
97
61
|
def _resolve_srcs(raw: ta.List[str]) -> ta.List[str]:
|