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