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/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]:
|