omdev 0.0.0.dev7__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/__about__.py +35 -0
- omdev/__init__.py +0 -0
- omdev/amalg/__init__.py +0 -0
- omdev/amalg/__main__.py +4 -0
- omdev/amalg/amalg.py +513 -0
- omdev/classdot.py +61 -0
- omdev/cmake.py +164 -0
- omdev/exts/__init__.py +0 -0
- omdev/exts/_distutils/__init__.py +10 -0
- omdev/exts/_distutils/build_ext.py +367 -0
- omdev/exts/_distutils/compilers/__init__.py +3 -0
- omdev/exts/_distutils/compilers/ccompiler.py +1032 -0
- omdev/exts/_distutils/compilers/options.py +80 -0
- omdev/exts/_distutils/compilers/unixccompiler.py +385 -0
- omdev/exts/_distutils/dir_util.py +76 -0
- omdev/exts/_distutils/errors.py +62 -0
- omdev/exts/_distutils/extension.py +107 -0
- omdev/exts/_distutils/file_util.py +216 -0
- omdev/exts/_distutils/modified.py +47 -0
- omdev/exts/_distutils/spawn.py +103 -0
- omdev/exts/_distutils/sysconfig.py +349 -0
- omdev/exts/_distutils/util.py +201 -0
- omdev/exts/_distutils/version.py +308 -0
- omdev/exts/build.py +43 -0
- omdev/exts/cmake.py +195 -0
- omdev/exts/importhook.py +88 -0
- omdev/exts/scan.py +74 -0
- omdev/interp/__init__.py +1 -0
- omdev/interp/__main__.py +4 -0
- omdev/interp/cli.py +63 -0
- omdev/interp/inspect.py +105 -0
- omdev/interp/providers.py +67 -0
- omdev/interp/pyenv.py +353 -0
- omdev/interp/resolvers.py +76 -0
- omdev/interp/standalone.py +187 -0
- omdev/interp/system.py +125 -0
- omdev/interp/types.py +92 -0
- omdev/mypy/__init__.py +0 -0
- omdev/mypy/debug.py +86 -0
- omdev/pyproject/__init__.py +1 -0
- omdev/pyproject/__main__.py +4 -0
- omdev/pyproject/cli.py +319 -0
- omdev/pyproject/configs.py +97 -0
- omdev/pyproject/ext.py +107 -0
- omdev/pyproject/pkg.py +196 -0
- omdev/scripts/__init__.py +0 -0
- omdev/scripts/execrss.py +19 -0
- omdev/scripts/findimports.py +62 -0
- omdev/scripts/findmagic.py +70 -0
- omdev/scripts/interp.py +2118 -0
- omdev/scripts/pyproject.py +3584 -0
- omdev/scripts/traceimport.py +502 -0
- omdev/tokens.py +42 -0
- omdev/toml/__init__.py +1 -0
- omdev/toml/parser.py +823 -0
- omdev/toml/writer.py +104 -0
- omdev/tools/__init__.py +0 -0
- omdev/tools/dockertools.py +81 -0
- omdev/tools/sqlrepl.py +193 -0
- omdev/versioning/__init__.py +1 -0
- omdev/versioning/specifiers.py +531 -0
- omdev/versioning/versions.py +416 -0
- omdev-0.0.0.dev7.dist-info/LICENSE +21 -0
- omdev-0.0.0.dev7.dist-info/METADATA +24 -0
- omdev-0.0.0.dev7.dist-info/RECORD +67 -0
- omdev-0.0.0.dev7.dist-info/WHEEL +5 -0
- omdev-0.0.0.dev7.dist-info/top_level.txt +1 -0
omdev/exts/scan.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import os.path
|
|
4
|
+
import typing as ta
|
|
5
|
+
|
|
6
|
+
from omlish import logs
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
SCAN_COMMENT = '// @omdev-ext'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _scan_one(
|
|
16
|
+
input_path: str,
|
|
17
|
+
**kwargs: ta.Any,
|
|
18
|
+
) -> None:
|
|
19
|
+
if not any(input_path.endswith(fx) for fx in ('.c', '.cc', '.cpp')):
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
with open(input_path, 'rb') as f:
|
|
23
|
+
srcb = f.read()
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
src = srcb.decode('utf-8')
|
|
27
|
+
except UnicodeDecodeError:
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
sls = [l for l in src.splitlines() if l.startswith(SCAN_COMMENT)]
|
|
31
|
+
for sl in sls:
|
|
32
|
+
sas = sl[len(SCAN_COMMENT):].split() # noqa
|
|
33
|
+
|
|
34
|
+
log.info('Found ext: %s', input_path)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _scan_cmd(args) -> None:
|
|
38
|
+
for i in args.inputs:
|
|
39
|
+
if not os.path.isdir(i):
|
|
40
|
+
raise Exception(f'Not a directory: {i}')
|
|
41
|
+
|
|
42
|
+
log.info('Scanning %s', i)
|
|
43
|
+
for we_dirpath, we_dirnames, we_filenames in os.walk(i): # noqa
|
|
44
|
+
for fname in we_filenames:
|
|
45
|
+
_scan_one(
|
|
46
|
+
os.path.abspath(os.path.join(we_dirpath, fname)),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
51
|
+
parser = argparse.ArgumentParser()
|
|
52
|
+
|
|
53
|
+
subparsers = parser.add_subparsers()
|
|
54
|
+
|
|
55
|
+
parser_scan = subparsers.add_parser('scan')
|
|
56
|
+
parser_scan.add_argument('inputs', nargs='+')
|
|
57
|
+
parser_scan.set_defaults(func=_scan_cmd)
|
|
58
|
+
|
|
59
|
+
return parser
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _main() -> None:
|
|
63
|
+
logs.configure_standard_logging('INFO')
|
|
64
|
+
|
|
65
|
+
parser = _build_parser()
|
|
66
|
+
args = parser.parse_args()
|
|
67
|
+
if not getattr(args, 'func', None):
|
|
68
|
+
parser.print_help()
|
|
69
|
+
else:
|
|
70
|
+
args.func(args)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
if __name__ == '__main__':
|
|
74
|
+
_main()
|
omdev/interp/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# @omlish-lite
|
omdev/interp/__main__.py
ADDED
omdev/interp/cli.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# @omdev-amalg ../scripts/interp.py
|
|
3
|
+
# ruff: noqa: UP007
|
|
4
|
+
"""
|
|
5
|
+
TODO:
|
|
6
|
+
- partial best-matches - '3.12'
|
|
7
|
+
- https://github.com/asdf-vm/asdf support (instead of pyenv) ?
|
|
8
|
+
- colon sep provider name prefix - pyenv:3.12
|
|
9
|
+
"""
|
|
10
|
+
import argparse
|
|
11
|
+
import typing as ta
|
|
12
|
+
|
|
13
|
+
from omlish.lite.logs import configure_standard_logging
|
|
14
|
+
from omlish.lite.runtime import check_runtime_version
|
|
15
|
+
|
|
16
|
+
from .resolvers import DEFAULT_INTERP_RESOLVER
|
|
17
|
+
from .types import InterpSpecifier
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _list_cmd(args) -> None:
|
|
21
|
+
r = DEFAULT_INTERP_RESOLVER
|
|
22
|
+
s = InterpSpecifier.parse(args.version)
|
|
23
|
+
r.list(s)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _resolve_cmd(args) -> None:
|
|
27
|
+
r = DEFAULT_INTERP_RESOLVER
|
|
28
|
+
s = InterpSpecifier.parse(args.version)
|
|
29
|
+
print(r.resolve(s).exe)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
33
|
+
parser = argparse.ArgumentParser()
|
|
34
|
+
|
|
35
|
+
subparsers = parser.add_subparsers()
|
|
36
|
+
|
|
37
|
+
parser_list = subparsers.add_parser('list')
|
|
38
|
+
parser_list.add_argument('version')
|
|
39
|
+
parser_list.add_argument('--debug', action='store_true')
|
|
40
|
+
parser_list.set_defaults(func=_list_cmd)
|
|
41
|
+
|
|
42
|
+
parser_resolve = subparsers.add_parser('resolve')
|
|
43
|
+
parser_resolve.add_argument('version')
|
|
44
|
+
parser_resolve.add_argument('--debug', action='store_true')
|
|
45
|
+
parser_resolve.set_defaults(func=_resolve_cmd)
|
|
46
|
+
|
|
47
|
+
return parser
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _main(argv: ta.Optional[ta.Sequence[str]] = None) -> None:
|
|
51
|
+
check_runtime_version()
|
|
52
|
+
configure_standard_logging()
|
|
53
|
+
|
|
54
|
+
parser = _build_parser()
|
|
55
|
+
args = parser.parse_args(argv)
|
|
56
|
+
if not getattr(args, 'func', None):
|
|
57
|
+
parser.print_help()
|
|
58
|
+
else:
|
|
59
|
+
args.func(args)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
|
+
_main()
|
omdev/interp/inspect.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import sys
|
|
6
|
+
import typing as ta
|
|
7
|
+
|
|
8
|
+
from omlish.lite.logs import log
|
|
9
|
+
from omlish.lite.subprocesses import subprocess_check_output
|
|
10
|
+
|
|
11
|
+
from ..versioning.versions import Version
|
|
12
|
+
from .types import InterpOpts
|
|
13
|
+
from .types import InterpVersion
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dc.dataclass(frozen=True)
|
|
17
|
+
class InterpInspection:
|
|
18
|
+
exe: str
|
|
19
|
+
version: Version
|
|
20
|
+
|
|
21
|
+
version_str: str
|
|
22
|
+
config_vars: ta.Mapping[str, str]
|
|
23
|
+
prefix: str
|
|
24
|
+
base_prefix: str
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def opts(self) -> InterpOpts:
|
|
28
|
+
return InterpOpts(
|
|
29
|
+
threaded=bool(self.config_vars.get('Py_GIL_DISABLED')),
|
|
30
|
+
debug=bool(self.config_vars.get('Py_DEBUG')),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def iv(self) -> InterpVersion:
|
|
35
|
+
return InterpVersion(
|
|
36
|
+
version=self.version,
|
|
37
|
+
opts=self.opts,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def is_venv(self) -> bool:
|
|
42
|
+
return self.prefix != self.base_prefix
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class InterpInspector:
|
|
46
|
+
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
super().__init__()
|
|
49
|
+
|
|
50
|
+
self._cache: ta.Dict[str, ta.Optional[InterpInspection]] = {}
|
|
51
|
+
|
|
52
|
+
_RAW_INSPECTION_CODE = """
|
|
53
|
+
__import__('json').dumps(dict(
|
|
54
|
+
version_str=__import__('sys').version,
|
|
55
|
+
prefix=__import__('sys').prefix,
|
|
56
|
+
base_prefix=__import__('sys').base_prefix,
|
|
57
|
+
config_vars=__import__('sysconfig').get_config_vars(),
|
|
58
|
+
))"""
|
|
59
|
+
|
|
60
|
+
_INSPECTION_CODE = ''.join(l.strip() for l in _RAW_INSPECTION_CODE.splitlines())
|
|
61
|
+
|
|
62
|
+
@staticmethod
|
|
63
|
+
def _build_inspection(
|
|
64
|
+
exe: str,
|
|
65
|
+
output: str,
|
|
66
|
+
) -> InterpInspection:
|
|
67
|
+
dct = json.loads(output)
|
|
68
|
+
|
|
69
|
+
version = Version(dct['version_str'].split()[0])
|
|
70
|
+
|
|
71
|
+
return InterpInspection(
|
|
72
|
+
exe=exe,
|
|
73
|
+
version=version,
|
|
74
|
+
**{k: dct[k] for k in (
|
|
75
|
+
'version_str',
|
|
76
|
+
'prefix',
|
|
77
|
+
'base_prefix',
|
|
78
|
+
'config_vars',
|
|
79
|
+
)},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def running(cls) -> 'InterpInspection':
|
|
84
|
+
return cls._build_inspection(sys.executable, eval(cls._INSPECTION_CODE)) # noqa
|
|
85
|
+
|
|
86
|
+
def _inspect(self, exe: str) -> InterpInspection:
|
|
87
|
+
output = subprocess_check_output(exe, '-c', f'print({self._INSPECTION_CODE})', quiet=True)
|
|
88
|
+
return self._build_inspection(exe, output.decode())
|
|
89
|
+
|
|
90
|
+
def inspect(self, exe: str) -> ta.Optional[InterpInspection]:
|
|
91
|
+
try:
|
|
92
|
+
return self._cache[exe]
|
|
93
|
+
except KeyError:
|
|
94
|
+
ret: ta.Optional[InterpInspection]
|
|
95
|
+
try:
|
|
96
|
+
ret = self._inspect(exe)
|
|
97
|
+
except Exception as e: # noqa
|
|
98
|
+
if log.isEnabledFor(logging.DEBUG):
|
|
99
|
+
log.exception('Failed to inspect interp: %s', exe)
|
|
100
|
+
ret = None
|
|
101
|
+
self._cache[exe] = ret
|
|
102
|
+
return ret
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
INTERP_INSPECTOR = InterpInspector()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- backends
|
|
4
|
+
- local builds
|
|
5
|
+
- deadsnakes?
|
|
6
|
+
- loose versions
|
|
7
|
+
"""
|
|
8
|
+
import abc
|
|
9
|
+
import sys
|
|
10
|
+
import typing as ta
|
|
11
|
+
|
|
12
|
+
from omlish.lite.cached import cached_nullary
|
|
13
|
+
from omlish.lite.strings import snake_case
|
|
14
|
+
|
|
15
|
+
from .inspect import InterpInspector
|
|
16
|
+
from .types import Interp
|
|
17
|
+
from .types import InterpSpecifier
|
|
18
|
+
from .types import InterpVersion
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class InterpProvider(abc.ABC):
|
|
25
|
+
name: ta.ClassVar[str]
|
|
26
|
+
|
|
27
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
|
28
|
+
super().__init_subclass__(**kwargs)
|
|
29
|
+
if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
|
|
30
|
+
sfx = 'InterpProvider'
|
|
31
|
+
if not cls.__name__.endswith(sfx):
|
|
32
|
+
raise NameError(cls)
|
|
33
|
+
setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
|
|
34
|
+
|
|
35
|
+
@abc.abstractmethod
|
|
36
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
@abc.abstractmethod
|
|
40
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
41
|
+
raise NotImplementedError
|
|
42
|
+
|
|
43
|
+
def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
def install_version(self, version: InterpVersion) -> Interp:
|
|
47
|
+
raise TypeError
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RunningInterpProvider(InterpProvider):
|
|
54
|
+
@cached_nullary
|
|
55
|
+
def version(self) -> InterpVersion:
|
|
56
|
+
return InterpInspector.running().iv
|
|
57
|
+
|
|
58
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
59
|
+
return [self.version()]
|
|
60
|
+
|
|
61
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
62
|
+
if version != self.version():
|
|
63
|
+
raise KeyError(version)
|
|
64
|
+
return Interp(
|
|
65
|
+
exe=sys.executable,
|
|
66
|
+
version=self.version(),
|
|
67
|
+
)
|
omdev/interp/pyenv.py
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- custom tags
|
|
4
|
+
- optionally install / upgrade pyenv itself
|
|
5
|
+
- new vers dont need these custom mac opts, only run on old vers
|
|
6
|
+
"""
|
|
7
|
+
# ruff: noqa: UP006 UP007
|
|
8
|
+
import abc
|
|
9
|
+
import dataclasses as dc
|
|
10
|
+
import itertools
|
|
11
|
+
import os.path
|
|
12
|
+
import shutil
|
|
13
|
+
import sys
|
|
14
|
+
import typing as ta
|
|
15
|
+
|
|
16
|
+
from omlish.lite.cached import cached_nullary
|
|
17
|
+
from omlish.lite.check import check_not_none
|
|
18
|
+
from omlish.lite.logs import log
|
|
19
|
+
from omlish.lite.subprocesses import subprocess_check_call
|
|
20
|
+
from omlish.lite.subprocesses import subprocess_check_output_str
|
|
21
|
+
from omlish.lite.subprocesses import subprocess_try_output
|
|
22
|
+
|
|
23
|
+
from ..versioning.versions import InvalidVersion
|
|
24
|
+
from ..versioning.versions import Version
|
|
25
|
+
from .inspect import INTERP_INSPECTOR
|
|
26
|
+
from .inspect import InterpInspector
|
|
27
|
+
from .providers import InterpProvider
|
|
28
|
+
from .types import Interp
|
|
29
|
+
from .types import InterpOpts
|
|
30
|
+
from .types import InterpSpecifier
|
|
31
|
+
from .types import InterpVersion
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Pyenv:
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self,
|
|
41
|
+
*,
|
|
42
|
+
root: ta.Optional[str] = None,
|
|
43
|
+
) -> None:
|
|
44
|
+
if root is not None and not (isinstance(root, str) and root):
|
|
45
|
+
raise ValueError(f'pyenv_root: {root!r}')
|
|
46
|
+
|
|
47
|
+
super().__init__()
|
|
48
|
+
|
|
49
|
+
self._root_kw = root
|
|
50
|
+
|
|
51
|
+
@cached_nullary
|
|
52
|
+
def root(self) -> ta.Optional[str]:
|
|
53
|
+
if self._root_kw is not None:
|
|
54
|
+
return self._root_kw
|
|
55
|
+
|
|
56
|
+
if shutil.which('pyenv'):
|
|
57
|
+
return subprocess_check_output_str('pyenv', 'root')
|
|
58
|
+
|
|
59
|
+
d = os.path.expanduser('~/.pyenv')
|
|
60
|
+
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
|
61
|
+
return d
|
|
62
|
+
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
@cached_nullary
|
|
66
|
+
def exe(self) -> str:
|
|
67
|
+
return os.path.join(check_not_none(self.root()), 'bin', 'pyenv')
|
|
68
|
+
|
|
69
|
+
def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
|
|
70
|
+
ret = []
|
|
71
|
+
vp = os.path.join(self.root(), 'versions')
|
|
72
|
+
for dn in os.listdir(vp):
|
|
73
|
+
ep = os.path.join(vp, dn, 'bin', 'python')
|
|
74
|
+
if not os.path.isfile(ep):
|
|
75
|
+
continue
|
|
76
|
+
ret.append((dn, ep))
|
|
77
|
+
return ret
|
|
78
|
+
|
|
79
|
+
def installable_versions(self) -> ta.List[str]:
|
|
80
|
+
ret = []
|
|
81
|
+
s = subprocess_check_output_str(self.exe(), 'install', '--list')
|
|
82
|
+
for l in s.splitlines():
|
|
83
|
+
if not l.startswith(' '):
|
|
84
|
+
continue
|
|
85
|
+
l = l.strip()
|
|
86
|
+
if not l:
|
|
87
|
+
continue
|
|
88
|
+
ret.append(l)
|
|
89
|
+
return ret
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dc.dataclass(frozen=True)
|
|
96
|
+
class PyenvInstallOpts:
|
|
97
|
+
opts: ta.Sequence[str] = ()
|
|
98
|
+
conf_opts: ta.Sequence[str] = ()
|
|
99
|
+
cflags: ta.Sequence[str] = ()
|
|
100
|
+
ldflags: ta.Sequence[str] = ()
|
|
101
|
+
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
|
102
|
+
|
|
103
|
+
def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
|
|
104
|
+
return PyenvInstallOpts(
|
|
105
|
+
opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
|
|
106
|
+
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
|
107
|
+
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
|
108
|
+
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
|
109
|
+
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
DEFAULT_PYENV_INSTALL_OPTS = PyenvInstallOpts(opts=['-s', '-v'])
|
|
114
|
+
DEBUG_PYENV_INSTALL_OPTS = PyenvInstallOpts(opts=['-g'])
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
#
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class PyenvInstallOptsProvider(abc.ABC):
|
|
121
|
+
@abc.abstractmethod
|
|
122
|
+
def opts(self) -> PyenvInstallOpts:
|
|
123
|
+
raise NotImplementedError
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class LinuxPyenvInstallOpts(PyenvInstallOptsProvider):
|
|
127
|
+
def opts(self) -> PyenvInstallOpts:
|
|
128
|
+
return PyenvInstallOpts()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class DarwinPyenvInstallOpts(PyenvInstallOptsProvider):
|
|
132
|
+
|
|
133
|
+
@cached_nullary
|
|
134
|
+
def framework_opts(self) -> PyenvInstallOpts:
|
|
135
|
+
return PyenvInstallOpts(conf_opts=['--enable-framework'])
|
|
136
|
+
|
|
137
|
+
@cached_nullary
|
|
138
|
+
def has_brew(self) -> bool:
|
|
139
|
+
return shutil.which('brew') is not None
|
|
140
|
+
|
|
141
|
+
BREW_DEPS: ta.Sequence[str] = [
|
|
142
|
+
'openssl',
|
|
143
|
+
'readline',
|
|
144
|
+
'sqlite3',
|
|
145
|
+
'zlib',
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
@cached_nullary
|
|
149
|
+
def brew_deps_opts(self) -> PyenvInstallOpts:
|
|
150
|
+
cflags = []
|
|
151
|
+
ldflags = []
|
|
152
|
+
for dep in self.BREW_DEPS:
|
|
153
|
+
dep_prefix = subprocess_check_output_str('brew', '--prefix', dep)
|
|
154
|
+
cflags.append(f'-I{dep_prefix}/include')
|
|
155
|
+
ldflags.append(f'-L{dep_prefix}/lib')
|
|
156
|
+
return PyenvInstallOpts(
|
|
157
|
+
cflags=cflags,
|
|
158
|
+
ldflags=ldflags,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
@cached_nullary
|
|
162
|
+
def brew_tcl_opts(self) -> PyenvInstallOpts:
|
|
163
|
+
if subprocess_try_output('brew', '--prefix', 'tcl-tk') is None:
|
|
164
|
+
return PyenvInstallOpts()
|
|
165
|
+
|
|
166
|
+
tcl_tk_prefix = subprocess_check_output_str('brew', '--prefix', 'tcl-tk')
|
|
167
|
+
tcl_tk_ver_str = subprocess_check_output_str('brew', 'ls', '--versions', 'tcl-tk')
|
|
168
|
+
tcl_tk_ver = '.'.join(tcl_tk_ver_str.split()[1].split('.')[:2])
|
|
169
|
+
|
|
170
|
+
return PyenvInstallOpts(conf_opts=[
|
|
171
|
+
f"--with-tcltk-includes='-I{tcl_tk_prefix}/include'",
|
|
172
|
+
f"--with-tcltk-libs='-L{tcl_tk_prefix}/lib -ltcl{tcl_tk_ver} -ltk{tcl_tk_ver}'",
|
|
173
|
+
])
|
|
174
|
+
|
|
175
|
+
@cached_nullary
|
|
176
|
+
def brew_ssl_opts(self) -> PyenvInstallOpts:
|
|
177
|
+
pkg_config_path = subprocess_check_output_str('brew', '--prefix', 'openssl')
|
|
178
|
+
if 'PKG_CONFIG_PATH' in os.environ:
|
|
179
|
+
pkg_config_path += ':' + os.environ['PKG_CONFIG_PATH']
|
|
180
|
+
return PyenvInstallOpts(env={'PKG_CONFIG_PATH': pkg_config_path})
|
|
181
|
+
|
|
182
|
+
def opts(self) -> PyenvInstallOpts:
|
|
183
|
+
return PyenvInstallOpts().merge(
|
|
184
|
+
self.framework_opts(),
|
|
185
|
+
self.brew_deps_opts(),
|
|
186
|
+
self.brew_tcl_opts(),
|
|
187
|
+
self.brew_ssl_opts(),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
PLATFORM_PYENV_INSTALL_OPTS: ta.Dict[str, PyenvInstallOptsProvider] = {
|
|
192
|
+
'darwin': DarwinPyenvInstallOpts(),
|
|
193
|
+
'linux': LinuxPyenvInstallOpts(),
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
##
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class PyenvVersionInstaller:
|
|
201
|
+
|
|
202
|
+
def __init__(
|
|
203
|
+
self,
|
|
204
|
+
version: str,
|
|
205
|
+
opts: ta.Optional[PyenvInstallOpts] = None,
|
|
206
|
+
*,
|
|
207
|
+
debug: bool = False,
|
|
208
|
+
pyenv: Pyenv = Pyenv(),
|
|
209
|
+
) -> None:
|
|
210
|
+
super().__init__()
|
|
211
|
+
|
|
212
|
+
if opts is None:
|
|
213
|
+
lst = [DEFAULT_PYENV_INSTALL_OPTS]
|
|
214
|
+
if debug:
|
|
215
|
+
lst.append(DEBUG_PYENV_INSTALL_OPTS)
|
|
216
|
+
lst.append(PLATFORM_PYENV_INSTALL_OPTS[sys.platform].opts())
|
|
217
|
+
opts = PyenvInstallOpts().merge(*lst)
|
|
218
|
+
|
|
219
|
+
self._version = version
|
|
220
|
+
self._opts = opts
|
|
221
|
+
self._debug = debug
|
|
222
|
+
self._pyenv = pyenv
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def version(self) -> str:
|
|
226
|
+
return self._version
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def opts(self) -> PyenvInstallOpts:
|
|
230
|
+
return self._opts
|
|
231
|
+
|
|
232
|
+
@cached_nullary
|
|
233
|
+
def install_name(self) -> str:
|
|
234
|
+
return self._version + ('-debug' if self._debug else '')
|
|
235
|
+
|
|
236
|
+
@cached_nullary
|
|
237
|
+
def install_dir(self) -> str:
|
|
238
|
+
return str(os.path.join(check_not_none(self._pyenv.root()), 'versions', self.install_name()))
|
|
239
|
+
|
|
240
|
+
@cached_nullary
|
|
241
|
+
def install(self) -> str:
|
|
242
|
+
env = dict(self._opts.env)
|
|
243
|
+
for k, l in [
|
|
244
|
+
('CFLAGS', self._opts.cflags),
|
|
245
|
+
('LDFLAGS', self._opts.ldflags),
|
|
246
|
+
('PYTHON_CONFIGURE_OPTS', self._opts.conf_opts),
|
|
247
|
+
]:
|
|
248
|
+
v = ' '.join(l)
|
|
249
|
+
if k in os.environ:
|
|
250
|
+
v += ' ' + os.environ[k]
|
|
251
|
+
env[k] = v
|
|
252
|
+
|
|
253
|
+
subprocess_check_call(self._pyenv.exe(), 'install', *self._opts.opts, self._version, env=env)
|
|
254
|
+
|
|
255
|
+
exe = os.path.join(self.install_dir(), 'bin', 'python')
|
|
256
|
+
if not os.path.isfile(exe):
|
|
257
|
+
raise RuntimeError(f'Interpreter not found: {exe}')
|
|
258
|
+
return exe
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
##
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class PyenvInterpProvider(InterpProvider):
|
|
265
|
+
|
|
266
|
+
def __init__(
|
|
267
|
+
self,
|
|
268
|
+
pyenv: Pyenv = Pyenv(),
|
|
269
|
+
|
|
270
|
+
inspect: bool = False,
|
|
271
|
+
inspector: InterpInspector = INTERP_INSPECTOR,
|
|
272
|
+
) -> None:
|
|
273
|
+
super().__init__()
|
|
274
|
+
|
|
275
|
+
self._pyenv = pyenv
|
|
276
|
+
|
|
277
|
+
self._inspect = inspect
|
|
278
|
+
self._inspector = inspector
|
|
279
|
+
|
|
280
|
+
#
|
|
281
|
+
|
|
282
|
+
@staticmethod
|
|
283
|
+
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
|
284
|
+
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
|
285
|
+
if s.endswith(sfx):
|
|
286
|
+
return s[:-len(sfx)], True
|
|
287
|
+
return s, False
|
|
288
|
+
ok = {}
|
|
289
|
+
s, ok['debug'] = strip_sfx(s, '-debug')
|
|
290
|
+
s, ok['threaded'] = strip_sfx(s, 't')
|
|
291
|
+
try:
|
|
292
|
+
v = Version(s)
|
|
293
|
+
except InvalidVersion:
|
|
294
|
+
return None
|
|
295
|
+
return InterpVersion(v, InterpOpts(**ok))
|
|
296
|
+
|
|
297
|
+
class Installed(ta.NamedTuple):
|
|
298
|
+
name: str
|
|
299
|
+
exe: str
|
|
300
|
+
version: InterpVersion
|
|
301
|
+
|
|
302
|
+
def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
|
303
|
+
iv: ta.Optional[InterpVersion]
|
|
304
|
+
if self._inspect:
|
|
305
|
+
try:
|
|
306
|
+
iv = check_not_none(self._inspector.inspect(ep)).iv
|
|
307
|
+
except Exception as e: # noqa
|
|
308
|
+
return None
|
|
309
|
+
else:
|
|
310
|
+
iv = self.guess_version(vn)
|
|
311
|
+
if iv is None:
|
|
312
|
+
return None
|
|
313
|
+
return PyenvInterpProvider.Installed(
|
|
314
|
+
name=vn,
|
|
315
|
+
exe=ep,
|
|
316
|
+
version=iv,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def installed(self) -> ta.Sequence[Installed]:
|
|
320
|
+
ret: ta.List[PyenvInterpProvider.Installed] = []
|
|
321
|
+
for vn, ep in self._pyenv.version_exes():
|
|
322
|
+
if (i := self._make_installed(vn, ep)) is None:
|
|
323
|
+
log.debug('Invalid pyenv version: %s', vn)
|
|
324
|
+
continue
|
|
325
|
+
ret.append(i)
|
|
326
|
+
return ret
|
|
327
|
+
|
|
328
|
+
#
|
|
329
|
+
|
|
330
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
331
|
+
return [i.version for i in self.installed()]
|
|
332
|
+
|
|
333
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
334
|
+
for i in self.installed():
|
|
335
|
+
if i.version == version:
|
|
336
|
+
return Interp(
|
|
337
|
+
exe=i.exe,
|
|
338
|
+
version=i.version,
|
|
339
|
+
)
|
|
340
|
+
raise KeyError(version)
|
|
341
|
+
|
|
342
|
+
#
|
|
343
|
+
|
|
344
|
+
def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
345
|
+
lst = []
|
|
346
|
+
for vs in self._pyenv.installable_versions():
|
|
347
|
+
if (iv := self.guess_version(vs)) is None:
|
|
348
|
+
continue
|
|
349
|
+
if iv.opts.debug:
|
|
350
|
+
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
|
351
|
+
for d in [False, True]:
|
|
352
|
+
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
|
353
|
+
return lst
|