omdev 0.0.0.dev147__py3-none-any.whl → 0.0.0.dev149__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.
Potentially problematic release.
This version of omdev might be problematic. Click here for more details.
- omdev/.manifests.json +12 -0
- omdev/amalg/amalg.py +1 -0
- omdev/cli/clicli.py +1 -1
- omdev/imgur.py +1 -1
- omdev/interp/cli.py +11 -6
- omdev/interp/inspect.py +5 -6
- omdev/interp/providers.py +6 -6
- omdev/interp/pyenv.py +77 -78
- omdev/interp/resolvers.py +11 -10
- omdev/interp/system.py +8 -8
- omdev/interp/types.py +2 -0
- omdev/manifests/__init__.py +0 -1
- omdev/manifests/__main__.py +11 -0
- omdev/manifests/build.py +2 -76
- omdev/manifests/main.py +84 -0
- omdev/pycharm/cli.py +1 -1
- omdev/pyproject/cli.py +21 -15
- omdev/scripts/interp.py +564 -134
- omdev/scripts/pyproject.py +1404 -975
- omdev/tools/doc.py +1 -1
- omdev/tools/docker.py +1 -1
- omdev/tools/git.py +1 -1
- omdev/tools/json/parsing.py +1 -1
- omdev/tools/notebook.py +1 -1
- omdev/tools/pip.py +1 -1
- omdev/tools/prof.py +1 -1
- omdev/tools/sqlrepl.py +1 -1
- {omdev-0.0.0.dev147.dist-info → omdev-0.0.0.dev149.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev147.dist-info → omdev-0.0.0.dev149.dist-info}/RECORD +33 -31
- {omdev-0.0.0.dev147.dist-info → omdev-0.0.0.dev149.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev147.dist-info → omdev-0.0.0.dev149.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev147.dist-info → omdev-0.0.0.dev149.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev147.dist-info → omdev-0.0.0.dev149.dist-info}/top_level.txt +0 -0
omdev/scripts/pyproject.py
CHANGED
|
@@ -25,6 +25,9 @@ See:
|
|
|
25
25
|
"""
|
|
26
26
|
import abc
|
|
27
27
|
import argparse
|
|
28
|
+
import asyncio
|
|
29
|
+
import asyncio.base_subprocess
|
|
30
|
+
import asyncio.subprocess
|
|
28
31
|
import base64
|
|
29
32
|
import collections
|
|
30
33
|
import collections.abc
|
|
@@ -89,6 +92,9 @@ TomlParseFloat = ta.Callable[[str], ta.Any]
|
|
|
89
92
|
TomlKey = ta.Tuple[str, ...]
|
|
90
93
|
TomlPos = int # ta.TypeAlias
|
|
91
94
|
|
|
95
|
+
# ../../omlish/lite/asyncio/asyncio.py
|
|
96
|
+
AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
|
|
97
|
+
|
|
92
98
|
# ../../omlish/lite/cached.py
|
|
93
99
|
T = ta.TypeVar('T')
|
|
94
100
|
CallableT = ta.TypeVar('CallableT', bound=ta.Callable)
|
|
@@ -1764,11 +1770,74 @@ class WheelFile(zipfile.ZipFile):
|
|
|
1764
1770
|
super().close()
|
|
1765
1771
|
|
|
1766
1772
|
|
|
1773
|
+
########################################
|
|
1774
|
+
# ../../../omlish/lite/asyncio/asyncio.py
|
|
1775
|
+
|
|
1776
|
+
|
|
1777
|
+
##
|
|
1778
|
+
|
|
1779
|
+
|
|
1780
|
+
ASYNCIO_DEFAULT_BUFFER_LIMIT = 2 ** 16
|
|
1781
|
+
|
|
1782
|
+
|
|
1783
|
+
async def asyncio_open_stream_reader(
|
|
1784
|
+
f: ta.IO,
|
|
1785
|
+
loop: ta.Any = None,
|
|
1786
|
+
*,
|
|
1787
|
+
limit: int = ASYNCIO_DEFAULT_BUFFER_LIMIT,
|
|
1788
|
+
) -> asyncio.StreamReader:
|
|
1789
|
+
if loop is None:
|
|
1790
|
+
loop = asyncio.get_running_loop()
|
|
1791
|
+
|
|
1792
|
+
reader = asyncio.StreamReader(limit=limit, loop=loop)
|
|
1793
|
+
await loop.connect_read_pipe(
|
|
1794
|
+
lambda: asyncio.StreamReaderProtocol(reader, loop=loop),
|
|
1795
|
+
f,
|
|
1796
|
+
)
|
|
1797
|
+
|
|
1798
|
+
return reader
|
|
1799
|
+
|
|
1800
|
+
|
|
1801
|
+
async def asyncio_open_stream_writer(
|
|
1802
|
+
f: ta.IO,
|
|
1803
|
+
loop: ta.Any = None,
|
|
1804
|
+
) -> asyncio.StreamWriter:
|
|
1805
|
+
if loop is None:
|
|
1806
|
+
loop = asyncio.get_running_loop()
|
|
1807
|
+
|
|
1808
|
+
writer_transport, writer_protocol = await loop.connect_write_pipe(
|
|
1809
|
+
lambda: asyncio.streams.FlowControlMixin(loop=loop),
|
|
1810
|
+
f,
|
|
1811
|
+
)
|
|
1812
|
+
|
|
1813
|
+
return asyncio.streams.StreamWriter(
|
|
1814
|
+
writer_transport,
|
|
1815
|
+
writer_protocol,
|
|
1816
|
+
None,
|
|
1817
|
+
loop,
|
|
1818
|
+
)
|
|
1819
|
+
|
|
1820
|
+
|
|
1821
|
+
##
|
|
1822
|
+
|
|
1823
|
+
|
|
1824
|
+
def asyncio_maybe_timeout(
|
|
1825
|
+
fut: AwaitableT,
|
|
1826
|
+
timeout: ta.Optional[float] = None,
|
|
1827
|
+
) -> AwaitableT:
|
|
1828
|
+
if timeout is not None:
|
|
1829
|
+
fut = asyncio.wait_for(fut, timeout) # type: ignore
|
|
1830
|
+
return fut
|
|
1831
|
+
|
|
1832
|
+
|
|
1767
1833
|
########################################
|
|
1768
1834
|
# ../../../omlish/lite/cached.py
|
|
1769
1835
|
|
|
1770
1836
|
|
|
1771
|
-
|
|
1837
|
+
##
|
|
1838
|
+
|
|
1839
|
+
|
|
1840
|
+
class _AbstractCachedNullary:
|
|
1772
1841
|
def __init__(self, fn):
|
|
1773
1842
|
super().__init__()
|
|
1774
1843
|
self._fn = fn
|
|
@@ -1776,17 +1845,25 @@ class _cached_nullary: # noqa
|
|
|
1776
1845
|
functools.update_wrapper(self, fn)
|
|
1777
1846
|
|
|
1778
1847
|
def __call__(self, *args, **kwargs): # noqa
|
|
1779
|
-
|
|
1780
|
-
self._value = self._fn()
|
|
1781
|
-
return self._value
|
|
1848
|
+
raise TypeError
|
|
1782
1849
|
|
|
1783
1850
|
def __get__(self, instance, owner): # noqa
|
|
1784
1851
|
bound = instance.__dict__[self._fn.__name__] = self.__class__(self._fn.__get__(instance, owner))
|
|
1785
1852
|
return bound
|
|
1786
1853
|
|
|
1787
1854
|
|
|
1855
|
+
##
|
|
1856
|
+
|
|
1857
|
+
|
|
1858
|
+
class _CachedNullary(_AbstractCachedNullary):
|
|
1859
|
+
def __call__(self, *args, **kwargs): # noqa
|
|
1860
|
+
if self._value is self._missing:
|
|
1861
|
+
self._value = self._fn()
|
|
1862
|
+
return self._value
|
|
1863
|
+
|
|
1864
|
+
|
|
1788
1865
|
def cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
|
|
1789
|
-
return
|
|
1866
|
+
return _CachedNullary(fn)
|
|
1790
1867
|
|
|
1791
1868
|
|
|
1792
1869
|
def static_init(fn: CallableT) -> CallableT:
|
|
@@ -1795,6 +1872,20 @@ def static_init(fn: CallableT) -> CallableT:
|
|
|
1795
1872
|
return fn
|
|
1796
1873
|
|
|
1797
1874
|
|
|
1875
|
+
##
|
|
1876
|
+
|
|
1877
|
+
|
|
1878
|
+
class _AsyncCachedNullary(_AbstractCachedNullary):
|
|
1879
|
+
async def __call__(self, *args, **kwargs):
|
|
1880
|
+
if self._value is self._missing:
|
|
1881
|
+
self._value = await self._fn()
|
|
1882
|
+
return self._value
|
|
1883
|
+
|
|
1884
|
+
|
|
1885
|
+
def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
|
|
1886
|
+
return _AsyncCachedNullary(fn)
|
|
1887
|
+
|
|
1888
|
+
|
|
1798
1889
|
########################################
|
|
1799
1890
|
# ../../../omlish/lite/check.py
|
|
1800
1891
|
|
|
@@ -1834,6 +1925,11 @@ def check_non_empty_str(v: ta.Optional[str]) -> str:
|
|
|
1834
1925
|
return v
|
|
1835
1926
|
|
|
1836
1927
|
|
|
1928
|
+
def check_arg(v: bool, msg: str = 'Illegal argument') -> None:
|
|
1929
|
+
if not v:
|
|
1930
|
+
raise ValueError(msg)
|
|
1931
|
+
|
|
1932
|
+
|
|
1837
1933
|
def check_state(v: bool, msg: str = 'Illegal state') -> None:
|
|
1838
1934
|
if not v:
|
|
1839
1935
|
raise ValueError(msg)
|
|
@@ -1886,7 +1982,7 @@ def check_empty(v: SizedT) -> SizedT:
|
|
|
1886
1982
|
return v
|
|
1887
1983
|
|
|
1888
1984
|
|
|
1889
|
-
def
|
|
1985
|
+
def check_not_empty(v: SizedT) -> SizedT:
|
|
1890
1986
|
if not len(v):
|
|
1891
1987
|
raise ValueError(v)
|
|
1892
1988
|
return v
|
|
@@ -3601,6 +3697,8 @@ class InterpSpecifier:
|
|
|
3601
3697
|
s, o = InterpOpts.parse_suffix(s)
|
|
3602
3698
|
if not any(s.startswith(o) for o in Specifier.OPERATORS):
|
|
3603
3699
|
s = '~=' + s
|
|
3700
|
+
if s.count('.') < 2:
|
|
3701
|
+
s += '.0'
|
|
3604
3702
|
return cls(
|
|
3605
3703
|
specifier=Specifier(s),
|
|
3606
3704
|
opts=o,
|
|
@@ -3825,7 +3923,7 @@ def subprocess_maybe_shell_wrap_exec(*args: str) -> ta.Tuple[str, ...]:
|
|
|
3825
3923
|
return args
|
|
3826
3924
|
|
|
3827
3925
|
|
|
3828
|
-
def
|
|
3926
|
+
def prepare_subprocess_invocation(
|
|
3829
3927
|
*args: str,
|
|
3830
3928
|
env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
|
3831
3929
|
extra_env: ta.Optional[ta.Mapping[str, ta.Any]] = None,
|
|
@@ -3833,9 +3931,9 @@ def _prepare_subprocess_invocation(
|
|
|
3833
3931
|
shell: bool = False,
|
|
3834
3932
|
**kwargs: ta.Any,
|
|
3835
3933
|
) -> ta.Tuple[ta.Tuple[ta.Any, ...], ta.Dict[str, ta.Any]]:
|
|
3836
|
-
log.debug(args)
|
|
3934
|
+
log.debug('prepare_subprocess_invocation: args=%r', args)
|
|
3837
3935
|
if extra_env:
|
|
3838
|
-
log.debug(extra_env)
|
|
3936
|
+
log.debug('prepare_subprocess_invocation: extra_env=%r', extra_env)
|
|
3839
3937
|
|
|
3840
3938
|
if extra_env:
|
|
3841
3939
|
env = {**(env if env is not None else os.environ), **extra_env}
|
|
@@ -3854,14 +3952,46 @@ def _prepare_subprocess_invocation(
|
|
|
3854
3952
|
)
|
|
3855
3953
|
|
|
3856
3954
|
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3955
|
+
##
|
|
3956
|
+
|
|
3957
|
+
|
|
3958
|
+
@contextlib.contextmanager
|
|
3959
|
+
def subprocess_common_context(*args: ta.Any, **kwargs: ta.Any) -> ta.Iterator[None]:
|
|
3960
|
+
start_time = time.time()
|
|
3961
|
+
try:
|
|
3962
|
+
log.debug('subprocess_common_context.try: args=%r', args)
|
|
3963
|
+
yield
|
|
3964
|
+
|
|
3965
|
+
except Exception as exc: # noqa
|
|
3966
|
+
log.debug('subprocess_common_context.except: exc=%r', exc)
|
|
3967
|
+
raise
|
|
3968
|
+
|
|
3969
|
+
finally:
|
|
3970
|
+
end_time = time.time()
|
|
3971
|
+
elapsed_s = end_time - start_time
|
|
3972
|
+
log.debug('subprocess_common_context.finally: elapsed_s=%f args=%r', elapsed_s, args)
|
|
3973
|
+
|
|
3974
|
+
|
|
3975
|
+
##
|
|
3976
|
+
|
|
3977
|
+
|
|
3978
|
+
def subprocess_check_call(
|
|
3979
|
+
*args: str,
|
|
3980
|
+
stdout: ta.Any = sys.stderr,
|
|
3981
|
+
**kwargs: ta.Any,
|
|
3982
|
+
) -> None:
|
|
3983
|
+
args, kwargs = prepare_subprocess_invocation(*args, stdout=stdout, **kwargs)
|
|
3984
|
+
with subprocess_common_context(*args, **kwargs):
|
|
3985
|
+
return subprocess.check_call(args, **kwargs) # type: ignore
|
|
3860
3986
|
|
|
3861
3987
|
|
|
3862
|
-
def subprocess_check_output(
|
|
3863
|
-
|
|
3864
|
-
|
|
3988
|
+
def subprocess_check_output(
|
|
3989
|
+
*args: str,
|
|
3990
|
+
**kwargs: ta.Any,
|
|
3991
|
+
) -> bytes:
|
|
3992
|
+
args, kwargs = prepare_subprocess_invocation(*args, **kwargs)
|
|
3993
|
+
with subprocess_common_context(*args, **kwargs):
|
|
3994
|
+
return subprocess.check_output(args, **kwargs)
|
|
3865
3995
|
|
|
3866
3996
|
|
|
3867
3997
|
def subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
|
|
@@ -3877,16 +4007,31 @@ DEFAULT_SUBPROCESS_TRY_EXCEPTIONS: ta.Tuple[ta.Type[Exception], ...] = (
|
|
|
3877
4007
|
)
|
|
3878
4008
|
|
|
3879
4009
|
|
|
3880
|
-
def
|
|
3881
|
-
|
|
4010
|
+
def _subprocess_try_run(
|
|
4011
|
+
fn: ta.Callable[..., T],
|
|
4012
|
+
*args: ta.Any,
|
|
3882
4013
|
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
|
3883
4014
|
**kwargs: ta.Any,
|
|
3884
|
-
) ->
|
|
4015
|
+
) -> ta.Union[T, Exception]:
|
|
3885
4016
|
try:
|
|
3886
|
-
|
|
4017
|
+
return fn(*args, **kwargs)
|
|
3887
4018
|
except try_exceptions as e: # noqa
|
|
3888
4019
|
if log.isEnabledFor(logging.DEBUG):
|
|
3889
4020
|
log.exception('command failed')
|
|
4021
|
+
return e
|
|
4022
|
+
|
|
4023
|
+
|
|
4024
|
+
def subprocess_try_call(
|
|
4025
|
+
*args: str,
|
|
4026
|
+
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
|
4027
|
+
**kwargs: ta.Any,
|
|
4028
|
+
) -> bool:
|
|
4029
|
+
if isinstance(_subprocess_try_run(
|
|
4030
|
+
subprocess_check_call,
|
|
4031
|
+
*args,
|
|
4032
|
+
try_exceptions=try_exceptions,
|
|
4033
|
+
**kwargs,
|
|
4034
|
+
), Exception):
|
|
3890
4035
|
return False
|
|
3891
4036
|
else:
|
|
3892
4037
|
return True
|
|
@@ -3897,12 +4042,15 @@ def subprocess_try_output(
|
|
|
3897
4042
|
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
|
3898
4043
|
**kwargs: ta.Any,
|
|
3899
4044
|
) -> ta.Optional[bytes]:
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
4045
|
+
if isinstance(ret := _subprocess_try_run(
|
|
4046
|
+
subprocess_check_output,
|
|
4047
|
+
*args,
|
|
4048
|
+
try_exceptions=try_exceptions,
|
|
4049
|
+
**kwargs,
|
|
4050
|
+
), Exception):
|
|
3905
4051
|
return None
|
|
4052
|
+
else:
|
|
4053
|
+
return ret
|
|
3906
4054
|
|
|
3907
4055
|
|
|
3908
4056
|
def subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
|
|
@@ -4329,6 +4477,285 @@ def get_git_status(
|
|
|
4329
4477
|
return parse_git_status(proc.stdout.decode()) # noqa
|
|
4330
4478
|
|
|
4331
4479
|
|
|
4480
|
+
########################################
|
|
4481
|
+
# ../../../omlish/lite/asyncio/subprocesses.py
|
|
4482
|
+
|
|
4483
|
+
|
|
4484
|
+
##
|
|
4485
|
+
|
|
4486
|
+
|
|
4487
|
+
@contextlib.asynccontextmanager
|
|
4488
|
+
async def asyncio_subprocess_popen(
|
|
4489
|
+
*cmd: str,
|
|
4490
|
+
shell: bool = False,
|
|
4491
|
+
timeout: ta.Optional[float] = None,
|
|
4492
|
+
**kwargs: ta.Any,
|
|
4493
|
+
) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
|
|
4494
|
+
fac: ta.Any
|
|
4495
|
+
if shell:
|
|
4496
|
+
fac = functools.partial(
|
|
4497
|
+
asyncio.create_subprocess_shell,
|
|
4498
|
+
check_single(cmd),
|
|
4499
|
+
)
|
|
4500
|
+
else:
|
|
4501
|
+
fac = functools.partial(
|
|
4502
|
+
asyncio.create_subprocess_exec,
|
|
4503
|
+
*cmd,
|
|
4504
|
+
)
|
|
4505
|
+
|
|
4506
|
+
with subprocess_common_context(
|
|
4507
|
+
*cmd,
|
|
4508
|
+
shell=shell,
|
|
4509
|
+
timeout=timeout,
|
|
4510
|
+
**kwargs,
|
|
4511
|
+
):
|
|
4512
|
+
proc: asyncio.subprocess.Process
|
|
4513
|
+
proc = await fac(**kwargs)
|
|
4514
|
+
try:
|
|
4515
|
+
yield proc
|
|
4516
|
+
|
|
4517
|
+
finally:
|
|
4518
|
+
await asyncio_maybe_timeout(proc.wait(), timeout)
|
|
4519
|
+
|
|
4520
|
+
|
|
4521
|
+
##
|
|
4522
|
+
|
|
4523
|
+
|
|
4524
|
+
class AsyncioProcessCommunicator:
|
|
4525
|
+
def __init__(
|
|
4526
|
+
self,
|
|
4527
|
+
proc: asyncio.subprocess.Process,
|
|
4528
|
+
loop: ta.Optional[ta.Any] = None,
|
|
4529
|
+
) -> None:
|
|
4530
|
+
super().__init__()
|
|
4531
|
+
|
|
4532
|
+
if loop is None:
|
|
4533
|
+
loop = asyncio.get_running_loop()
|
|
4534
|
+
|
|
4535
|
+
self._proc = proc
|
|
4536
|
+
self._loop = loop
|
|
4537
|
+
|
|
4538
|
+
self._transport: asyncio.base_subprocess.BaseSubprocessTransport = check_isinstance(
|
|
4539
|
+
proc._transport, # type: ignore # noqa
|
|
4540
|
+
asyncio.base_subprocess.BaseSubprocessTransport,
|
|
4541
|
+
)
|
|
4542
|
+
|
|
4543
|
+
@property
|
|
4544
|
+
def _debug(self) -> bool:
|
|
4545
|
+
return self._loop.get_debug()
|
|
4546
|
+
|
|
4547
|
+
async def _feed_stdin(self, input: bytes) -> None: # noqa
|
|
4548
|
+
stdin = check_not_none(self._proc.stdin)
|
|
4549
|
+
try:
|
|
4550
|
+
if input is not None:
|
|
4551
|
+
stdin.write(input)
|
|
4552
|
+
if self._debug:
|
|
4553
|
+
log.debug('%r communicate: feed stdin (%s bytes)', self, len(input))
|
|
4554
|
+
|
|
4555
|
+
await stdin.drain()
|
|
4556
|
+
|
|
4557
|
+
except (BrokenPipeError, ConnectionResetError) as exc:
|
|
4558
|
+
# communicate() ignores BrokenPipeError and ConnectionResetError. write() and drain() can raise these
|
|
4559
|
+
# exceptions.
|
|
4560
|
+
if self._debug:
|
|
4561
|
+
log.debug('%r communicate: stdin got %r', self, exc)
|
|
4562
|
+
|
|
4563
|
+
if self._debug:
|
|
4564
|
+
log.debug('%r communicate: close stdin', self)
|
|
4565
|
+
|
|
4566
|
+
stdin.close()
|
|
4567
|
+
|
|
4568
|
+
async def _noop(self) -> None:
|
|
4569
|
+
return None
|
|
4570
|
+
|
|
4571
|
+
async def _read_stream(self, fd: int) -> bytes:
|
|
4572
|
+
transport: ta.Any = check_not_none(self._transport.get_pipe_transport(fd))
|
|
4573
|
+
|
|
4574
|
+
if fd == 2:
|
|
4575
|
+
stream = check_not_none(self._proc.stderr)
|
|
4576
|
+
else:
|
|
4577
|
+
check_equal(fd, 1)
|
|
4578
|
+
stream = check_not_none(self._proc.stdout)
|
|
4579
|
+
|
|
4580
|
+
if self._debug:
|
|
4581
|
+
name = 'stdout' if fd == 1 else 'stderr'
|
|
4582
|
+
log.debug('%r communicate: read %s', self, name)
|
|
4583
|
+
|
|
4584
|
+
output = await stream.read()
|
|
4585
|
+
|
|
4586
|
+
if self._debug:
|
|
4587
|
+
name = 'stdout' if fd == 1 else 'stderr'
|
|
4588
|
+
log.debug('%r communicate: close %s', self, name)
|
|
4589
|
+
|
|
4590
|
+
transport.close()
|
|
4591
|
+
|
|
4592
|
+
return output
|
|
4593
|
+
|
|
4594
|
+
class Communication(ta.NamedTuple):
|
|
4595
|
+
stdout: ta.Optional[bytes]
|
|
4596
|
+
stderr: ta.Optional[bytes]
|
|
4597
|
+
|
|
4598
|
+
async def _communicate(
|
|
4599
|
+
self,
|
|
4600
|
+
input: ta.Any = None, # noqa
|
|
4601
|
+
) -> Communication:
|
|
4602
|
+
stdin_fut: ta.Any
|
|
4603
|
+
if self._proc.stdin is not None:
|
|
4604
|
+
stdin_fut = self._feed_stdin(input)
|
|
4605
|
+
else:
|
|
4606
|
+
stdin_fut = self._noop()
|
|
4607
|
+
|
|
4608
|
+
stdout_fut: ta.Any
|
|
4609
|
+
if self._proc.stdout is not None:
|
|
4610
|
+
stdout_fut = self._read_stream(1)
|
|
4611
|
+
else:
|
|
4612
|
+
stdout_fut = self._noop()
|
|
4613
|
+
|
|
4614
|
+
stderr_fut: ta.Any
|
|
4615
|
+
if self._proc.stderr is not None:
|
|
4616
|
+
stderr_fut = self._read_stream(2)
|
|
4617
|
+
else:
|
|
4618
|
+
stderr_fut = self._noop()
|
|
4619
|
+
|
|
4620
|
+
stdin_res, stdout_res, stderr_res = await asyncio.gather(stdin_fut, stdout_fut, stderr_fut)
|
|
4621
|
+
|
|
4622
|
+
await self._proc.wait()
|
|
4623
|
+
|
|
4624
|
+
return AsyncioProcessCommunicator.Communication(stdout_res, stderr_res)
|
|
4625
|
+
|
|
4626
|
+
async def communicate(
|
|
4627
|
+
self,
|
|
4628
|
+
input: ta.Any = None, # noqa
|
|
4629
|
+
timeout: ta.Optional[float] = None,
|
|
4630
|
+
) -> Communication:
|
|
4631
|
+
return await asyncio_maybe_timeout(self._communicate(input), timeout)
|
|
4632
|
+
|
|
4633
|
+
|
|
4634
|
+
async def asyncio_subprocess_communicate(
|
|
4635
|
+
proc: asyncio.subprocess.Process,
|
|
4636
|
+
input: ta.Any = None, # noqa
|
|
4637
|
+
timeout: ta.Optional[float] = None,
|
|
4638
|
+
) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
|
|
4639
|
+
return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
|
|
4640
|
+
|
|
4641
|
+
|
|
4642
|
+
##
|
|
4643
|
+
|
|
4644
|
+
|
|
4645
|
+
async def _asyncio_subprocess_check_run(
|
|
4646
|
+
*args: str,
|
|
4647
|
+
input: ta.Any = None, # noqa
|
|
4648
|
+
timeout: ta.Optional[float] = None,
|
|
4649
|
+
**kwargs: ta.Any,
|
|
4650
|
+
) -> ta.Tuple[ta.Optional[bytes], ta.Optional[bytes]]:
|
|
4651
|
+
args, kwargs = prepare_subprocess_invocation(*args, **kwargs)
|
|
4652
|
+
|
|
4653
|
+
proc: asyncio.subprocess.Process
|
|
4654
|
+
async with asyncio_subprocess_popen(*args, **kwargs) as proc:
|
|
4655
|
+
stdout, stderr = await asyncio_subprocess_communicate(proc, input, timeout)
|
|
4656
|
+
|
|
4657
|
+
if proc.returncode:
|
|
4658
|
+
raise subprocess.CalledProcessError(
|
|
4659
|
+
proc.returncode,
|
|
4660
|
+
args,
|
|
4661
|
+
output=stdout,
|
|
4662
|
+
stderr=stderr,
|
|
4663
|
+
)
|
|
4664
|
+
|
|
4665
|
+
return stdout, stderr
|
|
4666
|
+
|
|
4667
|
+
|
|
4668
|
+
async def asyncio_subprocess_check_call(
|
|
4669
|
+
*args: str,
|
|
4670
|
+
stdout: ta.Any = sys.stderr,
|
|
4671
|
+
input: ta.Any = None, # noqa
|
|
4672
|
+
timeout: ta.Optional[float] = None,
|
|
4673
|
+
**kwargs: ta.Any,
|
|
4674
|
+
) -> None:
|
|
4675
|
+
_, _ = await _asyncio_subprocess_check_run(
|
|
4676
|
+
*args,
|
|
4677
|
+
stdout=stdout,
|
|
4678
|
+
input=input,
|
|
4679
|
+
timeout=timeout,
|
|
4680
|
+
**kwargs,
|
|
4681
|
+
)
|
|
4682
|
+
|
|
4683
|
+
|
|
4684
|
+
async def asyncio_subprocess_check_output(
|
|
4685
|
+
*args: str,
|
|
4686
|
+
input: ta.Any = None, # noqa
|
|
4687
|
+
timeout: ta.Optional[float] = None,
|
|
4688
|
+
**kwargs: ta.Any,
|
|
4689
|
+
) -> bytes:
|
|
4690
|
+
stdout, stderr = await _asyncio_subprocess_check_run(
|
|
4691
|
+
*args,
|
|
4692
|
+
stdout=asyncio.subprocess.PIPE,
|
|
4693
|
+
input=input,
|
|
4694
|
+
timeout=timeout,
|
|
4695
|
+
**kwargs,
|
|
4696
|
+
)
|
|
4697
|
+
|
|
4698
|
+
return check_not_none(stdout)
|
|
4699
|
+
|
|
4700
|
+
|
|
4701
|
+
async def asyncio_subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
|
|
4702
|
+
return (await asyncio_subprocess_check_output(*args, **kwargs)).decode().strip()
|
|
4703
|
+
|
|
4704
|
+
|
|
4705
|
+
##
|
|
4706
|
+
|
|
4707
|
+
|
|
4708
|
+
async def _asyncio_subprocess_try_run(
|
|
4709
|
+
fn: ta.Callable[..., ta.Awaitable[T]],
|
|
4710
|
+
*args: ta.Any,
|
|
4711
|
+
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
|
4712
|
+
**kwargs: ta.Any,
|
|
4713
|
+
) -> ta.Union[T, Exception]:
|
|
4714
|
+
try:
|
|
4715
|
+
return await fn(*args, **kwargs)
|
|
4716
|
+
except try_exceptions as e: # noqa
|
|
4717
|
+
if log.isEnabledFor(logging.DEBUG):
|
|
4718
|
+
log.exception('command failed')
|
|
4719
|
+
return e
|
|
4720
|
+
|
|
4721
|
+
|
|
4722
|
+
async def asyncio_subprocess_try_call(
|
|
4723
|
+
*args: str,
|
|
4724
|
+
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
|
4725
|
+
**kwargs: ta.Any,
|
|
4726
|
+
) -> bool:
|
|
4727
|
+
if isinstance(await _asyncio_subprocess_try_run(
|
|
4728
|
+
asyncio_subprocess_check_call,
|
|
4729
|
+
*args,
|
|
4730
|
+
try_exceptions=try_exceptions,
|
|
4731
|
+
**kwargs,
|
|
4732
|
+
), Exception):
|
|
4733
|
+
return False
|
|
4734
|
+
else:
|
|
4735
|
+
return True
|
|
4736
|
+
|
|
4737
|
+
|
|
4738
|
+
async def asyncio_subprocess_try_output(
|
|
4739
|
+
*args: str,
|
|
4740
|
+
try_exceptions: ta.Tuple[ta.Type[Exception], ...] = DEFAULT_SUBPROCESS_TRY_EXCEPTIONS,
|
|
4741
|
+
**kwargs: ta.Any,
|
|
4742
|
+
) -> ta.Optional[bytes]:
|
|
4743
|
+
if isinstance(ret := await _asyncio_subprocess_try_run(
|
|
4744
|
+
asyncio_subprocess_check_output,
|
|
4745
|
+
*args,
|
|
4746
|
+
try_exceptions=try_exceptions,
|
|
4747
|
+
**kwargs,
|
|
4748
|
+
), Exception):
|
|
4749
|
+
return None
|
|
4750
|
+
else:
|
|
4751
|
+
return ret
|
|
4752
|
+
|
|
4753
|
+
|
|
4754
|
+
async def asyncio_subprocess_try_output_str(*args: str, **kwargs: ta.Any) -> ta.Optional[str]:
|
|
4755
|
+
out = await asyncio_subprocess_try_output(*args, **kwargs)
|
|
4756
|
+
return out.decode().strip() if out is not None else None
|
|
4757
|
+
|
|
4758
|
+
|
|
4332
4759
|
########################################
|
|
4333
4760
|
# ../../interp/inspect.py
|
|
4334
4761
|
|
|
@@ -4363,7 +4790,6 @@ class InterpInspection:
|
|
|
4363
4790
|
|
|
4364
4791
|
|
|
4365
4792
|
class InterpInspector:
|
|
4366
|
-
|
|
4367
4793
|
def __init__(self) -> None:
|
|
4368
4794
|
super().__init__()
|
|
4369
4795
|
|
|
@@ -4403,17 +4829,17 @@ class InterpInspector:
|
|
|
4403
4829
|
def running(cls) -> 'InterpInspection':
|
|
4404
4830
|
return cls._build_inspection(sys.executable, eval(cls._INSPECTION_CODE)) # noqa
|
|
4405
4831
|
|
|
4406
|
-
def _inspect(self, exe: str) -> InterpInspection:
|
|
4407
|
-
output =
|
|
4832
|
+
async def _inspect(self, exe: str) -> InterpInspection:
|
|
4833
|
+
output = await asyncio_subprocess_check_output(exe, '-c', f'print({self._INSPECTION_CODE})', quiet=True)
|
|
4408
4834
|
return self._build_inspection(exe, output.decode())
|
|
4409
4835
|
|
|
4410
|
-
def inspect(self, exe: str) -> ta.Optional[InterpInspection]:
|
|
4836
|
+
async def inspect(self, exe: str) -> ta.Optional[InterpInspection]:
|
|
4411
4837
|
try:
|
|
4412
4838
|
return self._cache[exe]
|
|
4413
4839
|
except KeyError:
|
|
4414
4840
|
ret: ta.Optional[InterpInspection]
|
|
4415
4841
|
try:
|
|
4416
|
-
ret = self._inspect(exe)
|
|
4842
|
+
ret = await self._inspect(exe)
|
|
4417
4843
|
except Exception as e: # noqa
|
|
4418
4844
|
if log.isEnabledFor(logging.DEBUG):
|
|
4419
4845
|
log.exception('Failed to inspect interp: %s', exe)
|
|
@@ -4426,95 +4852,34 @@ INTERP_INSPECTOR = InterpInspector()
|
|
|
4426
4852
|
|
|
4427
4853
|
|
|
4428
4854
|
########################################
|
|
4429
|
-
# ../../
|
|
4855
|
+
# ../../revisions.py
|
|
4430
4856
|
"""
|
|
4431
4857
|
TODO:
|
|
4432
|
-
-
|
|
4433
|
-
-
|
|
4434
|
-
- deadsnakes?
|
|
4435
|
-
- uv
|
|
4436
|
-
- loose versions
|
|
4858
|
+
- omlish-lite, move to pyproject/
|
|
4859
|
+
- vendor-lite wheel.wheelfile
|
|
4437
4860
|
"""
|
|
4438
4861
|
|
|
4439
4862
|
|
|
4440
4863
|
##
|
|
4441
4864
|
|
|
4442
4865
|
|
|
4443
|
-
class
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
|
|
4453
|
-
|
|
4454
|
-
@abc.abstractmethod
|
|
4455
|
-
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
4456
|
-
raise NotImplementedError
|
|
4457
|
-
|
|
4458
|
-
@abc.abstractmethod
|
|
4459
|
-
def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
4460
|
-
raise NotImplementedError
|
|
4866
|
+
class GitRevisionAdder:
|
|
4867
|
+
def __init__(
|
|
4868
|
+
self,
|
|
4869
|
+
revision: ta.Optional[str] = None,
|
|
4870
|
+
output_suffix: ta.Optional[str] = None,
|
|
4871
|
+
) -> None:
|
|
4872
|
+
super().__init__()
|
|
4873
|
+
self._given_revision = revision
|
|
4874
|
+
self._output_suffix = output_suffix
|
|
4461
4875
|
|
|
4462
|
-
|
|
4463
|
-
|
|
4876
|
+
@cached_nullary
|
|
4877
|
+
def revision(self) -> str:
|
|
4878
|
+
if self._given_revision is not None:
|
|
4879
|
+
return self._given_revision
|
|
4880
|
+
return check_non_empty_str(get_git_revision())
|
|
4464
4881
|
|
|
4465
|
-
|
|
4466
|
-
raise TypeError
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
##
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
class RunningInterpProvider(InterpProvider):
|
|
4473
|
-
@cached_nullary
|
|
4474
|
-
def version(self) -> InterpVersion:
|
|
4475
|
-
return InterpInspector.running().iv
|
|
4476
|
-
|
|
4477
|
-
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
4478
|
-
return [self.version()]
|
|
4479
|
-
|
|
4480
|
-
def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
4481
|
-
if version != self.version():
|
|
4482
|
-
raise KeyError(version)
|
|
4483
|
-
return Interp(
|
|
4484
|
-
exe=sys.executable,
|
|
4485
|
-
version=self.version(),
|
|
4486
|
-
)
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
########################################
|
|
4490
|
-
# ../../revisions.py
|
|
4491
|
-
"""
|
|
4492
|
-
TODO:
|
|
4493
|
-
- omlish-lite, move to pyproject/
|
|
4494
|
-
- vendor-lite wheel.wheelfile
|
|
4495
|
-
"""
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
##
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
class GitRevisionAdder:
|
|
4502
|
-
def __init__(
|
|
4503
|
-
self,
|
|
4504
|
-
revision: ta.Optional[str] = None,
|
|
4505
|
-
output_suffix: ta.Optional[str] = None,
|
|
4506
|
-
) -> None:
|
|
4507
|
-
super().__init__()
|
|
4508
|
-
self._given_revision = revision
|
|
4509
|
-
self._output_suffix = output_suffix
|
|
4510
|
-
|
|
4511
|
-
@cached_nullary
|
|
4512
|
-
def revision(self) -> str:
|
|
4513
|
-
if self._given_revision is not None:
|
|
4514
|
-
return self._given_revision
|
|
4515
|
-
return check_non_empty_str(get_git_revision())
|
|
4516
|
-
|
|
4517
|
-
REVISION_ATTR = '__revision__'
|
|
4882
|
+
REVISION_ATTR = '__revision__'
|
|
4518
4883
|
|
|
4519
4884
|
def add_to_contents(self, dct: ta.Dict[str, bytes]) -> bool:
|
|
4520
4885
|
changed = False
|
|
@@ -4607,1120 +4972,1179 @@ class GitRevisionAdder:
|
|
|
4607
4972
|
|
|
4608
4973
|
|
|
4609
4974
|
########################################
|
|
4610
|
-
# ../../interp/
|
|
4975
|
+
# ../../interp/providers.py
|
|
4611
4976
|
"""
|
|
4612
4977
|
TODO:
|
|
4613
|
-
-
|
|
4614
|
-
-
|
|
4615
|
-
-
|
|
4616
|
-
-
|
|
4617
|
-
|
|
4618
|
-
- *or* python-build directly just into the versions dir?
|
|
4619
|
-
- optionally install / upgrade pyenv itself
|
|
4620
|
-
- new vers dont need these custom mac opts, only run on old vers
|
|
4978
|
+
- backends
|
|
4979
|
+
- local builds
|
|
4980
|
+
- deadsnakes?
|
|
4981
|
+
- uv
|
|
4982
|
+
- loose versions
|
|
4621
4983
|
"""
|
|
4622
4984
|
|
|
4623
4985
|
|
|
4624
4986
|
##
|
|
4625
4987
|
|
|
4626
4988
|
|
|
4627
|
-
class
|
|
4989
|
+
class InterpProvider(abc.ABC):
|
|
4990
|
+
name: ta.ClassVar[str]
|
|
4628
4991
|
|
|
4629
|
-
def
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4992
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
|
4993
|
+
super().__init_subclass__(**kwargs)
|
|
4994
|
+
if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
|
|
4995
|
+
sfx = 'InterpProvider'
|
|
4996
|
+
if not cls.__name__.endswith(sfx):
|
|
4997
|
+
raise NameError(cls)
|
|
4998
|
+
setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
|
|
4636
4999
|
|
|
4637
|
-
|
|
5000
|
+
@abc.abstractmethod
|
|
5001
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Awaitable[ta.Sequence[InterpVersion]]:
|
|
5002
|
+
raise NotImplementedError
|
|
4638
5003
|
|
|
4639
|
-
|
|
5004
|
+
@abc.abstractmethod
|
|
5005
|
+
def get_installed_version(self, version: InterpVersion) -> ta.Awaitable[Interp]:
|
|
5006
|
+
raise NotImplementedError
|
|
4640
5007
|
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
if self._root_kw is not None:
|
|
4644
|
-
return self._root_kw
|
|
5008
|
+
async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
5009
|
+
return []
|
|
4645
5010
|
|
|
4646
|
-
|
|
4647
|
-
|
|
5011
|
+
async def install_version(self, version: InterpVersion) -> Interp:
|
|
5012
|
+
raise TypeError
|
|
4648
5013
|
|
|
4649
|
-
d = os.path.expanduser('~/.pyenv')
|
|
4650
|
-
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
|
4651
|
-
return d
|
|
4652
5014
|
|
|
4653
|
-
|
|
5015
|
+
##
|
|
4654
5016
|
|
|
5017
|
+
|
|
5018
|
+
class RunningInterpProvider(InterpProvider):
|
|
4655
5019
|
@cached_nullary
|
|
4656
|
-
def
|
|
4657
|
-
return
|
|
5020
|
+
def version(self) -> InterpVersion:
|
|
5021
|
+
return InterpInspector.running().iv
|
|
4658
5022
|
|
|
4659
|
-
def
|
|
4660
|
-
|
|
4661
|
-
return []
|
|
4662
|
-
ret = []
|
|
4663
|
-
vp = os.path.join(root, 'versions')
|
|
4664
|
-
if os.path.isdir(vp):
|
|
4665
|
-
for dn in os.listdir(vp):
|
|
4666
|
-
ep = os.path.join(vp, dn, 'bin', 'python')
|
|
4667
|
-
if not os.path.isfile(ep):
|
|
4668
|
-
continue
|
|
4669
|
-
ret.append((dn, ep))
|
|
4670
|
-
return ret
|
|
5023
|
+
async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
5024
|
+
return [self.version()]
|
|
4671
5025
|
|
|
4672
|
-
def
|
|
4673
|
-
if self.
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
continue
|
|
4680
|
-
l = l.strip()
|
|
4681
|
-
if not l:
|
|
4682
|
-
continue
|
|
4683
|
-
ret.append(l)
|
|
4684
|
-
return ret
|
|
5026
|
+
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
5027
|
+
if version != self.version():
|
|
5028
|
+
raise KeyError(version)
|
|
5029
|
+
return Interp(
|
|
5030
|
+
exe=sys.executable,
|
|
5031
|
+
version=self.version(),
|
|
5032
|
+
)
|
|
4685
5033
|
|
|
4686
|
-
def update(self) -> bool:
|
|
4687
|
-
if (root := self.root()) is None:
|
|
4688
|
-
return False
|
|
4689
|
-
if not os.path.isdir(os.path.join(root, '.git')):
|
|
4690
|
-
return False
|
|
4691
|
-
subprocess_check_call('git', 'pull', cwd=root)
|
|
4692
|
-
return True
|
|
4693
5034
|
|
|
5035
|
+
########################################
|
|
5036
|
+
# ../pkg.py
|
|
5037
|
+
"""
|
|
5038
|
+
TODO:
|
|
5039
|
+
- ext scanning
|
|
5040
|
+
- __revision__
|
|
5041
|
+
- entry_points
|
|
4694
5042
|
|
|
4695
|
-
|
|
5043
|
+
** NOTE **
|
|
5044
|
+
setuptools now (2024/09/02) has experimental support for extensions in pure pyproject.toml - but we still want a
|
|
5045
|
+
separate '-cext' package
|
|
5046
|
+
https://setuptools.pypa.io/en/latest/userguide/ext_modules.html
|
|
5047
|
+
https://github.com/pypa/setuptools/commit/1a9d87308dc0d8aabeaae0dce989b35dfb7699f0#diff-61d113525e9cc93565799a4bb8b34a68e2945b8a3f7d90c81380614a4ea39542R7-R8
|
|
4696
5048
|
|
|
5049
|
+
--
|
|
4697
5050
|
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
opts: ta.Sequence[str] = ()
|
|
4701
|
-
conf_opts: ta.Sequence[str] = ()
|
|
4702
|
-
cflags: ta.Sequence[str] = ()
|
|
4703
|
-
ldflags: ta.Sequence[str] = ()
|
|
4704
|
-
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
|
5051
|
+
https://setuptools.pypa.io/en/latest/references/keywords.html
|
|
5052
|
+
https://packaging.python.org/en/latest/specifications/pyproject-toml
|
|
4705
5053
|
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
|
|
4709
|
-
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
|
4710
|
-
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
|
4711
|
-
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
|
4712
|
-
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
|
4713
|
-
)
|
|
5054
|
+
How to build a C extension in keeping with PEP 517, i.e. with pyproject.toml instead of setup.py?
|
|
5055
|
+
https://stackoverflow.com/a/66479252
|
|
4714
5056
|
|
|
5057
|
+
https://github.com/pypa/sampleproject/blob/db5806e0a3204034c51b1c00dde7d5eb3fa2532e/setup.py
|
|
4715
5058
|
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
'-v',
|
|
4721
|
-
'-k',
|
|
4722
|
-
],
|
|
4723
|
-
conf_opts=[
|
|
4724
|
-
# FIXME: breaks on mac for older py's
|
|
4725
|
-
'--enable-loadable-sqlite-extensions',
|
|
5059
|
+
https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support
|
|
5060
|
+
vcs+protocol://repo_url/#egg=pkg&subdirectory=pkg_dir
|
|
5061
|
+
'git+https://github.com/wrmsr/omlish@master#subdirectory=.pip/omlish'
|
|
5062
|
+
""" # noqa
|
|
4726
5063
|
|
|
4727
|
-
# '--enable-shared',
|
|
4728
5064
|
|
|
4729
|
-
|
|
4730
|
-
'--with-lto',
|
|
5065
|
+
#
|
|
4731
5066
|
|
|
4732
|
-
# '--enable-profiling', # ?
|
|
4733
5067
|
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
5068
|
+
class BasePyprojectPackageGenerator(abc.ABC):
|
|
5069
|
+
def __init__(
|
|
5070
|
+
self,
|
|
5071
|
+
dir_name: str,
|
|
5072
|
+
pkgs_root: str,
|
|
5073
|
+
*,
|
|
5074
|
+
pkg_suffix: str = '',
|
|
5075
|
+
) -> None:
|
|
5076
|
+
super().__init__()
|
|
5077
|
+
self._dir_name = dir_name
|
|
5078
|
+
self._pkgs_root = pkgs_root
|
|
5079
|
+
self._pkg_suffix = pkg_suffix
|
|
4741
5080
|
|
|
4742
|
-
|
|
5081
|
+
#
|
|
4743
5082
|
|
|
4744
|
-
|
|
5083
|
+
@cached_nullary
|
|
5084
|
+
def about(self) -> types.ModuleType:
|
|
5085
|
+
return importlib.import_module(f'{self._dir_name}.__about__')
|
|
4745
5086
|
|
|
5087
|
+
#
|
|
4746
5088
|
|
|
4747
|
-
|
|
5089
|
+
@cached_nullary
|
|
5090
|
+
def _pkg_dir(self) -> str:
|
|
5091
|
+
pkg_dir: str = os.path.join(self._pkgs_root, self._dir_name + self._pkg_suffix)
|
|
5092
|
+
if os.path.isdir(pkg_dir):
|
|
5093
|
+
shutil.rmtree(pkg_dir)
|
|
5094
|
+
os.makedirs(pkg_dir)
|
|
5095
|
+
return pkg_dir
|
|
4748
5096
|
|
|
5097
|
+
#
|
|
4749
5098
|
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
5099
|
+
_GIT_IGNORE: ta.Sequence[str] = [
|
|
5100
|
+
'/*.egg-info/',
|
|
5101
|
+
'/dist',
|
|
5102
|
+
]
|
|
4754
5103
|
|
|
5104
|
+
def _write_git_ignore(self) -> None:
|
|
5105
|
+
with open(os.path.join(self._pkg_dir(), '.gitignore'), 'w') as f:
|
|
5106
|
+
f.write('\n'.join(self._GIT_IGNORE))
|
|
4755
5107
|
|
|
4756
|
-
|
|
4757
|
-
def opts(self) -> PyenvInstallOpts:
|
|
4758
|
-
return PyenvInstallOpts()
|
|
5108
|
+
#
|
|
4759
5109
|
|
|
5110
|
+
def _symlink_source_dir(self) -> None:
|
|
5111
|
+
os.symlink(
|
|
5112
|
+
os.path.relpath(self._dir_name, self._pkg_dir()),
|
|
5113
|
+
os.path.join(self._pkg_dir(), self._dir_name),
|
|
5114
|
+
)
|
|
4760
5115
|
|
|
4761
|
-
|
|
5116
|
+
#
|
|
4762
5117
|
|
|
4763
5118
|
@cached_nullary
|
|
4764
|
-
def
|
|
4765
|
-
return
|
|
5119
|
+
def project_cls(self) -> type:
|
|
5120
|
+
return self.about().Project
|
|
4766
5121
|
|
|
4767
5122
|
@cached_nullary
|
|
4768
|
-
def
|
|
4769
|
-
return
|
|
5123
|
+
def setuptools_cls(self) -> type:
|
|
5124
|
+
return self.about().Setuptools
|
|
4770
5125
|
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
5126
|
+
@staticmethod
|
|
5127
|
+
def _build_cls_dct(cls: type) -> ta.Dict[str, ta.Any]: # noqa
|
|
5128
|
+
dct = {}
|
|
5129
|
+
for b in reversed(cls.__mro__):
|
|
5130
|
+
for k, v in b.__dict__.items():
|
|
5131
|
+
if k.startswith('_'):
|
|
5132
|
+
continue
|
|
5133
|
+
dct[k] = v
|
|
5134
|
+
return dct
|
|
4777
5135
|
|
|
4778
|
-
@
|
|
4779
|
-
def
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
5136
|
+
@staticmethod
|
|
5137
|
+
def _move_dict_key(
|
|
5138
|
+
sd: ta.Dict[str, ta.Any],
|
|
5139
|
+
sk: str,
|
|
5140
|
+
dd: ta.Dict[str, ta.Any],
|
|
5141
|
+
dk: str,
|
|
5142
|
+
) -> None:
|
|
5143
|
+
if sk in sd:
|
|
5144
|
+
dd[dk] = sd.pop(sk)
|
|
5145
|
+
|
|
5146
|
+
@dc.dataclass(frozen=True)
|
|
5147
|
+
class Specs:
|
|
5148
|
+
pyproject: ta.Dict[str, ta.Any]
|
|
5149
|
+
setuptools: ta.Dict[str, ta.Any]
|
|
5150
|
+
|
|
5151
|
+
def build_specs(self) -> Specs:
|
|
5152
|
+
return self.Specs(
|
|
5153
|
+
self._build_cls_dct(self.project_cls()),
|
|
5154
|
+
self._build_cls_dct(self.setuptools_cls()),
|
|
4789
5155
|
)
|
|
4790
5156
|
|
|
5157
|
+
#
|
|
5158
|
+
|
|
5159
|
+
class _PkgData(ta.NamedTuple):
|
|
5160
|
+
inc: ta.List[str]
|
|
5161
|
+
exc: ta.List[str]
|
|
5162
|
+
|
|
4791
5163
|
@cached_nullary
|
|
4792
|
-
def
|
|
4793
|
-
|
|
4794
|
-
|
|
5164
|
+
def _collect_pkg_data(self) -> _PkgData:
|
|
5165
|
+
inc: ta.List[str] = []
|
|
5166
|
+
exc: ta.List[str] = []
|
|
4795
5167
|
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
5168
|
+
for p, ds, fs in os.walk(self._dir_name): # noqa
|
|
5169
|
+
for f in fs:
|
|
5170
|
+
if f != '.pkgdata':
|
|
5171
|
+
continue
|
|
5172
|
+
rp = os.path.relpath(p, self._dir_name)
|
|
5173
|
+
log.info('Found pkgdata %s for pkg %s', rp, self._dir_name)
|
|
5174
|
+
with open(os.path.join(p, f)) as fo:
|
|
5175
|
+
src = fo.read()
|
|
5176
|
+
for l in src.splitlines():
|
|
5177
|
+
if not (l := l.strip()):
|
|
5178
|
+
continue
|
|
5179
|
+
if l.startswith('!'):
|
|
5180
|
+
exc.append(os.path.join(rp, l[1:]))
|
|
5181
|
+
else:
|
|
5182
|
+
inc.append(os.path.join(rp, l))
|
|
4799
5183
|
|
|
4800
|
-
return
|
|
4801
|
-
f"--with-tcltk-includes='-I{tcl_tk_prefix}/include'",
|
|
4802
|
-
f"--with-tcltk-libs='-L{tcl_tk_prefix}/lib -ltcl{tcl_tk_ver} -ltk{tcl_tk_ver}'",
|
|
4803
|
-
])
|
|
5184
|
+
return self._PkgData(inc, exc)
|
|
4804
5185
|
|
|
4805
|
-
#
|
|
4806
|
-
# def brew_ssl_opts(self) -> PyenvInstallOpts:
|
|
4807
|
-
# pkg_config_path = subprocess_check_output_str('brew', '--prefix', 'openssl')
|
|
4808
|
-
# if 'PKG_CONFIG_PATH' in os.environ:
|
|
4809
|
-
# pkg_config_path += ':' + os.environ['PKG_CONFIG_PATH']
|
|
4810
|
-
# return PyenvInstallOpts(env={'PKG_CONFIG_PATH': pkg_config_path})
|
|
5186
|
+
#
|
|
4811
5187
|
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
self.brew_deps_opts(),
|
|
4816
|
-
self.brew_tcl_opts(),
|
|
4817
|
-
# self.brew_ssl_opts(),
|
|
4818
|
-
)
|
|
5188
|
+
@abc.abstractmethod
|
|
5189
|
+
def _write_file_contents(self) -> None:
|
|
5190
|
+
raise NotImplementedError
|
|
4819
5191
|
|
|
5192
|
+
#
|
|
4820
5193
|
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
5194
|
+
_STANDARD_FILES: ta.Sequence[str] = [
|
|
5195
|
+
'LICENSE',
|
|
5196
|
+
'README.rst',
|
|
5197
|
+
]
|
|
4825
5198
|
|
|
5199
|
+
def _symlink_standard_files(self) -> None:
|
|
5200
|
+
for fn in self._STANDARD_FILES:
|
|
5201
|
+
if os.path.exists(fn):
|
|
5202
|
+
os.symlink(os.path.relpath(fn, self._pkg_dir()), os.path.join(self._pkg_dir(), fn))
|
|
4826
5203
|
|
|
4827
|
-
|
|
5204
|
+
#
|
|
4828
5205
|
|
|
5206
|
+
def children(self) -> ta.Sequence['BasePyprojectPackageGenerator']:
|
|
5207
|
+
return []
|
|
4829
5208
|
|
|
4830
|
-
|
|
4831
|
-
"""
|
|
4832
|
-
Messy: can install freethreaded build with a 't' suffixed version str _or_ by THREADED_PYENV_INSTALL_OPTS - need
|
|
4833
|
-
latter to build custom interp with ft, need former to use canned / blessed interps. Muh.
|
|
4834
|
-
"""
|
|
5209
|
+
#
|
|
4835
5210
|
|
|
4836
|
-
def
|
|
5211
|
+
def gen(self) -> str:
|
|
5212
|
+
log.info('Generating pyproject package: %s -> %s (%s)', self._dir_name, self._pkgs_root, self._pkg_suffix)
|
|
5213
|
+
|
|
5214
|
+
self._pkg_dir()
|
|
5215
|
+
self._write_git_ignore()
|
|
5216
|
+
self._symlink_source_dir()
|
|
5217
|
+
self._write_file_contents()
|
|
5218
|
+
self._symlink_standard_files()
|
|
5219
|
+
|
|
5220
|
+
return self._pkg_dir()
|
|
5221
|
+
|
|
5222
|
+
#
|
|
5223
|
+
|
|
5224
|
+
@dc.dataclass(frozen=True)
|
|
5225
|
+
class BuildOpts:
|
|
5226
|
+
add_revision: bool = False
|
|
5227
|
+
test: bool = False
|
|
5228
|
+
|
|
5229
|
+
def build(
|
|
4837
5230
|
self,
|
|
4838
|
-
|
|
4839
|
-
opts:
|
|
4840
|
-
interp_opts: InterpOpts = InterpOpts(),
|
|
4841
|
-
*,
|
|
4842
|
-
install_name: ta.Optional[str] = None,
|
|
4843
|
-
no_default_opts: bool = False,
|
|
4844
|
-
pyenv: Pyenv = Pyenv(),
|
|
5231
|
+
output_dir: ta.Optional[str] = None,
|
|
5232
|
+
opts: BuildOpts = BuildOpts(),
|
|
4845
5233
|
) -> None:
|
|
4846
|
-
|
|
5234
|
+
subprocess_check_call(
|
|
5235
|
+
sys.executable,
|
|
5236
|
+
'-m',
|
|
5237
|
+
'build',
|
|
5238
|
+
cwd=self._pkg_dir(),
|
|
5239
|
+
)
|
|
4847
5240
|
|
|
4848
|
-
|
|
4849
|
-
if opts is None:
|
|
4850
|
-
opts = PyenvInstallOpts()
|
|
4851
|
-
else:
|
|
4852
|
-
lst = [opts if opts is not None else DEFAULT_PYENV_INSTALL_OPTS]
|
|
4853
|
-
if interp_opts.debug:
|
|
4854
|
-
lst.append(DEBUG_PYENV_INSTALL_OPTS)
|
|
4855
|
-
if interp_opts.threaded:
|
|
4856
|
-
lst.append(THREADED_PYENV_INSTALL_OPTS)
|
|
4857
|
-
lst.append(PLATFORM_PYENV_INSTALL_OPTS[sys.platform].opts())
|
|
4858
|
-
opts = PyenvInstallOpts().merge(*lst)
|
|
5241
|
+
dist_dir = os.path.join(self._pkg_dir(), 'dist')
|
|
4859
5242
|
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
self._interp_opts = interp_opts
|
|
4863
|
-
self._given_install_name = install_name
|
|
5243
|
+
if opts.add_revision:
|
|
5244
|
+
GitRevisionAdder().add_to(dist_dir)
|
|
4864
5245
|
|
|
4865
|
-
|
|
4866
|
-
|
|
5246
|
+
if opts.test:
|
|
5247
|
+
for fn in os.listdir(dist_dir):
|
|
5248
|
+
tmp_dir = tempfile.mkdtemp()
|
|
4867
5249
|
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
5250
|
+
subprocess_check_call(
|
|
5251
|
+
sys.executable,
|
|
5252
|
+
'-m', 'venv',
|
|
5253
|
+
'test-install',
|
|
5254
|
+
cwd=tmp_dir,
|
|
5255
|
+
)
|
|
4871
5256
|
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
5257
|
+
subprocess_check_call(
|
|
5258
|
+
os.path.join(tmp_dir, 'test-install', 'bin', 'python3'),
|
|
5259
|
+
'-m', 'pip',
|
|
5260
|
+
'install',
|
|
5261
|
+
os.path.abspath(os.path.join(dist_dir, fn)),
|
|
5262
|
+
cwd=tmp_dir,
|
|
5263
|
+
)
|
|
4875
5264
|
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
return self._given_install_name
|
|
4880
|
-
return self._version + ('-debug' if self._interp_opts.debug else '')
|
|
5265
|
+
if output_dir is not None:
|
|
5266
|
+
for fn in os.listdir(dist_dir):
|
|
5267
|
+
shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(output_dir, fn))
|
|
4881
5268
|
|
|
4882
|
-
@cached_nullary
|
|
4883
|
-
def install_dir(self) -> str:
|
|
4884
|
-
return str(os.path.join(check_not_none(self._pyenv.root()), 'versions', self.install_name()))
|
|
4885
5269
|
|
|
4886
|
-
|
|
4887
|
-
def install(self) -> str:
|
|
4888
|
-
env = {**os.environ, **self._opts.env}
|
|
4889
|
-
for k, l in [
|
|
4890
|
-
('CFLAGS', self._opts.cflags),
|
|
4891
|
-
('LDFLAGS', self._opts.ldflags),
|
|
4892
|
-
('PYTHON_CONFIGURE_OPTS', self._opts.conf_opts),
|
|
4893
|
-
]:
|
|
4894
|
-
v = ' '.join(l)
|
|
4895
|
-
if k in os.environ:
|
|
4896
|
-
v += ' ' + os.environ[k]
|
|
4897
|
-
env[k] = v
|
|
5270
|
+
#
|
|
4898
5271
|
|
|
4899
|
-
conf_args = [
|
|
4900
|
-
*self._opts.opts,
|
|
4901
|
-
self._version,
|
|
4902
|
-
]
|
|
4903
5272
|
|
|
4904
|
-
|
|
4905
|
-
full_args = [
|
|
4906
|
-
os.path.join(check_not_none(self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'),
|
|
4907
|
-
*conf_args,
|
|
4908
|
-
self.install_dir(),
|
|
4909
|
-
]
|
|
4910
|
-
else:
|
|
4911
|
-
full_args = [
|
|
4912
|
-
self._pyenv.exe(),
|
|
4913
|
-
'install',
|
|
4914
|
-
*conf_args,
|
|
4915
|
-
]
|
|
5273
|
+
class PyprojectPackageGenerator(BasePyprojectPackageGenerator):
|
|
4916
5274
|
|
|
4917
|
-
|
|
4918
|
-
*full_args,
|
|
4919
|
-
env=env,
|
|
4920
|
-
)
|
|
5275
|
+
#
|
|
4921
5276
|
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
5277
|
+
@dc.dataclass(frozen=True)
|
|
5278
|
+
class FileContents:
|
|
5279
|
+
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
5280
|
+
manifest_in: ta.Optional[ta.Sequence[str]]
|
|
4926
5281
|
|
|
5282
|
+
@cached_nullary
|
|
5283
|
+
def file_contents(self) -> FileContents:
|
|
5284
|
+
specs = self.build_specs()
|
|
4927
5285
|
|
|
4928
|
-
|
|
5286
|
+
#
|
|
4929
5287
|
|
|
5288
|
+
pyp_dct = {}
|
|
4930
5289
|
|
|
4931
|
-
|
|
5290
|
+
pyp_dct['build-system'] = {
|
|
5291
|
+
'requires': ['setuptools'],
|
|
5292
|
+
'build-backend': 'setuptools.build_meta',
|
|
5293
|
+
}
|
|
4932
5294
|
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
pyenv: Pyenv = Pyenv(),
|
|
5295
|
+
prj = specs.pyproject
|
|
5296
|
+
prj['name'] += self._pkg_suffix
|
|
4936
5297
|
|
|
4937
|
-
|
|
4938
|
-
inspector: InterpInspector = INTERP_INSPECTOR,
|
|
5298
|
+
pyp_dct['project'] = prj
|
|
4939
5299
|
|
|
4940
|
-
|
|
5300
|
+
self._move_dict_key(prj, 'optional_dependencies', pyp_dct, extrask := 'project.optional-dependencies')
|
|
5301
|
+
if (extras := pyp_dct.get(extrask)):
|
|
5302
|
+
pyp_dct[extrask] = {
|
|
5303
|
+
'all': [
|
|
5304
|
+
e
|
|
5305
|
+
for lst in extras.values()
|
|
5306
|
+
for e in lst
|
|
5307
|
+
],
|
|
5308
|
+
**extras,
|
|
5309
|
+
}
|
|
4941
5310
|
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
super().__init__()
|
|
5311
|
+
if (eps := prj.pop('entry_points', None)):
|
|
5312
|
+
pyp_dct['project.entry-points'] = {TomlWriter.Literal(f"'{k}'"): v for k, v in eps.items()} # type: ignore # noqa
|
|
4945
5313
|
|
|
4946
|
-
|
|
5314
|
+
if (scs := prj.pop('scripts', None)):
|
|
5315
|
+
pyp_dct['project.scripts'] = scs
|
|
4947
5316
|
|
|
4948
|
-
|
|
4949
|
-
self._inspector = inspector
|
|
5317
|
+
prj.pop('cli_scripts', None)
|
|
4950
5318
|
|
|
4951
|
-
|
|
5319
|
+
##
|
|
4952
5320
|
|
|
4953
|
-
|
|
5321
|
+
st = dict(specs.setuptools)
|
|
5322
|
+
pyp_dct['tool.setuptools'] = st
|
|
4954
5323
|
|
|
4955
|
-
|
|
4956
|
-
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
|
4957
|
-
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
|
4958
|
-
if s.endswith(sfx):
|
|
4959
|
-
return s[:-len(sfx)], True
|
|
4960
|
-
return s, False
|
|
4961
|
-
ok = {}
|
|
4962
|
-
s, ok['debug'] = strip_sfx(s, '-debug')
|
|
4963
|
-
s, ok['threaded'] = strip_sfx(s, 't')
|
|
4964
|
-
try:
|
|
4965
|
-
v = Version(s)
|
|
4966
|
-
except InvalidVersion:
|
|
4967
|
-
return None
|
|
4968
|
-
return InterpVersion(v, InterpOpts(**ok))
|
|
5324
|
+
st.pop('cexts', None)
|
|
4969
5325
|
|
|
4970
|
-
|
|
4971
|
-
name: str
|
|
4972
|
-
exe: str
|
|
4973
|
-
version: InterpVersion
|
|
5326
|
+
#
|
|
4974
5327
|
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
except Exception as e: # noqa
|
|
4981
|
-
return None
|
|
4982
|
-
else:
|
|
4983
|
-
iv = self.guess_version(vn)
|
|
4984
|
-
if iv is None:
|
|
4985
|
-
return None
|
|
4986
|
-
return PyenvInterpProvider.Installed(
|
|
4987
|
-
name=vn,
|
|
4988
|
-
exe=ep,
|
|
4989
|
-
version=iv,
|
|
4990
|
-
)
|
|
5328
|
+
# TODO: default
|
|
5329
|
+
# find_packages = {
|
|
5330
|
+
# 'include': [Project.name, f'{Project.name}.*'],
|
|
5331
|
+
# 'exclude': [*SetuptoolsBase.find_packages['exclude']],
|
|
5332
|
+
# }
|
|
4991
5333
|
|
|
4992
|
-
|
|
4993
|
-
ret: ta.List[PyenvInterpProvider.Installed] = []
|
|
4994
|
-
for vn, ep in self._pyenv.version_exes():
|
|
4995
|
-
if (i := self._make_installed(vn, ep)) is None:
|
|
4996
|
-
log.debug('Invalid pyenv version: %s', vn)
|
|
4997
|
-
continue
|
|
4998
|
-
ret.append(i)
|
|
4999
|
-
return ret
|
|
5334
|
+
fp = dict(st.pop('find_packages', {}))
|
|
5000
5335
|
|
|
5001
|
-
|
|
5336
|
+
pyp_dct['tool.setuptools.packages.find'] = fp
|
|
5002
5337
|
|
|
5003
|
-
|
|
5004
|
-
return [i.version for i in self.installed()]
|
|
5338
|
+
#
|
|
5005
5339
|
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5340
|
+
# TODO: default
|
|
5341
|
+
# package_data = {
|
|
5342
|
+
# '*': [
|
|
5343
|
+
# '*.c',
|
|
5344
|
+
# '*.cc',
|
|
5345
|
+
# '*.h',
|
|
5346
|
+
# '.manifests.json',
|
|
5347
|
+
# 'LICENSE',
|
|
5348
|
+
# ],
|
|
5349
|
+
# }
|
|
5014
5350
|
|
|
5015
|
-
|
|
5351
|
+
pd = dict(st.pop('package_data', {}))
|
|
5352
|
+
epd = dict(st.pop('exclude_package_data', {}))
|
|
5016
5353
|
|
|
5017
|
-
|
|
5018
|
-
|
|
5354
|
+
cpd = self._collect_pkg_data()
|
|
5355
|
+
if cpd.inc:
|
|
5356
|
+
pd['*'] = [*pd.get('*', []), *sorted(set(cpd.inc))]
|
|
5357
|
+
if cpd.exc:
|
|
5358
|
+
epd['*'] = [*epd.get('*', []), *sorted(set(cpd.exc))]
|
|
5019
5359
|
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
|
5025
|
-
for d in [False, True]:
|
|
5026
|
-
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
|
5360
|
+
if pd:
|
|
5361
|
+
pyp_dct['tool.setuptools.package-data'] = pd
|
|
5362
|
+
if epd:
|
|
5363
|
+
pyp_dct['tool.setuptools.exclude-package-data'] = epd
|
|
5027
5364
|
|
|
5028
|
-
|
|
5365
|
+
#
|
|
5366
|
+
|
|
5367
|
+
# TODO: default
|
|
5368
|
+
# manifest_in = [
|
|
5369
|
+
# 'global-exclude **/conftest.py',
|
|
5370
|
+
# ]
|
|
5029
5371
|
|
|
5030
|
-
|
|
5031
|
-
lst = self._get_installable_versions(spec)
|
|
5372
|
+
mani_in = st.pop('manifest_in', None)
|
|
5032
5373
|
|
|
5033
|
-
|
|
5034
|
-
if self._pyenv.update():
|
|
5035
|
-
lst = self._get_installable_versions(spec)
|
|
5374
|
+
#
|
|
5036
5375
|
|
|
5037
|
-
return
|
|
5376
|
+
return self.FileContents(
|
|
5377
|
+
pyp_dct,
|
|
5378
|
+
mani_in,
|
|
5379
|
+
)
|
|
5038
5380
|
|
|
5039
|
-
def
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
inst_opts = dc.replace(inst_opts, threaded=False)
|
|
5381
|
+
def _write_file_contents(self) -> None:
|
|
5382
|
+
fc = self.file_contents()
|
|
5383
|
+
|
|
5384
|
+
with open(os.path.join(self._pkg_dir(), 'pyproject.toml'), 'w') as f:
|
|
5385
|
+
TomlWriter(f).write_root(fc.pyproject_dct)
|
|
5045
5386
|
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
)
|
|
5387
|
+
if fc.manifest_in:
|
|
5388
|
+
with open(os.path.join(self._pkg_dir(), 'MANIFEST.in'), 'w') as f:
|
|
5389
|
+
f.write('\n'.join(fc.manifest_in)) # noqa
|
|
5050
5390
|
|
|
5051
|
-
|
|
5052
|
-
return Interp(exe, version)
|
|
5391
|
+
#
|
|
5053
5392
|
|
|
5393
|
+
@cached_nullary
|
|
5394
|
+
def children(self) -> ta.Sequence[BasePyprojectPackageGenerator]:
|
|
5395
|
+
out: ta.List[BasePyprojectPackageGenerator] = []
|
|
5054
5396
|
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
"""
|
|
5397
|
+
if self.build_specs().setuptools.get('cexts'):
|
|
5398
|
+
out.append(_PyprojectCextPackageGenerator(
|
|
5399
|
+
self._dir_name,
|
|
5400
|
+
self._pkgs_root,
|
|
5401
|
+
pkg_suffix='-cext',
|
|
5402
|
+
))
|
|
5062
5403
|
|
|
5404
|
+
if self.build_specs().pyproject.get('cli_scripts'):
|
|
5405
|
+
out.append(_PyprojectCliPackageGenerator(
|
|
5406
|
+
self._dir_name,
|
|
5407
|
+
self._pkgs_root,
|
|
5408
|
+
pkg_suffix='-cli',
|
|
5409
|
+
))
|
|
5063
5410
|
|
|
5064
|
-
|
|
5411
|
+
return out
|
|
5065
5412
|
|
|
5066
5413
|
|
|
5067
|
-
|
|
5068
|
-
class SystemInterpProvider(InterpProvider):
|
|
5069
|
-
cmd: str = 'python3'
|
|
5070
|
-
path: ta.Optional[str] = None
|
|
5414
|
+
#
|
|
5071
5415
|
|
|
5072
|
-
|
|
5073
|
-
|
|
5416
|
+
|
|
5417
|
+
class _PyprojectCextPackageGenerator(BasePyprojectPackageGenerator):
|
|
5074
5418
|
|
|
5075
5419
|
#
|
|
5076
5420
|
|
|
5077
|
-
@
|
|
5078
|
-
def
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
if path is None:
|
|
5085
|
-
path = os.environ.get('PATH', None)
|
|
5086
|
-
if path is None:
|
|
5087
|
-
try:
|
|
5088
|
-
path = os.confstr('CS_PATH')
|
|
5089
|
-
except (AttributeError, ValueError):
|
|
5090
|
-
path = os.defpath
|
|
5421
|
+
@cached_nullary
|
|
5422
|
+
def find_cext_srcs(self) -> ta.Sequence[str]:
|
|
5423
|
+
return sorted(find_magic_files(
|
|
5424
|
+
CextMagic.STYLE,
|
|
5425
|
+
[self._dir_name],
|
|
5426
|
+
keys=[CextMagic.KEY],
|
|
5427
|
+
))
|
|
5091
5428
|
|
|
5092
|
-
|
|
5093
|
-
return []
|
|
5429
|
+
#
|
|
5094
5430
|
|
|
5095
|
-
|
|
5096
|
-
|
|
5431
|
+
@dc.dataclass(frozen=True)
|
|
5432
|
+
class FileContents:
|
|
5433
|
+
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
5434
|
+
setup_py: str
|
|
5097
5435
|
|
|
5098
|
-
|
|
5099
|
-
|
|
5436
|
+
@cached_nullary
|
|
5437
|
+
def file_contents(self) -> FileContents:
|
|
5438
|
+
specs = self.build_specs()
|
|
5100
5439
|
|
|
5101
|
-
|
|
5102
|
-
seen = set()
|
|
5103
|
-
for d in pathlst:
|
|
5104
|
-
normdir = os.path.normcase(d)
|
|
5105
|
-
if normdir not in seen:
|
|
5106
|
-
seen.add(normdir)
|
|
5107
|
-
if not _access_check(normdir, mode):
|
|
5108
|
-
continue
|
|
5109
|
-
for thefile in os.listdir(d):
|
|
5110
|
-
name = os.path.join(d, thefile)
|
|
5111
|
-
if not (
|
|
5112
|
-
os.path.isfile(name) and
|
|
5113
|
-
pat.fullmatch(thefile) and
|
|
5114
|
-
_access_check(name, mode)
|
|
5115
|
-
):
|
|
5116
|
-
continue
|
|
5117
|
-
out.append(name)
|
|
5440
|
+
#
|
|
5118
5441
|
|
|
5119
|
-
|
|
5442
|
+
pyp_dct = {}
|
|
5120
5443
|
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
path=self.path,
|
|
5126
|
-
)
|
|
5444
|
+
pyp_dct['build-system'] = {
|
|
5445
|
+
'requires': ['setuptools'],
|
|
5446
|
+
'build-backend': 'setuptools.build_meta',
|
|
5447
|
+
}
|
|
5127
5448
|
|
|
5128
|
-
|
|
5449
|
+
prj = specs.pyproject
|
|
5450
|
+
prj['dependencies'] = [f'{prj["name"]} == {prj["version"]}']
|
|
5451
|
+
prj['name'] += self._pkg_suffix
|
|
5452
|
+
for k in [
|
|
5453
|
+
'optional_dependencies',
|
|
5454
|
+
'entry_points',
|
|
5455
|
+
'scripts',
|
|
5456
|
+
'cli_scripts',
|
|
5457
|
+
]:
|
|
5458
|
+
prj.pop(k, None)
|
|
5129
5459
|
|
|
5130
|
-
|
|
5131
|
-
if not self.inspect:
|
|
5132
|
-
s = os.path.basename(exe)
|
|
5133
|
-
if s.startswith('python'):
|
|
5134
|
-
s = s[len('python'):]
|
|
5135
|
-
if '.' in s:
|
|
5136
|
-
try:
|
|
5137
|
-
return InterpVersion.parse(s)
|
|
5138
|
-
except InvalidVersion:
|
|
5139
|
-
pass
|
|
5140
|
-
ii = self.inspector.inspect(exe)
|
|
5141
|
-
return ii.iv if ii is not None else None
|
|
5460
|
+
pyp_dct['project'] = prj
|
|
5142
5461
|
|
|
5143
|
-
|
|
5144
|
-
lst = []
|
|
5145
|
-
for e in self.exes():
|
|
5146
|
-
if (ev := self.get_exe_version(e)) is None:
|
|
5147
|
-
log.debug('Invalid system version: %s', e)
|
|
5148
|
-
continue
|
|
5149
|
-
lst.append((e, ev))
|
|
5150
|
-
return lst
|
|
5462
|
+
#
|
|
5151
5463
|
|
|
5152
|
-
|
|
5464
|
+
st = dict(specs.setuptools)
|
|
5465
|
+
pyp_dct['tool.setuptools'] = st
|
|
5153
5466
|
|
|
5154
|
-
|
|
5155
|
-
|
|
5467
|
+
for k in [
|
|
5468
|
+
'cexts',
|
|
5156
5469
|
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
exe=e,
|
|
5163
|
-
version=ev,
|
|
5164
|
-
)
|
|
5165
|
-
raise KeyError(version)
|
|
5470
|
+
'find_packages',
|
|
5471
|
+
'package_data',
|
|
5472
|
+
'manifest_in',
|
|
5473
|
+
]:
|
|
5474
|
+
st.pop(k, None)
|
|
5166
5475
|
|
|
5476
|
+
pyp_dct['tool.setuptools.packages.find'] = {
|
|
5477
|
+
'include': [],
|
|
5478
|
+
}
|
|
5167
5479
|
|
|
5168
|
-
|
|
5169
|
-
# ../pkg.py
|
|
5170
|
-
"""
|
|
5171
|
-
TODO:
|
|
5172
|
-
- ext scanning
|
|
5173
|
-
- __revision__
|
|
5174
|
-
- entry_points
|
|
5480
|
+
#
|
|
5175
5481
|
|
|
5176
|
-
|
|
5177
|
-
setuptools now (2024/09/02) has experimental support for extensions in pure pyproject.toml - but we still want a
|
|
5178
|
-
separate '-cext' package
|
|
5179
|
-
https://setuptools.pypa.io/en/latest/userguide/ext_modules.html
|
|
5180
|
-
https://github.com/pypa/setuptools/commit/1a9d87308dc0d8aabeaae0dce989b35dfb7699f0#diff-61d113525e9cc93565799a4bb8b34a68e2945b8a3f7d90c81380614a4ea39542R7-R8
|
|
5482
|
+
ext_lines = []
|
|
5181
5483
|
|
|
5182
|
-
|
|
5484
|
+
for ext_src in self.find_cext_srcs():
|
|
5485
|
+
ext_name = ext_src.rpartition('.')[0].replace(os.sep, '.')
|
|
5486
|
+
ext_lines.extend([
|
|
5487
|
+
'st.Extension(',
|
|
5488
|
+
f" name='{ext_name}',",
|
|
5489
|
+
f" sources=['{ext_src}'],",
|
|
5490
|
+
" extra_compile_args=['-std=c++20'],",
|
|
5491
|
+
'),',
|
|
5492
|
+
])
|
|
5183
5493
|
|
|
5184
|
-
|
|
5185
|
-
|
|
5494
|
+
src = '\n'.join([
|
|
5495
|
+
'import setuptools as st',
|
|
5496
|
+
'',
|
|
5497
|
+
'',
|
|
5498
|
+
'st.setup(',
|
|
5499
|
+
' ext_modules=[',
|
|
5500
|
+
*[' ' + l for l in ext_lines],
|
|
5501
|
+
' ]',
|
|
5502
|
+
')',
|
|
5503
|
+
'',
|
|
5504
|
+
])
|
|
5186
5505
|
|
|
5187
|
-
|
|
5188
|
-
https://stackoverflow.com/a/66479252
|
|
5506
|
+
#
|
|
5189
5507
|
|
|
5190
|
-
|
|
5508
|
+
return self.FileContents(
|
|
5509
|
+
pyp_dct,
|
|
5510
|
+
src,
|
|
5511
|
+
)
|
|
5191
5512
|
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
'git+https://github.com/wrmsr/omlish@master#subdirectory=.pip/omlish'
|
|
5195
|
-
""" # noqa
|
|
5513
|
+
def _write_file_contents(self) -> None:
|
|
5514
|
+
fc = self.file_contents()
|
|
5196
5515
|
|
|
5516
|
+
with open(os.path.join(self._pkg_dir(), 'pyproject.toml'), 'w') as f:
|
|
5517
|
+
TomlWriter(f).write_root(fc.pyproject_dct)
|
|
5197
5518
|
|
|
5198
|
-
|
|
5519
|
+
with open(os.path.join(self._pkg_dir(), 'setup.py'), 'w') as f:
|
|
5520
|
+
f.write(fc.setup_py)
|
|
5199
5521
|
|
|
5200
5522
|
|
|
5201
|
-
|
|
5202
|
-
def __init__(
|
|
5203
|
-
self,
|
|
5204
|
-
dir_name: str,
|
|
5205
|
-
pkgs_root: str,
|
|
5206
|
-
*,
|
|
5207
|
-
pkg_suffix: str = '',
|
|
5208
|
-
) -> None:
|
|
5209
|
-
super().__init__()
|
|
5210
|
-
self._dir_name = dir_name
|
|
5211
|
-
self._pkgs_root = pkgs_root
|
|
5212
|
-
self._pkg_suffix = pkg_suffix
|
|
5523
|
+
##
|
|
5213
5524
|
|
|
5214
|
-
#
|
|
5215
5525
|
|
|
5216
|
-
|
|
5217
|
-
def about(self) -> types.ModuleType:
|
|
5218
|
-
return importlib.import_module(f'{self._dir_name}.__about__')
|
|
5526
|
+
class _PyprojectCliPackageGenerator(BasePyprojectPackageGenerator):
|
|
5219
5527
|
|
|
5220
5528
|
#
|
|
5221
5529
|
|
|
5530
|
+
@dc.dataclass(frozen=True)
|
|
5531
|
+
class FileContents:
|
|
5532
|
+
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
5533
|
+
|
|
5222
5534
|
@cached_nullary
|
|
5223
|
-
def
|
|
5224
|
-
|
|
5225
|
-
if os.path.isdir(pkg_dir):
|
|
5226
|
-
shutil.rmtree(pkg_dir)
|
|
5227
|
-
os.makedirs(pkg_dir)
|
|
5228
|
-
return pkg_dir
|
|
5535
|
+
def file_contents(self) -> FileContents:
|
|
5536
|
+
specs = self.build_specs()
|
|
5229
5537
|
|
|
5230
|
-
|
|
5538
|
+
#
|
|
5231
5539
|
|
|
5232
|
-
|
|
5233
|
-
'/*.egg-info/',
|
|
5234
|
-
'/dist',
|
|
5235
|
-
]
|
|
5540
|
+
pyp_dct = {}
|
|
5236
5541
|
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5542
|
+
pyp_dct['build-system'] = {
|
|
5543
|
+
'requires': ['setuptools'],
|
|
5544
|
+
'build-backend': 'setuptools.build_meta',
|
|
5545
|
+
}
|
|
5240
5546
|
|
|
5241
|
-
|
|
5547
|
+
prj = specs.pyproject
|
|
5548
|
+
prj['dependencies'] = [f'{prj["name"]} == {prj["version"]}']
|
|
5549
|
+
prj['name'] += self._pkg_suffix
|
|
5550
|
+
for k in [
|
|
5551
|
+
'optional_dependencies',
|
|
5552
|
+
'entry_points',
|
|
5553
|
+
'scripts',
|
|
5554
|
+
]:
|
|
5555
|
+
prj.pop(k, None)
|
|
5242
5556
|
|
|
5243
|
-
|
|
5244
|
-
os.symlink(
|
|
5245
|
-
os.path.relpath(self._dir_name, self._pkg_dir()),
|
|
5246
|
-
os.path.join(self._pkg_dir(), self._dir_name),
|
|
5247
|
-
)
|
|
5557
|
+
pyp_dct['project'] = prj
|
|
5248
5558
|
|
|
5249
|
-
|
|
5559
|
+
if (scs := prj.pop('cli_scripts', None)):
|
|
5560
|
+
pyp_dct['project.scripts'] = scs
|
|
5250
5561
|
|
|
5251
|
-
|
|
5252
|
-
def project_cls(self) -> type:
|
|
5253
|
-
return self.about().Project
|
|
5562
|
+
#
|
|
5254
5563
|
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
return self.about().Setuptools
|
|
5564
|
+
st = dict(specs.setuptools)
|
|
5565
|
+
pyp_dct['tool.setuptools'] = st
|
|
5258
5566
|
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
dct = {}
|
|
5262
|
-
for b in reversed(cls.__mro__):
|
|
5263
|
-
for k, v in b.__dict__.items():
|
|
5264
|
-
if k.startswith('_'):
|
|
5265
|
-
continue
|
|
5266
|
-
dct[k] = v
|
|
5267
|
-
return dct
|
|
5567
|
+
for k in [
|
|
5568
|
+
'cexts',
|
|
5268
5569
|
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
dk: str,
|
|
5275
|
-
) -> None:
|
|
5276
|
-
if sk in sd:
|
|
5277
|
-
dd[dk] = sd.pop(sk)
|
|
5570
|
+
'find_packages',
|
|
5571
|
+
'package_data',
|
|
5572
|
+
'manifest_in',
|
|
5573
|
+
]:
|
|
5574
|
+
st.pop(k, None)
|
|
5278
5575
|
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
setuptools: ta.Dict[str, ta.Any]
|
|
5576
|
+
pyp_dct['tool.setuptools.packages.find'] = {
|
|
5577
|
+
'include': [],
|
|
5578
|
+
}
|
|
5283
5579
|
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5580
|
+
#
|
|
5581
|
+
|
|
5582
|
+
return self.FileContents(
|
|
5583
|
+
pyp_dct,
|
|
5288
5584
|
)
|
|
5289
5585
|
|
|
5290
|
-
|
|
5586
|
+
def _write_file_contents(self) -> None:
|
|
5587
|
+
fc = self.file_contents()
|
|
5291
5588
|
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
exc: ta.List[str]
|
|
5589
|
+
with open(os.path.join(self._pkg_dir(), 'pyproject.toml'), 'w') as f:
|
|
5590
|
+
TomlWriter(f).write_root(fc.pyproject_dct)
|
|
5295
5591
|
|
|
5296
|
-
@cached_nullary
|
|
5297
|
-
def _collect_pkg_data(self) -> _PkgData:
|
|
5298
|
-
inc: ta.List[str] = []
|
|
5299
|
-
exc: ta.List[str] = []
|
|
5300
5592
|
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
else:
|
|
5315
|
-
inc.append(os.path.join(rp, l))
|
|
5593
|
+
########################################
|
|
5594
|
+
# ../../interp/pyenv.py
|
|
5595
|
+
"""
|
|
5596
|
+
TODO:
|
|
5597
|
+
- custom tags
|
|
5598
|
+
- 'aliases'
|
|
5599
|
+
- https://github.com/pyenv/pyenv/pull/2966
|
|
5600
|
+
- https://github.com/pyenv/pyenv/issues/218 (lol)
|
|
5601
|
+
- probably need custom (temp?) definition file
|
|
5602
|
+
- *or* python-build directly just into the versions dir?
|
|
5603
|
+
- optionally install / upgrade pyenv itself
|
|
5604
|
+
- new vers dont need these custom mac opts, only run on old vers
|
|
5605
|
+
"""
|
|
5316
5606
|
|
|
5317
|
-
return self._PkgData(inc, exc)
|
|
5318
5607
|
|
|
5319
|
-
|
|
5608
|
+
##
|
|
5320
5609
|
|
|
5321
|
-
@abc.abstractmethod
|
|
5322
|
-
def _write_file_contents(self) -> None:
|
|
5323
|
-
raise NotImplementedError
|
|
5324
5610
|
|
|
5325
|
-
|
|
5611
|
+
class Pyenv:
|
|
5612
|
+
def __init__(
|
|
5613
|
+
self,
|
|
5614
|
+
*,
|
|
5615
|
+
root: ta.Optional[str] = None,
|
|
5616
|
+
) -> None:
|
|
5617
|
+
if root is not None and not (isinstance(root, str) and root):
|
|
5618
|
+
raise ValueError(f'pyenv_root: {root!r}')
|
|
5326
5619
|
|
|
5327
|
-
|
|
5328
|
-
'LICENSE',
|
|
5329
|
-
'README.rst',
|
|
5330
|
-
]
|
|
5620
|
+
super().__init__()
|
|
5331
5621
|
|
|
5332
|
-
|
|
5333
|
-
for fn in self._STANDARD_FILES:
|
|
5334
|
-
if os.path.exists(fn):
|
|
5335
|
-
os.symlink(os.path.relpath(fn, self._pkg_dir()), os.path.join(self._pkg_dir(), fn))
|
|
5622
|
+
self._root_kw = root
|
|
5336
5623
|
|
|
5337
|
-
|
|
5624
|
+
@async_cached_nullary
|
|
5625
|
+
async def root(self) -> ta.Optional[str]:
|
|
5626
|
+
if self._root_kw is not None:
|
|
5627
|
+
return self._root_kw
|
|
5338
5628
|
|
|
5339
|
-
|
|
5340
|
-
|
|
5629
|
+
if shutil.which('pyenv'):
|
|
5630
|
+
return await asyncio_subprocess_check_output_str('pyenv', 'root')
|
|
5341
5631
|
|
|
5342
|
-
|
|
5632
|
+
d = os.path.expanduser('~/.pyenv')
|
|
5633
|
+
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
|
5634
|
+
return d
|
|
5343
5635
|
|
|
5344
|
-
|
|
5345
|
-
log.info('Generating pyproject package: %s -> %s (%s)', self._dir_name, self._pkgs_root, self._pkg_suffix)
|
|
5636
|
+
return None
|
|
5346
5637
|
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
self.
|
|
5350
|
-
self._write_file_contents()
|
|
5351
|
-
self._symlink_standard_files()
|
|
5638
|
+
@async_cached_nullary
|
|
5639
|
+
async def exe(self) -> str:
|
|
5640
|
+
return os.path.join(check_not_none(await self.root()), 'bin', 'pyenv')
|
|
5352
5641
|
|
|
5353
|
-
|
|
5642
|
+
async def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
|
|
5643
|
+
if (root := await self.root()) is None:
|
|
5644
|
+
return []
|
|
5645
|
+
ret = []
|
|
5646
|
+
vp = os.path.join(root, 'versions')
|
|
5647
|
+
if os.path.isdir(vp):
|
|
5648
|
+
for dn in os.listdir(vp):
|
|
5649
|
+
ep = os.path.join(vp, dn, 'bin', 'python')
|
|
5650
|
+
if not os.path.isfile(ep):
|
|
5651
|
+
continue
|
|
5652
|
+
ret.append((dn, ep))
|
|
5653
|
+
return ret
|
|
5354
5654
|
|
|
5355
|
-
|
|
5655
|
+
async def installable_versions(self) -> ta.List[str]:
|
|
5656
|
+
if await self.root() is None:
|
|
5657
|
+
return []
|
|
5658
|
+
ret = []
|
|
5659
|
+
s = await asyncio_subprocess_check_output_str(await self.exe(), 'install', '--list')
|
|
5660
|
+
for l in s.splitlines():
|
|
5661
|
+
if not l.startswith(' '):
|
|
5662
|
+
continue
|
|
5663
|
+
l = l.strip()
|
|
5664
|
+
if not l:
|
|
5665
|
+
continue
|
|
5666
|
+
ret.append(l)
|
|
5667
|
+
return ret
|
|
5356
5668
|
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5669
|
+
async def update(self) -> bool:
|
|
5670
|
+
if (root := await self.root()) is None:
|
|
5671
|
+
return False
|
|
5672
|
+
if not os.path.isdir(os.path.join(root, '.git')):
|
|
5673
|
+
return False
|
|
5674
|
+
await asyncio_subprocess_check_call('git', 'pull', cwd=root)
|
|
5675
|
+
return True
|
|
5361
5676
|
|
|
5362
|
-
def build(
|
|
5363
|
-
self,
|
|
5364
|
-
output_dir: ta.Optional[str] = None,
|
|
5365
|
-
opts: BuildOpts = BuildOpts(),
|
|
5366
|
-
) -> None:
|
|
5367
|
-
subprocess_check_call(
|
|
5368
|
-
sys.executable,
|
|
5369
|
-
'-m',
|
|
5370
|
-
'build',
|
|
5371
|
-
cwd=self._pkg_dir(),
|
|
5372
|
-
)
|
|
5373
5677
|
|
|
5374
|
-
|
|
5678
|
+
##
|
|
5375
5679
|
|
|
5376
|
-
if opts.add_revision:
|
|
5377
|
-
GitRevisionAdder().add_to(dist_dir)
|
|
5378
5680
|
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5681
|
+
@dc.dataclass(frozen=True)
|
|
5682
|
+
class PyenvInstallOpts:
|
|
5683
|
+
opts: ta.Sequence[str] = ()
|
|
5684
|
+
conf_opts: ta.Sequence[str] = ()
|
|
5685
|
+
cflags: ta.Sequence[str] = ()
|
|
5686
|
+
ldflags: ta.Sequence[str] = ()
|
|
5687
|
+
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
|
5382
5688
|
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5689
|
+
def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
|
|
5690
|
+
return PyenvInstallOpts(
|
|
5691
|
+
opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
|
|
5692
|
+
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
|
5693
|
+
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
|
5694
|
+
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
|
5695
|
+
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
|
5696
|
+
)
|
|
5389
5697
|
|
|
5390
|
-
subprocess_check_call(
|
|
5391
|
-
os.path.join(tmp_dir, 'test-install', 'bin', 'python3'),
|
|
5392
|
-
'-m', 'pip',
|
|
5393
|
-
'install',
|
|
5394
|
-
os.path.abspath(os.path.join(dist_dir, fn)),
|
|
5395
|
-
cwd=tmp_dir,
|
|
5396
|
-
)
|
|
5397
5698
|
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5699
|
+
# TODO: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-for-maximum-performance
|
|
5700
|
+
DEFAULT_PYENV_INSTALL_OPTS = PyenvInstallOpts(
|
|
5701
|
+
opts=[
|
|
5702
|
+
'-s',
|
|
5703
|
+
'-v',
|
|
5704
|
+
'-k',
|
|
5705
|
+
],
|
|
5706
|
+
conf_opts=[
|
|
5707
|
+
# FIXME: breaks on mac for older py's
|
|
5708
|
+
'--enable-loadable-sqlite-extensions',
|
|
5401
5709
|
|
|
5710
|
+
# '--enable-shared',
|
|
5402
5711
|
|
|
5403
|
-
|
|
5712
|
+
'--enable-optimizations',
|
|
5713
|
+
'--with-lto',
|
|
5404
5714
|
|
|
5715
|
+
# '--enable-profiling', # ?
|
|
5405
5716
|
|
|
5406
|
-
|
|
5717
|
+
# '--enable-ipv6', # ?
|
|
5718
|
+
],
|
|
5719
|
+
cflags=[
|
|
5720
|
+
# '-march=native',
|
|
5721
|
+
# '-mtune=native',
|
|
5722
|
+
],
|
|
5723
|
+
)
|
|
5407
5724
|
|
|
5408
|
-
|
|
5725
|
+
DEBUG_PYENV_INSTALL_OPTS = PyenvInstallOpts(opts=['-g'])
|
|
5726
|
+
|
|
5727
|
+
THREADED_PYENV_INSTALL_OPTS = PyenvInstallOpts(conf_opts=['--disable-gil'])
|
|
5409
5728
|
|
|
5410
|
-
@dc.dataclass(frozen=True)
|
|
5411
|
-
class FileContents:
|
|
5412
|
-
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
5413
|
-
manifest_in: ta.Optional[ta.Sequence[str]]
|
|
5414
5729
|
|
|
5415
|
-
|
|
5416
|
-
def file_contents(self) -> FileContents:
|
|
5417
|
-
specs = self.build_specs()
|
|
5730
|
+
#
|
|
5418
5731
|
|
|
5419
|
-
#
|
|
5420
5732
|
|
|
5421
|
-
|
|
5733
|
+
class PyenvInstallOptsProvider(abc.ABC):
|
|
5734
|
+
@abc.abstractmethod
|
|
5735
|
+
def opts(self) -> ta.Awaitable[PyenvInstallOpts]:
|
|
5736
|
+
raise NotImplementedError
|
|
5422
5737
|
|
|
5423
|
-
pyp_dct['build-system'] = {
|
|
5424
|
-
'requires': ['setuptools'],
|
|
5425
|
-
'build-backend': 'setuptools.build_meta',
|
|
5426
|
-
}
|
|
5427
5738
|
|
|
5428
|
-
|
|
5429
|
-
|
|
5739
|
+
class LinuxPyenvInstallOpts(PyenvInstallOptsProvider):
|
|
5740
|
+
async def opts(self) -> PyenvInstallOpts:
|
|
5741
|
+
return PyenvInstallOpts()
|
|
5430
5742
|
|
|
5431
|
-
pyp_dct['project'] = prj
|
|
5432
5743
|
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
e
|
|
5438
|
-
for lst in extras.values()
|
|
5439
|
-
for e in lst
|
|
5440
|
-
],
|
|
5441
|
-
**extras,
|
|
5442
|
-
}
|
|
5744
|
+
class DarwinPyenvInstallOpts(PyenvInstallOptsProvider):
|
|
5745
|
+
@cached_nullary
|
|
5746
|
+
def framework_opts(self) -> PyenvInstallOpts:
|
|
5747
|
+
return PyenvInstallOpts(conf_opts=['--enable-framework'])
|
|
5443
5748
|
|
|
5444
|
-
|
|
5445
|
-
|
|
5749
|
+
@cached_nullary
|
|
5750
|
+
def has_brew(self) -> bool:
|
|
5751
|
+
return shutil.which('brew') is not None
|
|
5446
5752
|
|
|
5447
|
-
|
|
5448
|
-
|
|
5753
|
+
BREW_DEPS: ta.Sequence[str] = [
|
|
5754
|
+
'openssl',
|
|
5755
|
+
'readline',
|
|
5756
|
+
'sqlite3',
|
|
5757
|
+
'zlib',
|
|
5758
|
+
]
|
|
5449
5759
|
|
|
5450
|
-
|
|
5760
|
+
@async_cached_nullary
|
|
5761
|
+
async def brew_deps_opts(self) -> PyenvInstallOpts:
|
|
5762
|
+
cflags = []
|
|
5763
|
+
ldflags = []
|
|
5764
|
+
for dep in self.BREW_DEPS:
|
|
5765
|
+
dep_prefix = await asyncio_subprocess_check_output_str('brew', '--prefix', dep)
|
|
5766
|
+
cflags.append(f'-I{dep_prefix}/include')
|
|
5767
|
+
ldflags.append(f'-L{dep_prefix}/lib')
|
|
5768
|
+
return PyenvInstallOpts(
|
|
5769
|
+
cflags=cflags,
|
|
5770
|
+
ldflags=ldflags,
|
|
5771
|
+
)
|
|
5451
5772
|
|
|
5452
|
-
|
|
5773
|
+
@async_cached_nullary
|
|
5774
|
+
async def brew_tcl_opts(self) -> PyenvInstallOpts:
|
|
5775
|
+
if await asyncio_subprocess_try_output('brew', '--prefix', 'tcl-tk') is None:
|
|
5776
|
+
return PyenvInstallOpts()
|
|
5453
5777
|
|
|
5454
|
-
|
|
5455
|
-
|
|
5778
|
+
tcl_tk_prefix = await asyncio_subprocess_check_output_str('brew', '--prefix', 'tcl-tk')
|
|
5779
|
+
tcl_tk_ver_str = await asyncio_subprocess_check_output_str('brew', 'ls', '--versions', 'tcl-tk')
|
|
5780
|
+
tcl_tk_ver = '.'.join(tcl_tk_ver_str.split()[1].split('.')[:2])
|
|
5456
5781
|
|
|
5457
|
-
|
|
5782
|
+
return PyenvInstallOpts(conf_opts=[
|
|
5783
|
+
f"--with-tcltk-includes='-I{tcl_tk_prefix}/include'",
|
|
5784
|
+
f"--with-tcltk-libs='-L{tcl_tk_prefix}/lib -ltcl{tcl_tk_ver} -ltk{tcl_tk_ver}'",
|
|
5785
|
+
])
|
|
5458
5786
|
|
|
5459
|
-
|
|
5787
|
+
# @cached_nullary
|
|
5788
|
+
# def brew_ssl_opts(self) -> PyenvInstallOpts:
|
|
5789
|
+
# pkg_config_path = subprocess_check_output_str('brew', '--prefix', 'openssl')
|
|
5790
|
+
# if 'PKG_CONFIG_PATH' in os.environ:
|
|
5791
|
+
# pkg_config_path += ':' + os.environ['PKG_CONFIG_PATH']
|
|
5792
|
+
# return PyenvInstallOpts(env={'PKG_CONFIG_PATH': pkg_config_path})
|
|
5460
5793
|
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5794
|
+
async def opts(self) -> PyenvInstallOpts:
|
|
5795
|
+
return PyenvInstallOpts().merge(
|
|
5796
|
+
self.framework_opts(),
|
|
5797
|
+
await self.brew_deps_opts(),
|
|
5798
|
+
await self.brew_tcl_opts(),
|
|
5799
|
+
# self.brew_ssl_opts(),
|
|
5800
|
+
)
|
|
5466
5801
|
|
|
5467
|
-
fp = dict(st.pop('find_packages', {}))
|
|
5468
5802
|
|
|
5469
|
-
|
|
5803
|
+
PLATFORM_PYENV_INSTALL_OPTS: ta.Dict[str, PyenvInstallOptsProvider] = {
|
|
5804
|
+
'darwin': DarwinPyenvInstallOpts(),
|
|
5805
|
+
'linux': LinuxPyenvInstallOpts(),
|
|
5806
|
+
}
|
|
5470
5807
|
|
|
5471
|
-
#
|
|
5472
5808
|
|
|
5473
|
-
|
|
5474
|
-
# package_data = {
|
|
5475
|
-
# '*': [
|
|
5476
|
-
# '*.c',
|
|
5477
|
-
# '*.cc',
|
|
5478
|
-
# '*.h',
|
|
5479
|
-
# '.manifests.json',
|
|
5480
|
-
# 'LICENSE',
|
|
5481
|
-
# ],
|
|
5482
|
-
# }
|
|
5809
|
+
##
|
|
5483
5810
|
|
|
5484
|
-
pd = dict(st.pop('package_data', {}))
|
|
5485
|
-
epd = dict(st.pop('exclude_package_data', {}))
|
|
5486
5811
|
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5812
|
+
class PyenvVersionInstaller:
|
|
5813
|
+
"""
|
|
5814
|
+
Messy: can install freethreaded build with a 't' suffixed version str _or_ by THREADED_PYENV_INSTALL_OPTS - need
|
|
5815
|
+
latter to build custom interp with ft, need former to use canned / blessed interps. Muh.
|
|
5816
|
+
"""
|
|
5492
5817
|
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5818
|
+
def __init__(
|
|
5819
|
+
self,
|
|
5820
|
+
version: str,
|
|
5821
|
+
opts: ta.Optional[PyenvInstallOpts] = None,
|
|
5822
|
+
interp_opts: InterpOpts = InterpOpts(),
|
|
5823
|
+
*,
|
|
5824
|
+
install_name: ta.Optional[str] = None,
|
|
5825
|
+
no_default_opts: bool = False,
|
|
5826
|
+
pyenv: Pyenv = Pyenv(),
|
|
5827
|
+
) -> None:
|
|
5828
|
+
super().__init__()
|
|
5497
5829
|
|
|
5498
|
-
|
|
5830
|
+
self._version = version
|
|
5831
|
+
self._given_opts = opts
|
|
5832
|
+
self._interp_opts = interp_opts
|
|
5833
|
+
self._given_install_name = install_name
|
|
5499
5834
|
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
# 'global-exclude **/conftest.py',
|
|
5503
|
-
# ]
|
|
5835
|
+
self._no_default_opts = no_default_opts
|
|
5836
|
+
self._pyenv = pyenv
|
|
5504
5837
|
|
|
5505
|
-
|
|
5838
|
+
@property
|
|
5839
|
+
def version(self) -> str:
|
|
5840
|
+
return self._version
|
|
5506
5841
|
|
|
5507
|
-
|
|
5842
|
+
@async_cached_nullary
|
|
5843
|
+
async def opts(self) -> PyenvInstallOpts:
|
|
5844
|
+
opts = self._given_opts
|
|
5845
|
+
if self._no_default_opts:
|
|
5846
|
+
if opts is None:
|
|
5847
|
+
opts = PyenvInstallOpts()
|
|
5848
|
+
else:
|
|
5849
|
+
lst = [self._given_opts if self._given_opts is not None else DEFAULT_PYENV_INSTALL_OPTS]
|
|
5850
|
+
if self._interp_opts.debug:
|
|
5851
|
+
lst.append(DEBUG_PYENV_INSTALL_OPTS)
|
|
5852
|
+
if self._interp_opts.threaded:
|
|
5853
|
+
lst.append(THREADED_PYENV_INSTALL_OPTS)
|
|
5854
|
+
lst.append(await PLATFORM_PYENV_INSTALL_OPTS[sys.platform].opts())
|
|
5855
|
+
opts = PyenvInstallOpts().merge(*lst)
|
|
5856
|
+
return opts
|
|
5508
5857
|
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5858
|
+
@cached_nullary
|
|
5859
|
+
def install_name(self) -> str:
|
|
5860
|
+
if self._given_install_name is not None:
|
|
5861
|
+
return self._given_install_name
|
|
5862
|
+
return self._version + ('-debug' if self._interp_opts.debug else '')
|
|
5513
5863
|
|
|
5514
|
-
|
|
5515
|
-
|
|
5864
|
+
@async_cached_nullary
|
|
5865
|
+
async def install_dir(self) -> str:
|
|
5866
|
+
return str(os.path.join(check_not_none(await self._pyenv.root()), 'versions', self.install_name()))
|
|
5516
5867
|
|
|
5517
|
-
|
|
5518
|
-
|
|
5868
|
+
@async_cached_nullary
|
|
5869
|
+
async def install(self) -> str:
|
|
5870
|
+
opts = await self.opts()
|
|
5871
|
+
env = {**os.environ, **opts.env}
|
|
5872
|
+
for k, l in [
|
|
5873
|
+
('CFLAGS', opts.cflags),
|
|
5874
|
+
('LDFLAGS', opts.ldflags),
|
|
5875
|
+
('PYTHON_CONFIGURE_OPTS', opts.conf_opts),
|
|
5876
|
+
]:
|
|
5877
|
+
v = ' '.join(l)
|
|
5878
|
+
if k in os.environ:
|
|
5879
|
+
v += ' ' + os.environ[k]
|
|
5880
|
+
env[k] = v
|
|
5519
5881
|
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
|
|
5882
|
+
conf_args = [
|
|
5883
|
+
*opts.opts,
|
|
5884
|
+
self._version,
|
|
5885
|
+
]
|
|
5523
5886
|
|
|
5524
|
-
|
|
5887
|
+
if self._given_install_name is not None:
|
|
5888
|
+
full_args = [
|
|
5889
|
+
os.path.join(check_not_none(await self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'), # noqa
|
|
5890
|
+
*conf_args,
|
|
5891
|
+
self.install_dir(),
|
|
5892
|
+
]
|
|
5893
|
+
else:
|
|
5894
|
+
full_args = [
|
|
5895
|
+
self._pyenv.exe(),
|
|
5896
|
+
'install',
|
|
5897
|
+
*conf_args,
|
|
5898
|
+
]
|
|
5525
5899
|
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5900
|
+
await asyncio_subprocess_check_call(
|
|
5901
|
+
*full_args,
|
|
5902
|
+
env=env,
|
|
5903
|
+
)
|
|
5529
5904
|
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
pkg_suffix='-cext',
|
|
5535
|
-
))
|
|
5905
|
+
exe = os.path.join(await self.install_dir(), 'bin', 'python')
|
|
5906
|
+
if not os.path.isfile(exe):
|
|
5907
|
+
raise RuntimeError(f'Interpreter not found: {exe}')
|
|
5908
|
+
return exe
|
|
5536
5909
|
|
|
5537
|
-
if self.build_specs().pyproject.get('cli_scripts'):
|
|
5538
|
-
out.append(_PyprojectCliPackageGenerator(
|
|
5539
|
-
self._dir_name,
|
|
5540
|
-
self._pkgs_root,
|
|
5541
|
-
pkg_suffix='-cli',
|
|
5542
|
-
))
|
|
5543
5910
|
|
|
5544
|
-
|
|
5911
|
+
##
|
|
5545
5912
|
|
|
5546
5913
|
|
|
5547
|
-
|
|
5914
|
+
class PyenvInterpProvider(InterpProvider):
|
|
5915
|
+
def __init__(
|
|
5916
|
+
self,
|
|
5917
|
+
pyenv: Pyenv = Pyenv(),
|
|
5548
5918
|
|
|
5919
|
+
inspect: bool = False,
|
|
5920
|
+
inspector: InterpInspector = INTERP_INSPECTOR,
|
|
5549
5921
|
|
|
5550
|
-
|
|
5922
|
+
*,
|
|
5551
5923
|
|
|
5552
|
-
|
|
5924
|
+
try_update: bool = False,
|
|
5925
|
+
) -> None:
|
|
5926
|
+
super().__init__()
|
|
5553
5927
|
|
|
5554
|
-
|
|
5555
|
-
def find_cext_srcs(self) -> ta.Sequence[str]:
|
|
5556
|
-
return sorted(find_magic_files(
|
|
5557
|
-
CextMagic.STYLE,
|
|
5558
|
-
[self._dir_name],
|
|
5559
|
-
keys=[CextMagic.KEY],
|
|
5560
|
-
))
|
|
5928
|
+
self._pyenv = pyenv
|
|
5561
5929
|
|
|
5562
|
-
|
|
5930
|
+
self._inspect = inspect
|
|
5931
|
+
self._inspector = inspector
|
|
5563
5932
|
|
|
5564
|
-
|
|
5565
|
-
class FileContents:
|
|
5566
|
-
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
5567
|
-
setup_py: str
|
|
5933
|
+
self._try_update = try_update
|
|
5568
5934
|
|
|
5569
|
-
|
|
5570
|
-
def file_contents(self) -> FileContents:
|
|
5571
|
-
specs = self.build_specs()
|
|
5935
|
+
#
|
|
5572
5936
|
|
|
5573
|
-
|
|
5937
|
+
@staticmethod
|
|
5938
|
+
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
|
5939
|
+
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
|
5940
|
+
if s.endswith(sfx):
|
|
5941
|
+
return s[:-len(sfx)], True
|
|
5942
|
+
return s, False
|
|
5943
|
+
ok = {}
|
|
5944
|
+
s, ok['debug'] = strip_sfx(s, '-debug')
|
|
5945
|
+
s, ok['threaded'] = strip_sfx(s, 't')
|
|
5946
|
+
try:
|
|
5947
|
+
v = Version(s)
|
|
5948
|
+
except InvalidVersion:
|
|
5949
|
+
return None
|
|
5950
|
+
return InterpVersion(v, InterpOpts(**ok))
|
|
5574
5951
|
|
|
5575
|
-
|
|
5952
|
+
class Installed(ta.NamedTuple):
|
|
5953
|
+
name: str
|
|
5954
|
+
exe: str
|
|
5955
|
+
version: InterpVersion
|
|
5576
5956
|
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5957
|
+
async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
|
5958
|
+
iv: ta.Optional[InterpVersion]
|
|
5959
|
+
if self._inspect:
|
|
5960
|
+
try:
|
|
5961
|
+
iv = check_not_none(await self._inspector.inspect(ep)).iv
|
|
5962
|
+
except Exception as e: # noqa
|
|
5963
|
+
return None
|
|
5964
|
+
else:
|
|
5965
|
+
iv = self.guess_version(vn)
|
|
5966
|
+
if iv is None:
|
|
5967
|
+
return None
|
|
5968
|
+
return PyenvInterpProvider.Installed(
|
|
5969
|
+
name=vn,
|
|
5970
|
+
exe=ep,
|
|
5971
|
+
version=iv,
|
|
5972
|
+
)
|
|
5581
5973
|
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
]:
|
|
5591
|
-
prj.pop(k, None)
|
|
5974
|
+
async def installed(self) -> ta.Sequence[Installed]:
|
|
5975
|
+
ret: ta.List[PyenvInterpProvider.Installed] = []
|
|
5976
|
+
for vn, ep in await self._pyenv.version_exes():
|
|
5977
|
+
if (i := await self._make_installed(vn, ep)) is None:
|
|
5978
|
+
log.debug('Invalid pyenv version: %s', vn)
|
|
5979
|
+
continue
|
|
5980
|
+
ret.append(i)
|
|
5981
|
+
return ret
|
|
5592
5982
|
|
|
5593
|
-
|
|
5983
|
+
#
|
|
5594
5984
|
|
|
5595
|
-
|
|
5985
|
+
async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
5986
|
+
return [i.version for i in await self.installed()]
|
|
5596
5987
|
|
|
5597
|
-
|
|
5598
|
-
|
|
5988
|
+
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
5989
|
+
for i in await self.installed():
|
|
5990
|
+
if i.version == version:
|
|
5991
|
+
return Interp(
|
|
5992
|
+
exe=i.exe,
|
|
5993
|
+
version=i.version,
|
|
5994
|
+
)
|
|
5995
|
+
raise KeyError(version)
|
|
5599
5996
|
|
|
5600
|
-
|
|
5601
|
-
'cexts',
|
|
5997
|
+
#
|
|
5602
5998
|
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
'manifest_in',
|
|
5606
|
-
]:
|
|
5607
|
-
st.pop(k, None)
|
|
5999
|
+
async def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
6000
|
+
lst = []
|
|
5608
6001
|
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
6002
|
+
for vs in await self._pyenv.installable_versions():
|
|
6003
|
+
if (iv := self.guess_version(vs)) is None:
|
|
6004
|
+
continue
|
|
6005
|
+
if iv.opts.debug:
|
|
6006
|
+
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
|
6007
|
+
for d in [False, True]:
|
|
6008
|
+
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
|
5612
6009
|
|
|
5613
|
-
|
|
6010
|
+
return lst
|
|
5614
6011
|
|
|
5615
|
-
|
|
6012
|
+
async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
6013
|
+
lst = await self._get_installable_versions(spec)
|
|
5616
6014
|
|
|
5617
|
-
for
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
'st.Extension(',
|
|
5621
|
-
f" name='{ext_name}',",
|
|
5622
|
-
f" sources=['{ext_src}'],",
|
|
5623
|
-
" extra_compile_args=['-std=c++20'],",
|
|
5624
|
-
'),',
|
|
5625
|
-
])
|
|
6015
|
+
if self._try_update and not any(v in spec for v in lst):
|
|
6016
|
+
if self._pyenv.update():
|
|
6017
|
+
lst = await self._get_installable_versions(spec)
|
|
5626
6018
|
|
|
5627
|
-
|
|
5628
|
-
'import setuptools as st',
|
|
5629
|
-
'',
|
|
5630
|
-
'',
|
|
5631
|
-
'st.setup(',
|
|
5632
|
-
' ext_modules=[',
|
|
5633
|
-
*[' ' + l for l in ext_lines],
|
|
5634
|
-
' ]',
|
|
5635
|
-
')',
|
|
5636
|
-
'',
|
|
5637
|
-
])
|
|
6019
|
+
return lst
|
|
5638
6020
|
|
|
5639
|
-
|
|
6021
|
+
async def install_version(self, version: InterpVersion) -> Interp:
|
|
6022
|
+
inst_version = str(version.version)
|
|
6023
|
+
inst_opts = version.opts
|
|
6024
|
+
if inst_opts.threaded:
|
|
6025
|
+
inst_version += 't'
|
|
6026
|
+
inst_opts = dc.replace(inst_opts, threaded=False)
|
|
5640
6027
|
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
6028
|
+
installer = PyenvVersionInstaller(
|
|
6029
|
+
inst_version,
|
|
6030
|
+
interp_opts=inst_opts,
|
|
5644
6031
|
)
|
|
5645
6032
|
|
|
5646
|
-
|
|
5647
|
-
|
|
6033
|
+
exe = await installer.install()
|
|
6034
|
+
return Interp(exe, version)
|
|
5648
6035
|
|
|
5649
|
-
with open(os.path.join(self._pkg_dir(), 'pyproject.toml'), 'w') as f:
|
|
5650
|
-
TomlWriter(f).write_root(fc.pyproject_dct)
|
|
5651
6036
|
|
|
5652
|
-
|
|
5653
|
-
|
|
6037
|
+
########################################
|
|
6038
|
+
# ../../interp/system.py
|
|
6039
|
+
"""
|
|
6040
|
+
TODO:
|
|
6041
|
+
- python, python3, python3.12, ...
|
|
6042
|
+
- check if path py's are venvs: sys.prefix != sys.base_prefix
|
|
6043
|
+
"""
|
|
5654
6044
|
|
|
5655
6045
|
|
|
5656
6046
|
##
|
|
5657
6047
|
|
|
5658
6048
|
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
@dc.dataclass(frozen=True)
|
|
5664
|
-
class FileContents:
|
|
5665
|
-
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
5666
|
-
|
|
5667
|
-
@cached_nullary
|
|
5668
|
-
def file_contents(self) -> FileContents:
|
|
5669
|
-
specs = self.build_specs()
|
|
6049
|
+
@dc.dataclass(frozen=True)
|
|
6050
|
+
class SystemInterpProvider(InterpProvider):
|
|
6051
|
+
cmd: str = 'python3'
|
|
6052
|
+
path: ta.Optional[str] = None
|
|
5670
6053
|
|
|
5671
|
-
|
|
6054
|
+
inspect: bool = False
|
|
6055
|
+
inspector: InterpInspector = INTERP_INSPECTOR
|
|
5672
6056
|
|
|
5673
|
-
|
|
6057
|
+
#
|
|
5674
6058
|
|
|
5675
|
-
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
|
|
6059
|
+
@staticmethod
|
|
6060
|
+
def _re_which(
|
|
6061
|
+
pat: re.Pattern,
|
|
6062
|
+
*,
|
|
6063
|
+
mode: int = os.F_OK | os.X_OK,
|
|
6064
|
+
path: ta.Optional[str] = None,
|
|
6065
|
+
) -> ta.List[str]:
|
|
6066
|
+
if path is None:
|
|
6067
|
+
path = os.environ.get('PATH', None)
|
|
6068
|
+
if path is None:
|
|
6069
|
+
try:
|
|
6070
|
+
path = os.confstr('CS_PATH')
|
|
6071
|
+
except (AttributeError, ValueError):
|
|
6072
|
+
path = os.defpath
|
|
5679
6073
|
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
prj['name'] += self._pkg_suffix
|
|
5683
|
-
for k in [
|
|
5684
|
-
'optional_dependencies',
|
|
5685
|
-
'entry_points',
|
|
5686
|
-
'scripts',
|
|
5687
|
-
]:
|
|
5688
|
-
prj.pop(k, None)
|
|
6074
|
+
if not path:
|
|
6075
|
+
return []
|
|
5689
6076
|
|
|
5690
|
-
|
|
6077
|
+
path = os.fsdecode(path)
|
|
6078
|
+
pathlst = path.split(os.pathsep)
|
|
5691
6079
|
|
|
5692
|
-
|
|
5693
|
-
|
|
6080
|
+
def _access_check(fn: str, mode: int) -> bool:
|
|
6081
|
+
return os.path.exists(fn) and os.access(fn, mode)
|
|
5694
6082
|
|
|
5695
|
-
|
|
6083
|
+
out = []
|
|
6084
|
+
seen = set()
|
|
6085
|
+
for d in pathlst:
|
|
6086
|
+
normdir = os.path.normcase(d)
|
|
6087
|
+
if normdir not in seen:
|
|
6088
|
+
seen.add(normdir)
|
|
6089
|
+
if not _access_check(normdir, mode):
|
|
6090
|
+
continue
|
|
6091
|
+
for thefile in os.listdir(d):
|
|
6092
|
+
name = os.path.join(d, thefile)
|
|
6093
|
+
if not (
|
|
6094
|
+
os.path.isfile(name) and
|
|
6095
|
+
pat.fullmatch(thefile) and
|
|
6096
|
+
_access_check(name, mode)
|
|
6097
|
+
):
|
|
6098
|
+
continue
|
|
6099
|
+
out.append(name)
|
|
5696
6100
|
|
|
5697
|
-
|
|
5698
|
-
pyp_dct['tool.setuptools'] = st
|
|
6101
|
+
return out
|
|
5699
6102
|
|
|
5700
|
-
|
|
5701
|
-
|
|
6103
|
+
@cached_nullary
|
|
6104
|
+
def exes(self) -> ta.List[str]:
|
|
6105
|
+
return self._re_which(
|
|
6106
|
+
re.compile(r'python3(\.\d+)?'),
|
|
6107
|
+
path=self.path,
|
|
6108
|
+
)
|
|
5702
6109
|
|
|
5703
|
-
|
|
5704
|
-
'package_data',
|
|
5705
|
-
'manifest_in',
|
|
5706
|
-
]:
|
|
5707
|
-
st.pop(k, None)
|
|
6110
|
+
#
|
|
5708
6111
|
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
6112
|
+
async def get_exe_version(self, exe: str) -> ta.Optional[InterpVersion]:
|
|
6113
|
+
if not self.inspect:
|
|
6114
|
+
s = os.path.basename(exe)
|
|
6115
|
+
if s.startswith('python'):
|
|
6116
|
+
s = s[len('python'):]
|
|
6117
|
+
if '.' in s:
|
|
6118
|
+
try:
|
|
6119
|
+
return InterpVersion.parse(s)
|
|
6120
|
+
except InvalidVersion:
|
|
6121
|
+
pass
|
|
6122
|
+
ii = await self.inspector.inspect(exe)
|
|
6123
|
+
return ii.iv if ii is not None else None
|
|
5712
6124
|
|
|
5713
|
-
|
|
6125
|
+
async def exe_versions(self) -> ta.Sequence[ta.Tuple[str, InterpVersion]]:
|
|
6126
|
+
lst = []
|
|
6127
|
+
for e in self.exes():
|
|
6128
|
+
if (ev := await self.get_exe_version(e)) is None:
|
|
6129
|
+
log.debug('Invalid system version: %s', e)
|
|
6130
|
+
continue
|
|
6131
|
+
lst.append((e, ev))
|
|
6132
|
+
return lst
|
|
5714
6133
|
|
|
5715
|
-
|
|
5716
|
-
pyp_dct,
|
|
5717
|
-
)
|
|
6134
|
+
#
|
|
5718
6135
|
|
|
5719
|
-
def
|
|
5720
|
-
|
|
6136
|
+
async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
6137
|
+
return [ev for e, ev in await self.exe_versions()]
|
|
5721
6138
|
|
|
5722
|
-
|
|
5723
|
-
|
|
6139
|
+
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
6140
|
+
for e, ev in await self.exe_versions():
|
|
6141
|
+
if ev != version:
|
|
6142
|
+
continue
|
|
6143
|
+
return Interp(
|
|
6144
|
+
exe=e,
|
|
6145
|
+
version=ev,
|
|
6146
|
+
)
|
|
6147
|
+
raise KeyError(version)
|
|
5724
6148
|
|
|
5725
6149
|
|
|
5726
6150
|
########################################
|
|
@@ -5738,13 +6162,14 @@ class InterpResolver:
|
|
|
5738
6162
|
providers: ta.Sequence[ta.Tuple[str, InterpProvider]],
|
|
5739
6163
|
) -> None:
|
|
5740
6164
|
super().__init__()
|
|
6165
|
+
|
|
5741
6166
|
self._providers: ta.Mapping[str, InterpProvider] = collections.OrderedDict(providers)
|
|
5742
6167
|
|
|
5743
|
-
def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
|
|
6168
|
+
async def _resolve_installed(self, spec: InterpSpecifier) -> ta.Optional[ta.Tuple[InterpProvider, InterpVersion]]:
|
|
5744
6169
|
lst = [
|
|
5745
6170
|
(i, si)
|
|
5746
6171
|
for i, p in enumerate(self._providers.values())
|
|
5747
|
-
for si in p.get_installed_versions(spec)
|
|
6172
|
+
for si in await p.get_installed_versions(spec)
|
|
5748
6173
|
if spec.contains(si)
|
|
5749
6174
|
]
|
|
5750
6175
|
|
|
@@ -5756,16 +6181,16 @@ class InterpResolver:
|
|
|
5756
6181
|
bp = list(self._providers.values())[bi]
|
|
5757
6182
|
return (bp, bv)
|
|
5758
6183
|
|
|
5759
|
-
def resolve(
|
|
6184
|
+
async def resolve(
|
|
5760
6185
|
self,
|
|
5761
6186
|
spec: InterpSpecifier,
|
|
5762
6187
|
*,
|
|
5763
6188
|
install: bool = False,
|
|
5764
6189
|
) -> ta.Optional[Interp]:
|
|
5765
|
-
tup = self._resolve_installed(spec)
|
|
6190
|
+
tup = await self._resolve_installed(spec)
|
|
5766
6191
|
if tup is not None:
|
|
5767
6192
|
bp, bv = tup
|
|
5768
|
-
return bp.get_installed_version(bv)
|
|
6193
|
+
return await bp.get_installed_version(bv)
|
|
5769
6194
|
|
|
5770
6195
|
if not install:
|
|
5771
6196
|
return None
|
|
@@ -5773,21 +6198,21 @@ class InterpResolver:
|
|
|
5773
6198
|
tp = list(self._providers.values())[0] # noqa
|
|
5774
6199
|
|
|
5775
6200
|
sv = sorted(
|
|
5776
|
-
[s for s in tp.get_installable_versions(spec) if s in spec],
|
|
6201
|
+
[s for s in await tp.get_installable_versions(spec) if s in spec],
|
|
5777
6202
|
key=lambda s: s.version,
|
|
5778
6203
|
)
|
|
5779
6204
|
if not sv:
|
|
5780
6205
|
return None
|
|
5781
6206
|
|
|
5782
6207
|
bv = sv[-1]
|
|
5783
|
-
return tp.install_version(bv)
|
|
6208
|
+
return await tp.install_version(bv)
|
|
5784
6209
|
|
|
5785
|
-
def list(self, spec: InterpSpecifier) -> None:
|
|
6210
|
+
async def list(self, spec: InterpSpecifier) -> None:
|
|
5786
6211
|
print('installed:')
|
|
5787
6212
|
for n, p in self._providers.items():
|
|
5788
6213
|
lst = [
|
|
5789
6214
|
si
|
|
5790
|
-
for si in p.get_installed_versions(spec)
|
|
6215
|
+
for si in await p.get_installed_versions(spec)
|
|
5791
6216
|
if spec.contains(si)
|
|
5792
6217
|
]
|
|
5793
6218
|
if lst:
|
|
@@ -5801,7 +6226,7 @@ class InterpResolver:
|
|
|
5801
6226
|
for n, p in self._providers.items():
|
|
5802
6227
|
lst = [
|
|
5803
6228
|
si
|
|
5804
|
-
for si in p.get_installable_versions(spec)
|
|
6229
|
+
for si in await p.get_installable_versions(spec)
|
|
5805
6230
|
if spec.contains(si)
|
|
5806
6231
|
]
|
|
5807
6232
|
if lst:
|
|
@@ -5892,10 +6317,10 @@ class Venv:
|
|
|
5892
6317
|
def dir_name(self) -> str:
|
|
5893
6318
|
return os.path.join(self.DIR_NAME, self._name)
|
|
5894
6319
|
|
|
5895
|
-
@
|
|
5896
|
-
def interp_exe(self) -> str:
|
|
6320
|
+
@async_cached_nullary
|
|
6321
|
+
async def interp_exe(self) -> str:
|
|
5897
6322
|
i = InterpSpecifier.parse(check_not_none(self._cfg.interp))
|
|
5898
|
-
return check_not_none(DEFAULT_INTERP_RESOLVER.resolve(i, install=True)).exe
|
|
6323
|
+
return check_not_none(await DEFAULT_INTERP_RESOLVER.resolve(i, install=True)).exe
|
|
5899
6324
|
|
|
5900
6325
|
@cached_nullary
|
|
5901
6326
|
def exe(self) -> str:
|
|
@@ -5904,14 +6329,14 @@ class Venv:
|
|
|
5904
6329
|
raise Exception(f'venv exe {ve} does not exist or is not a file!')
|
|
5905
6330
|
return ve
|
|
5906
6331
|
|
|
5907
|
-
@
|
|
5908
|
-
def create(self) -> bool:
|
|
6332
|
+
@async_cached_nullary
|
|
6333
|
+
async def create(self) -> bool:
|
|
5909
6334
|
if os.path.exists(dn := self.dir_name):
|
|
5910
6335
|
if not os.path.isdir(dn):
|
|
5911
6336
|
raise Exception(f'{dn} exists but is not a directory!')
|
|
5912
6337
|
return False
|
|
5913
6338
|
|
|
5914
|
-
log.info('Using interpreter %s', (ie := self.interp_exe()))
|
|
6339
|
+
log.info('Using interpreter %s', (ie := await self.interp_exe()))
|
|
5915
6340
|
subprocess_check_call(ie, '-m', 'venv', dn)
|
|
5916
6341
|
|
|
5917
6342
|
ve = self.exe()
|
|
@@ -6011,7 +6436,7 @@ class Run:
|
|
|
6011
6436
|
##
|
|
6012
6437
|
|
|
6013
6438
|
|
|
6014
|
-
def _venv_cmd(args) -> None:
|
|
6439
|
+
async def _venv_cmd(args) -> None:
|
|
6015
6440
|
venv = Run().venvs()[args.name]
|
|
6016
6441
|
if (sd := venv.cfg.docker) is not None and sd != (cd := args._docker_container): # noqa
|
|
6017
6442
|
script = ' '.join([
|
|
@@ -6048,10 +6473,10 @@ def _venv_cmd(args) -> None:
|
|
|
6048
6473
|
|
|
6049
6474
|
cmd = args.cmd
|
|
6050
6475
|
if not cmd:
|
|
6051
|
-
venv.create()
|
|
6476
|
+
await venv.create()
|
|
6052
6477
|
|
|
6053
6478
|
elif cmd == 'python':
|
|
6054
|
-
venv.create()
|
|
6479
|
+
await venv.create()
|
|
6055
6480
|
os.execl(
|
|
6056
6481
|
(exe := venv.exe()),
|
|
6057
6482
|
exe,
|
|
@@ -6059,12 +6484,12 @@ def _venv_cmd(args) -> None:
|
|
|
6059
6484
|
)
|
|
6060
6485
|
|
|
6061
6486
|
elif cmd == 'exe':
|
|
6062
|
-
venv.create()
|
|
6487
|
+
await venv.create()
|
|
6063
6488
|
check_not(args.args)
|
|
6064
6489
|
print(venv.exe())
|
|
6065
6490
|
|
|
6066
6491
|
elif cmd == 'run':
|
|
6067
|
-
venv.create()
|
|
6492
|
+
await venv.create()
|
|
6068
6493
|
sh = check_not_none(shutil.which('bash'))
|
|
6069
6494
|
script = ' '.join(args.args)
|
|
6070
6495
|
if not script:
|
|
@@ -6081,7 +6506,7 @@ def _venv_cmd(args) -> None:
|
|
|
6081
6506
|
print('\n'.join(venv.srcs()))
|
|
6082
6507
|
|
|
6083
6508
|
elif cmd == 'test':
|
|
6084
|
-
venv.create()
|
|
6509
|
+
await venv.create()
|
|
6085
6510
|
subprocess_check_call(venv.exe(), '-m', 'pytest', *(args.args or []), *venv.srcs())
|
|
6086
6511
|
|
|
6087
6512
|
else:
|
|
@@ -6091,7 +6516,7 @@ def _venv_cmd(args) -> None:
|
|
|
6091
6516
|
##
|
|
6092
6517
|
|
|
6093
6518
|
|
|
6094
|
-
def _pkg_cmd(args) -> None:
|
|
6519
|
+
async def _pkg_cmd(args) -> None:
|
|
6095
6520
|
run = Run()
|
|
6096
6521
|
|
|
6097
6522
|
cmd = args.cmd
|
|
@@ -6178,7 +6603,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
6178
6603
|
return parser
|
|
6179
6604
|
|
|
6180
6605
|
|
|
6181
|
-
def
|
|
6606
|
+
async def _async_main(argv: ta.Optional[ta.Sequence[str]] = None) -> None:
|
|
6182
6607
|
check_runtime_version()
|
|
6183
6608
|
configure_standard_logging()
|
|
6184
6609
|
|
|
@@ -6187,7 +6612,11 @@ def _main(argv: ta.Optional[ta.Sequence[str]] = None) -> None:
|
|
|
6187
6612
|
if not getattr(args, 'func', None):
|
|
6188
6613
|
parser.print_help()
|
|
6189
6614
|
else:
|
|
6190
|
-
args.func(args)
|
|
6615
|
+
await args.func(args)
|
|
6616
|
+
|
|
6617
|
+
|
|
6618
|
+
def _main(argv: ta.Optional[ta.Sequence[str]] = None) -> None:
|
|
6619
|
+
asyncio.run(_async_main(argv))
|
|
6191
6620
|
|
|
6192
6621
|
|
|
6193
6622
|
if __name__ == '__main__':
|