omdev 0.0.0.dev111__py3-none-any.whl → 0.0.0.dev113__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 +13 -1
- omdev/git.py +313 -14
- omdev/pycharm/__init__.py +0 -0
- omdev/pycharm/__main__.py +11 -0
- omdev/pycharm/cli.py +98 -0
- omdev/scripts/pyproject.py +1421 -1127
- omdev/tools/git.py +54 -0
- {omdev-0.0.0.dev111.dist-info → omdev-0.0.0.dev113.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev111.dist-info → omdev-0.0.0.dev113.dist-info}/RECORD +13 -10
- {omdev-0.0.0.dev111.dist-info → omdev-0.0.0.dev113.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev111.dist-info → omdev-0.0.0.dev113.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev111.dist-info → omdev-0.0.0.dev113.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev111.dist-info → omdev-0.0.0.dev113.dist-info}/top_level.txt +0 -0
omdev/scripts/pyproject.py
CHANGED
|
@@ -99,109 +99,6 @@ UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
|
|
|
99
99
|
CallableVersionOperator = ta.Callable[['Version', str], bool]
|
|
100
100
|
|
|
101
101
|
|
|
102
|
-
########################################
|
|
103
|
-
# ../../git.py
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def git_clone_subtree(
|
|
107
|
-
*,
|
|
108
|
-
base_dir: str,
|
|
109
|
-
repo_url: str,
|
|
110
|
-
repo_dir: str,
|
|
111
|
-
branch: ta.Optional[str] = None,
|
|
112
|
-
rev: ta.Optional[str] = None,
|
|
113
|
-
repo_subtrees: ta.Sequence[str],
|
|
114
|
-
) -> None:
|
|
115
|
-
if not bool(branch) ^ bool(rev):
|
|
116
|
-
raise ValueError('must set branch or rev')
|
|
117
|
-
|
|
118
|
-
if isinstance(repo_subtrees, str):
|
|
119
|
-
raise TypeError(repo_subtrees)
|
|
120
|
-
|
|
121
|
-
git_opts = [
|
|
122
|
-
'-c', 'advice.detachedHead=false',
|
|
123
|
-
]
|
|
124
|
-
|
|
125
|
-
subprocess.check_call(
|
|
126
|
-
[
|
|
127
|
-
'git',
|
|
128
|
-
*git_opts,
|
|
129
|
-
'clone',
|
|
130
|
-
'-n',
|
|
131
|
-
'--depth=1',
|
|
132
|
-
'--filter=tree:0',
|
|
133
|
-
*(['-b', branch] if branch else []),
|
|
134
|
-
'--single-branch',
|
|
135
|
-
repo_url,
|
|
136
|
-
repo_dir,
|
|
137
|
-
],
|
|
138
|
-
cwd=base_dir,
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
rd = os.path.join(base_dir, repo_dir)
|
|
142
|
-
subprocess.check_call(
|
|
143
|
-
[
|
|
144
|
-
'git',
|
|
145
|
-
*git_opts,
|
|
146
|
-
'sparse-checkout',
|
|
147
|
-
'set',
|
|
148
|
-
'--no-cone',
|
|
149
|
-
*repo_subtrees,
|
|
150
|
-
],
|
|
151
|
-
cwd=rd,
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
subprocess.check_call(
|
|
155
|
-
[
|
|
156
|
-
'git',
|
|
157
|
-
*git_opts,
|
|
158
|
-
'checkout',
|
|
159
|
-
*([rev] if rev else []),
|
|
160
|
-
],
|
|
161
|
-
cwd=rd,
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def get_git_revision(
|
|
166
|
-
*,
|
|
167
|
-
cwd: ta.Optional[str] = None,
|
|
168
|
-
) -> ta.Optional[str]:
|
|
169
|
-
subprocess.check_output(['git', '--version'])
|
|
170
|
-
|
|
171
|
-
if cwd is None:
|
|
172
|
-
cwd = os.getcwd()
|
|
173
|
-
|
|
174
|
-
if subprocess.run( # noqa
|
|
175
|
-
[
|
|
176
|
-
'git',
|
|
177
|
-
'rev-parse',
|
|
178
|
-
'--is-inside-work-tree',
|
|
179
|
-
],
|
|
180
|
-
stdout=subprocess.PIPE,
|
|
181
|
-
stderr=subprocess.PIPE,
|
|
182
|
-
).returncode:
|
|
183
|
-
return None
|
|
184
|
-
|
|
185
|
-
has_untracked = bool(subprocess.check_output([
|
|
186
|
-
'git',
|
|
187
|
-
'ls-files',
|
|
188
|
-
'.',
|
|
189
|
-
'--exclude-standard',
|
|
190
|
-
'--others',
|
|
191
|
-
], cwd=cwd).decode().strip())
|
|
192
|
-
|
|
193
|
-
dirty_rev = subprocess.check_output([
|
|
194
|
-
'git',
|
|
195
|
-
'describe',
|
|
196
|
-
'--match=NeVeRmAtCh',
|
|
197
|
-
'--always',
|
|
198
|
-
'--abbrev=40',
|
|
199
|
-
'--dirty',
|
|
200
|
-
], cwd=cwd).decode().strip()
|
|
201
|
-
|
|
202
|
-
return dirty_rev + ('-untracked' if has_untracked else '')
|
|
203
|
-
|
|
204
|
-
|
|
205
102
|
########################################
|
|
206
103
|
# ../../magic/magic.py
|
|
207
104
|
|
|
@@ -3735,126 +3632,6 @@ class RequirementsRewriter:
|
|
|
3735
3632
|
return in_req
|
|
3736
3633
|
|
|
3737
3634
|
|
|
3738
|
-
########################################
|
|
3739
|
-
# ../../revisions.py
|
|
3740
|
-
"""
|
|
3741
|
-
TODO:
|
|
3742
|
-
- omlish-lite, move to pyproject/
|
|
3743
|
-
- vendor-lite wheel.wheelfile
|
|
3744
|
-
"""
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
##
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
class GitRevisionAdder:
|
|
3751
|
-
def __init__(
|
|
3752
|
-
self,
|
|
3753
|
-
revision: ta.Optional[str] = None,
|
|
3754
|
-
output_suffix: ta.Optional[str] = None,
|
|
3755
|
-
) -> None:
|
|
3756
|
-
super().__init__()
|
|
3757
|
-
self._given_revision = revision
|
|
3758
|
-
self._output_suffix = output_suffix
|
|
3759
|
-
|
|
3760
|
-
@cached_nullary
|
|
3761
|
-
def revision(self) -> str:
|
|
3762
|
-
if self._given_revision is not None:
|
|
3763
|
-
return self._given_revision
|
|
3764
|
-
return check_non_empty_str(get_git_revision())
|
|
3765
|
-
|
|
3766
|
-
REVISION_ATTR = '__revision__'
|
|
3767
|
-
|
|
3768
|
-
def add_to_contents(self, dct: ta.Dict[str, bytes]) -> bool:
|
|
3769
|
-
changed = False
|
|
3770
|
-
for n in dct:
|
|
3771
|
-
if not n.endswith('__about__.py'):
|
|
3772
|
-
continue
|
|
3773
|
-
src = dct[n].decode('utf-8')
|
|
3774
|
-
lines = src.splitlines(keepends=True)
|
|
3775
|
-
for i, l in enumerate(lines):
|
|
3776
|
-
if l != f'{self.REVISION_ATTR} = None\n':
|
|
3777
|
-
continue
|
|
3778
|
-
lines[i] = f"{self.REVISION_ATTR} = '{self.revision()}'\n"
|
|
3779
|
-
changed = True
|
|
3780
|
-
dct[n] = ''.join(lines).encode('utf-8')
|
|
3781
|
-
return changed
|
|
3782
|
-
|
|
3783
|
-
def add_to_wheel(self, f: str) -> None:
|
|
3784
|
-
if not f.endswith('.whl'):
|
|
3785
|
-
raise Exception(f)
|
|
3786
|
-
log.info('Scanning wheel %s', f)
|
|
3787
|
-
|
|
3788
|
-
zis: ta.Dict[str, zipfile.ZipInfo] = {}
|
|
3789
|
-
dct: ta.Dict[str, bytes] = {}
|
|
3790
|
-
with WheelFile(f) as wf:
|
|
3791
|
-
for zi in wf.filelist:
|
|
3792
|
-
if zi.filename == wf.record_path:
|
|
3793
|
-
continue
|
|
3794
|
-
zis[zi.filename] = zi
|
|
3795
|
-
dct[zi.filename] = wf.read(zi.filename)
|
|
3796
|
-
|
|
3797
|
-
if self.add_to_contents(dct):
|
|
3798
|
-
of = f[:-4] + (self._output_suffix or '') + '.whl'
|
|
3799
|
-
log.info('Repacking wheel %s', of)
|
|
3800
|
-
with WheelFile(of, 'w') as wf:
|
|
3801
|
-
for n, d in dct.items():
|
|
3802
|
-
log.info('Adding zipinfo %s', n)
|
|
3803
|
-
wf.writestr(zis[n], d)
|
|
3804
|
-
|
|
3805
|
-
def add_to_tgz(self, f: str) -> None:
|
|
3806
|
-
if not f.endswith('.tar.gz'):
|
|
3807
|
-
raise Exception(f)
|
|
3808
|
-
log.info('Scanning tgz %s', f)
|
|
3809
|
-
|
|
3810
|
-
tis: ta.Dict[str, tarfile.TarInfo] = {}
|
|
3811
|
-
dct: ta.Dict[str, bytes] = {}
|
|
3812
|
-
with tarfile.open(f, 'r:gz') as tf:
|
|
3813
|
-
for ti in tf:
|
|
3814
|
-
tis[ti.name] = ti
|
|
3815
|
-
if ti.type == tarfile.REGTYPE:
|
|
3816
|
-
with tf.extractfile(ti.name) as tif: # type: ignore
|
|
3817
|
-
dct[ti.name] = tif.read()
|
|
3818
|
-
|
|
3819
|
-
if self.add_to_contents(dct):
|
|
3820
|
-
of = f[:-7] + (self._output_suffix or '') + '.tar.gz'
|
|
3821
|
-
log.info('Repacking tgz %s', of)
|
|
3822
|
-
with tarfile.open(of, 'w:gz') as tf:
|
|
3823
|
-
for n, ti in tis.items():
|
|
3824
|
-
log.info('Adding tarinfo %s', n)
|
|
3825
|
-
if n in dct:
|
|
3826
|
-
data = dct[n]
|
|
3827
|
-
ti.size = len(data)
|
|
3828
|
-
fo = io.BytesIO(data)
|
|
3829
|
-
else:
|
|
3830
|
-
fo = None
|
|
3831
|
-
tf.addfile(ti, fileobj=fo)
|
|
3832
|
-
|
|
3833
|
-
EXTS = ('.tar.gz', '.whl')
|
|
3834
|
-
|
|
3835
|
-
def add_to_file(self, f: str) -> None:
|
|
3836
|
-
if f.endswith('.whl'):
|
|
3837
|
-
self.add_to_wheel(f)
|
|
3838
|
-
|
|
3839
|
-
elif f.endswith('.tar.gz'):
|
|
3840
|
-
self.add_to_tgz(f)
|
|
3841
|
-
|
|
3842
|
-
def add_to(self, tgt: str) -> None:
|
|
3843
|
-
log.info('Using revision %s', self.revision())
|
|
3844
|
-
|
|
3845
|
-
if os.path.isfile(tgt):
|
|
3846
|
-
self.add_to_file(tgt)
|
|
3847
|
-
|
|
3848
|
-
elif os.path.isdir(tgt):
|
|
3849
|
-
for dp, dns, fns in os.walk(tgt): # noqa
|
|
3850
|
-
for f in fns:
|
|
3851
|
-
if any(f.endswith(ext) for ext in self.EXTS):
|
|
3852
|
-
self.add_to_file(os.path.join(dp, f))
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
#
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
3635
|
########################################
|
|
3859
3636
|
# ../../../omlish/lite/subprocesses.py
|
|
3860
3637
|
|
|
@@ -3980,39 +3757,436 @@ def subprocess_close(
|
|
|
3980
3757
|
|
|
3981
3758
|
|
|
3982
3759
|
########################################
|
|
3983
|
-
# ../../
|
|
3984
|
-
|
|
3760
|
+
# ../../git.py
|
|
3761
|
+
"""
|
|
3762
|
+
git status
|
|
3763
|
+
--porcelain=v1
|
|
3764
|
+
--ignore-submodules
|
|
3765
|
+
2>/dev/null
|
|
3766
|
+
"""
|
|
3985
3767
|
|
|
3986
|
-
@dc.dataclass(frozen=True)
|
|
3987
|
-
class InterpInspection:
|
|
3988
|
-
exe: str
|
|
3989
|
-
version: Version
|
|
3990
3768
|
|
|
3991
|
-
|
|
3992
|
-
config_vars: ta.Mapping[str, str]
|
|
3993
|
-
prefix: str
|
|
3994
|
-
base_prefix: str
|
|
3769
|
+
##
|
|
3995
3770
|
|
|
3996
|
-
@property
|
|
3997
|
-
def opts(self) -> InterpOpts:
|
|
3998
|
-
return InterpOpts(
|
|
3999
|
-
threaded=bool(self.config_vars.get('Py_GIL_DISABLED')),
|
|
4000
|
-
debug=bool(self.config_vars.get('Py_DEBUG')),
|
|
4001
|
-
)
|
|
4002
3771
|
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
3772
|
+
def git_clone_subtree(
|
|
3773
|
+
*,
|
|
3774
|
+
base_dir: str,
|
|
3775
|
+
repo_url: str,
|
|
3776
|
+
repo_dir: str,
|
|
3777
|
+
branch: ta.Optional[str] = None,
|
|
3778
|
+
rev: ta.Optional[str] = None,
|
|
3779
|
+
repo_subtrees: ta.Sequence[str],
|
|
3780
|
+
) -> None:
|
|
3781
|
+
if not bool(branch) ^ bool(rev):
|
|
3782
|
+
raise ValueError('must set branch or rev')
|
|
4009
3783
|
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
return self.prefix != self.base_prefix
|
|
3784
|
+
if isinstance(repo_subtrees, str):
|
|
3785
|
+
raise TypeError(repo_subtrees)
|
|
4013
3786
|
|
|
3787
|
+
git_opts = [
|
|
3788
|
+
'-c', 'advice.detachedHead=false',
|
|
3789
|
+
]
|
|
4014
3790
|
|
|
4015
|
-
|
|
3791
|
+
subprocess.check_call(
|
|
3792
|
+
subprocess_maybe_shell_wrap_exec(
|
|
3793
|
+
'git',
|
|
3794
|
+
*git_opts,
|
|
3795
|
+
'clone',
|
|
3796
|
+
'-n',
|
|
3797
|
+
'--depth=1',
|
|
3798
|
+
'--filter=tree:0',
|
|
3799
|
+
*(['-b', branch] if branch else []),
|
|
3800
|
+
'--single-branch',
|
|
3801
|
+
repo_url,
|
|
3802
|
+
repo_dir,
|
|
3803
|
+
),
|
|
3804
|
+
cwd=base_dir,
|
|
3805
|
+
)
|
|
3806
|
+
|
|
3807
|
+
rd = os.path.join(base_dir, repo_dir)
|
|
3808
|
+
subprocess.check_call(
|
|
3809
|
+
subprocess_maybe_shell_wrap_exec(
|
|
3810
|
+
'git',
|
|
3811
|
+
*git_opts,
|
|
3812
|
+
'sparse-checkout',
|
|
3813
|
+
'set',
|
|
3814
|
+
'--no-cone',
|
|
3815
|
+
*repo_subtrees,
|
|
3816
|
+
),
|
|
3817
|
+
cwd=rd,
|
|
3818
|
+
)
|
|
3819
|
+
|
|
3820
|
+
subprocess.check_call(
|
|
3821
|
+
subprocess_maybe_shell_wrap_exec(
|
|
3822
|
+
'git',
|
|
3823
|
+
*git_opts,
|
|
3824
|
+
'checkout',
|
|
3825
|
+
*([rev] if rev else []),
|
|
3826
|
+
),
|
|
3827
|
+
cwd=rd,
|
|
3828
|
+
)
|
|
3829
|
+
|
|
3830
|
+
|
|
3831
|
+
def get_git_revision(
|
|
3832
|
+
*,
|
|
3833
|
+
cwd: ta.Optional[str] = None,
|
|
3834
|
+
) -> ta.Optional[str]:
|
|
3835
|
+
subprocess.check_output(subprocess_maybe_shell_wrap_exec('git', '--version'))
|
|
3836
|
+
|
|
3837
|
+
if cwd is None:
|
|
3838
|
+
cwd = os.getcwd()
|
|
3839
|
+
|
|
3840
|
+
if subprocess.run( # noqa
|
|
3841
|
+
subprocess_maybe_shell_wrap_exec(
|
|
3842
|
+
'git',
|
|
3843
|
+
'rev-parse',
|
|
3844
|
+
'--is-inside-work-tree',
|
|
3845
|
+
),
|
|
3846
|
+
stdout=subprocess.PIPE,
|
|
3847
|
+
stderr=subprocess.PIPE,
|
|
3848
|
+
).returncode:
|
|
3849
|
+
return None
|
|
3850
|
+
|
|
3851
|
+
has_untracked = bool(subprocess.check_output(subprocess_maybe_shell_wrap_exec(
|
|
3852
|
+
'git',
|
|
3853
|
+
'ls-files',
|
|
3854
|
+
'.',
|
|
3855
|
+
'--exclude-standard',
|
|
3856
|
+
'--others',
|
|
3857
|
+
), cwd=cwd).decode().strip())
|
|
3858
|
+
|
|
3859
|
+
dirty_rev = subprocess.check_output(subprocess_maybe_shell_wrap_exec(
|
|
3860
|
+
'git',
|
|
3861
|
+
'describe',
|
|
3862
|
+
'--match=NeVeRmAtCh',
|
|
3863
|
+
'--always',
|
|
3864
|
+
'--abbrev=40',
|
|
3865
|
+
'--dirty',
|
|
3866
|
+
), cwd=cwd).decode().strip()
|
|
3867
|
+
|
|
3868
|
+
return dirty_rev + ('-untracked' if has_untracked else '')
|
|
3869
|
+
|
|
3870
|
+
|
|
3871
|
+
##
|
|
3872
|
+
|
|
3873
|
+
|
|
3874
|
+
_GIT_STATUS_LINE_ESCAPE_CODES: ta.Mapping[str, str] = {
|
|
3875
|
+
'\\': '\\',
|
|
3876
|
+
'"': '"',
|
|
3877
|
+
'n': '\n',
|
|
3878
|
+
't': '\t',
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3881
|
+
|
|
3882
|
+
def yield_git_status_line_fields(l: str) -> ta.Iterator[str]:
|
|
3883
|
+
def find_any(chars: str, start: int = 0) -> int:
|
|
3884
|
+
ret = -1
|
|
3885
|
+
for c in chars:
|
|
3886
|
+
if (found := l.find(c, start)) >= 0 and (ret < 0 or ret > found):
|
|
3887
|
+
ret = found
|
|
3888
|
+
return ret
|
|
3889
|
+
|
|
3890
|
+
p = 0
|
|
3891
|
+
while True:
|
|
3892
|
+
if l[p] == '"':
|
|
3893
|
+
p += 1
|
|
3894
|
+
s = []
|
|
3895
|
+
while (n := find_any('\\"', p)) > 0:
|
|
3896
|
+
if (c := l[n]) == '\\':
|
|
3897
|
+
s.append(l[p:n])
|
|
3898
|
+
s.append(_GIT_STATUS_LINE_ESCAPE_CODES[l[n + 1]])
|
|
3899
|
+
p = n + 2
|
|
3900
|
+
elif c == '"':
|
|
3901
|
+
s.append(l[p:n])
|
|
3902
|
+
p = n
|
|
3903
|
+
break
|
|
3904
|
+
else:
|
|
3905
|
+
raise ValueError(l)
|
|
3906
|
+
|
|
3907
|
+
if l[p] != '"':
|
|
3908
|
+
raise ValueError(l)
|
|
3909
|
+
|
|
3910
|
+
yield ''.join(s)
|
|
3911
|
+
|
|
3912
|
+
p += 1
|
|
3913
|
+
if p == len(l):
|
|
3914
|
+
return
|
|
3915
|
+
elif l[p] != ' ':
|
|
3916
|
+
raise ValueError(l)
|
|
3917
|
+
|
|
3918
|
+
p += 1
|
|
3919
|
+
|
|
3920
|
+
else:
|
|
3921
|
+
if (e := l.find(' ', p)) < 0:
|
|
3922
|
+
yield l[p:]
|
|
3923
|
+
return
|
|
3924
|
+
|
|
3925
|
+
yield l[p:e]
|
|
3926
|
+
p = e + 1
|
|
3927
|
+
|
|
3928
|
+
|
|
3929
|
+
"""
|
|
3930
|
+
When merge is occurring and was successful, or outside of a merge situation, X shows the status of the index and Y shows
|
|
3931
|
+
the status of the working tree:
|
|
3932
|
+
-------------------------------------------------
|
|
3933
|
+
X Y Meaning
|
|
3934
|
+
-------------------------------------------------
|
|
3935
|
+
[AMD] not updated
|
|
3936
|
+
M [ MTD] updated in index
|
|
3937
|
+
T [ MTD] type changed in index
|
|
3938
|
+
A [ MTD] added to index
|
|
3939
|
+
D deleted from index
|
|
3940
|
+
R [ MTD] renamed in index
|
|
3941
|
+
C [ MTD] copied in index
|
|
3942
|
+
[MTARC] index and work tree matches
|
|
3943
|
+
[ MTARC] M work tree changed since index
|
|
3944
|
+
[ MTARC] T type changed in work tree since index
|
|
3945
|
+
[ MTARC] D deleted in work tree
|
|
3946
|
+
R renamed in work tree
|
|
3947
|
+
C copied in work tree
|
|
3948
|
+
|
|
3949
|
+
When merge conflict has occurred and has not yet been resolved, X and Y show the state introduced by each head of the
|
|
3950
|
+
merge, relative to the common ancestor:
|
|
3951
|
+
-------------------------------------------------
|
|
3952
|
+
X Y Meaning
|
|
3953
|
+
-------------------------------------------------
|
|
3954
|
+
D D unmerged, both deleted
|
|
3955
|
+
A U unmerged, added by us
|
|
3956
|
+
U D unmerged, deleted by them
|
|
3957
|
+
U A unmerged, added by them
|
|
3958
|
+
D U unmerged, deleted by us
|
|
3959
|
+
A A unmerged, both added
|
|
3960
|
+
U U unmerged, both modified
|
|
3961
|
+
|
|
3962
|
+
When path is untracked, X and Y are always the same, since they are unknown to the index:
|
|
3963
|
+
-------------------------------------------------
|
|
3964
|
+
X Y Meaning
|
|
3965
|
+
-------------------------------------------------
|
|
3966
|
+
? ? untracked
|
|
3967
|
+
! ! ignored
|
|
3968
|
+
|
|
3969
|
+
Submodules have more state and instead report
|
|
3970
|
+
|
|
3971
|
+
- M = the submodule has a different HEAD than recorded in the index
|
|
3972
|
+
- m = the submodule has modified content
|
|
3973
|
+
- ? = the submodule has untracked files
|
|
3974
|
+
|
|
3975
|
+
This is since modified content or untracked files in a submodule cannot be added via git add in the superproject to
|
|
3976
|
+
prepare a commit. m and ? are applied recursively. For example if a nested submodule in a submodule contains an
|
|
3977
|
+
untracked file, this is reported as ? as well.
|
|
3978
|
+
""" # noqa
|
|
3979
|
+
|
|
3980
|
+
|
|
3981
|
+
class GitStatusLineState(enum.Enum):
|
|
3982
|
+
UNMODIFIED = ' '
|
|
3983
|
+
MODIFIED = 'M'
|
|
3984
|
+
FILE_TYPE_CHANGED = 'T'
|
|
3985
|
+
ADDED = 'A'
|
|
3986
|
+
DELETED = 'D'
|
|
3987
|
+
RENAMED = 'R'
|
|
3988
|
+
COPIED = 'C'
|
|
3989
|
+
UPDATED_BUT_UNMERGED = 'U'
|
|
3990
|
+
UNTRACKED = '?'
|
|
3991
|
+
IGNORED = '!'
|
|
3992
|
+
SUBMODULE_MODIFIED_CONTENT = 'm'
|
|
3993
|
+
|
|
3994
|
+
|
|
3995
|
+
_EXTRA_UNMERGED_GIT_STATUS_LINE_STATES: ta.FrozenSet[ta.Tuple[GitStatusLineState, GitStatusLineState]] = frozenset([
|
|
3996
|
+
(GitStatusLineState.ADDED, GitStatusLineState.ADDED),
|
|
3997
|
+
(GitStatusLineState.DELETED, GitStatusLineState.DELETED),
|
|
3998
|
+
])
|
|
3999
|
+
|
|
4000
|
+
|
|
4001
|
+
@dc.dataclass(frozen=True)
|
|
4002
|
+
class GitStatusLine:
|
|
4003
|
+
x: GitStatusLineState
|
|
4004
|
+
y: GitStatusLineState
|
|
4005
|
+
|
|
4006
|
+
a: str
|
|
4007
|
+
b: ta.Optional[str]
|
|
4008
|
+
|
|
4009
|
+
@property
|
|
4010
|
+
def is_unmerged(self) -> bool:
|
|
4011
|
+
return (
|
|
4012
|
+
self.x is GitStatusLineState.UPDATED_BUT_UNMERGED or
|
|
4013
|
+
self.y is GitStatusLineState.UPDATED_BUT_UNMERGED or
|
|
4014
|
+
(self.x, self.y) in _EXTRA_UNMERGED_GIT_STATUS_LINE_STATES
|
|
4015
|
+
)
|
|
4016
|
+
|
|
4017
|
+
def __repr__(self) -> str:
|
|
4018
|
+
return (
|
|
4019
|
+
f'{self.__class__.__name__}('
|
|
4020
|
+
f'x={self.x.name}, '
|
|
4021
|
+
f'y={self.y.name}, '
|
|
4022
|
+
f'a={self.a!r}' +
|
|
4023
|
+
(f', b={self.b!r}' if self.b is not None else '') +
|
|
4024
|
+
')'
|
|
4025
|
+
)
|
|
4026
|
+
|
|
4027
|
+
|
|
4028
|
+
def parse_git_status_line(l: str) -> GitStatusLine:
|
|
4029
|
+
if len(l) < 3 or l[2] != ' ':
|
|
4030
|
+
raise ValueError(l)
|
|
4031
|
+
x, y = l[0], l[1]
|
|
4032
|
+
|
|
4033
|
+
fields = list(yield_git_status_line_fields(l[3:]))
|
|
4034
|
+
if len(fields) == 1:
|
|
4035
|
+
a, b = fields[0], None
|
|
4036
|
+
elif len(fields) == 3:
|
|
4037
|
+
check_state(fields[1] == '->', l)
|
|
4038
|
+
a, b = fields[0], fields[2]
|
|
4039
|
+
else:
|
|
4040
|
+
raise ValueError(l)
|
|
4041
|
+
|
|
4042
|
+
return GitStatusLine(
|
|
4043
|
+
GitStatusLineState(x),
|
|
4044
|
+
GitStatusLineState(y),
|
|
4045
|
+
a,
|
|
4046
|
+
b,
|
|
4047
|
+
)
|
|
4048
|
+
|
|
4049
|
+
|
|
4050
|
+
class GitStatus(ta.Sequence[GitStatusLine]):
|
|
4051
|
+
def __init__(self, lines: ta.Iterable[GitStatusLine]) -> None:
|
|
4052
|
+
super().__init__()
|
|
4053
|
+
|
|
4054
|
+
self._lst = list(lines)
|
|
4055
|
+
|
|
4056
|
+
by_x: ta.Dict[GitStatusLineState, list[GitStatusLine]] = {}
|
|
4057
|
+
by_y: ta.Dict[GitStatusLineState, list[GitStatusLine]] = {}
|
|
4058
|
+
|
|
4059
|
+
by_a: ta.Dict[str, GitStatusLine] = {}
|
|
4060
|
+
by_b: ta.Dict[str, GitStatusLine] = {}
|
|
4061
|
+
|
|
4062
|
+
for l in self._lst:
|
|
4063
|
+
by_x.setdefault(l.x, []).append(l)
|
|
4064
|
+
by_y.setdefault(l.y, []).append(l)
|
|
4065
|
+
|
|
4066
|
+
if l.a in by_a:
|
|
4067
|
+
raise KeyError(l.a)
|
|
4068
|
+
by_a[l.a] = l
|
|
4069
|
+
|
|
4070
|
+
if l.b is not None:
|
|
4071
|
+
if l.b in by_b:
|
|
4072
|
+
raise KeyError(l.b)
|
|
4073
|
+
by_b[l.b] = l
|
|
4074
|
+
|
|
4075
|
+
self._by_x = by_x
|
|
4076
|
+
self._by_y = by_y
|
|
4077
|
+
|
|
4078
|
+
self._by_a = by_a
|
|
4079
|
+
self._by_b = by_b
|
|
4080
|
+
|
|
4081
|
+
self._has_unmerged = any(l.is_unmerged for l in self)
|
|
4082
|
+
|
|
4083
|
+
#
|
|
4084
|
+
|
|
4085
|
+
def __iter__(self) -> ta.Iterator[GitStatusLine]:
|
|
4086
|
+
return iter(self._lst)
|
|
4087
|
+
|
|
4088
|
+
def __getitem__(self, index):
|
|
4089
|
+
return self._lst[index]
|
|
4090
|
+
|
|
4091
|
+
def __len__(self) -> int:
|
|
4092
|
+
return len(self._lst)
|
|
4093
|
+
|
|
4094
|
+
#
|
|
4095
|
+
|
|
4096
|
+
@property
|
|
4097
|
+
def by_x(self) -> ta.Mapping[GitStatusLineState, ta.Sequence[GitStatusLine]]:
|
|
4098
|
+
return self._by_x
|
|
4099
|
+
|
|
4100
|
+
@property
|
|
4101
|
+
def by_y(self) -> ta.Mapping[GitStatusLineState, ta.Sequence[GitStatusLine]]:
|
|
4102
|
+
return self._by_y
|
|
4103
|
+
|
|
4104
|
+
@property
|
|
4105
|
+
def by_a(self) -> ta.Mapping[str, GitStatusLine]:
|
|
4106
|
+
return self._by_a
|
|
4107
|
+
|
|
4108
|
+
@property
|
|
4109
|
+
def by_b(self) -> ta.Mapping[str, GitStatusLine]:
|
|
4110
|
+
return self._by_b
|
|
4111
|
+
|
|
4112
|
+
#
|
|
4113
|
+
|
|
4114
|
+
@property
|
|
4115
|
+
def has_unmerged(self) -> bool:
|
|
4116
|
+
return self._has_unmerged
|
|
4117
|
+
|
|
4118
|
+
@property
|
|
4119
|
+
def has_staged(self) -> bool:
|
|
4120
|
+
return any(l.x != GitStatusLineState.UNMODIFIED for l in self._lst)
|
|
4121
|
+
|
|
4122
|
+
@property
|
|
4123
|
+
def has_dirty(self) -> bool:
|
|
4124
|
+
return any(l.y != GitStatusLineState.UNMODIFIED for l in self._lst)
|
|
4125
|
+
|
|
4126
|
+
|
|
4127
|
+
def parse_git_status(s: str) -> GitStatus:
|
|
4128
|
+
return GitStatus(parse_git_status_line(l) for l in s.splitlines())
|
|
4129
|
+
|
|
4130
|
+
|
|
4131
|
+
def get_git_status(
|
|
4132
|
+
*,
|
|
4133
|
+
cwd: ta.Optional[str] = None,
|
|
4134
|
+
ignore_submodules: bool = False,
|
|
4135
|
+
verbose: bool = False,
|
|
4136
|
+
) -> GitStatus:
|
|
4137
|
+
if cwd is None:
|
|
4138
|
+
cwd = os.getcwd()
|
|
4139
|
+
|
|
4140
|
+
proc = subprocess.run( # type: ignore
|
|
4141
|
+
subprocess_maybe_shell_wrap_exec(
|
|
4142
|
+
'git',
|
|
4143
|
+
'status',
|
|
4144
|
+
'--porcelain=v1',
|
|
4145
|
+
*(['--ignore-submodules'] if ignore_submodules else []),
|
|
4146
|
+
),
|
|
4147
|
+
cwd=cwd,
|
|
4148
|
+
stdout=subprocess.PIPE,
|
|
4149
|
+
**(dict(stderr=subprocess.PIPE) if not verbose else {}),
|
|
4150
|
+
check=True,
|
|
4151
|
+
)
|
|
4152
|
+
|
|
4153
|
+
return parse_git_status(proc.stdout.decode()) # noqa
|
|
4154
|
+
|
|
4155
|
+
|
|
4156
|
+
########################################
|
|
4157
|
+
# ../../interp/inspect.py
|
|
4158
|
+
|
|
4159
|
+
|
|
4160
|
+
@dc.dataclass(frozen=True)
|
|
4161
|
+
class InterpInspection:
|
|
4162
|
+
exe: str
|
|
4163
|
+
version: Version
|
|
4164
|
+
|
|
4165
|
+
version_str: str
|
|
4166
|
+
config_vars: ta.Mapping[str, str]
|
|
4167
|
+
prefix: str
|
|
4168
|
+
base_prefix: str
|
|
4169
|
+
|
|
4170
|
+
@property
|
|
4171
|
+
def opts(self) -> InterpOpts:
|
|
4172
|
+
return InterpOpts(
|
|
4173
|
+
threaded=bool(self.config_vars.get('Py_GIL_DISABLED')),
|
|
4174
|
+
debug=bool(self.config_vars.get('Py_DEBUG')),
|
|
4175
|
+
)
|
|
4176
|
+
|
|
4177
|
+
@property
|
|
4178
|
+
def iv(self) -> InterpVersion:
|
|
4179
|
+
return InterpVersion(
|
|
4180
|
+
version=self.version,
|
|
4181
|
+
opts=self.opts,
|
|
4182
|
+
)
|
|
4183
|
+
|
|
4184
|
+
@property
|
|
4185
|
+
def is_venv(self) -> bool:
|
|
4186
|
+
return self.prefix != self.base_prefix
|
|
4187
|
+
|
|
4188
|
+
|
|
4189
|
+
class InterpInspector:
|
|
4016
4190
|
|
|
4017
4191
|
def __init__(self) -> None:
|
|
4018
4192
|
super().__init__()
|
|
@@ -4076,1180 +4250,1300 @@ INTERP_INSPECTOR = InterpInspector()
|
|
|
4076
4250
|
|
|
4077
4251
|
|
|
4078
4252
|
########################################
|
|
4079
|
-
#
|
|
4253
|
+
# ../../interp/providers.py
|
|
4080
4254
|
"""
|
|
4081
4255
|
TODO:
|
|
4082
|
-
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4256
|
+
- backends
|
|
4257
|
+
- local builds
|
|
4258
|
+
- deadsnakes?
|
|
4259
|
+
- uv
|
|
4260
|
+
- loose versions
|
|
4261
|
+
"""
|
|
4085
4262
|
|
|
4086
|
-
** NOTE **
|
|
4087
|
-
setuptools now (2024/09/02) has experimental support for extensions in pure pyproject.toml - but we still want a
|
|
4088
|
-
separate '-cext' package
|
|
4089
|
-
https://setuptools.pypa.io/en/latest/userguide/ext_modules.html
|
|
4090
|
-
https://github.com/pypa/setuptools/commit/1a9d87308dc0d8aabeaae0dce989b35dfb7699f0#diff-61d113525e9cc93565799a4bb8b34a68e2945b8a3f7d90c81380614a4ea39542R7-R8
|
|
4091
4263
|
|
|
4092
|
-
|
|
4264
|
+
##
|
|
4093
4265
|
|
|
4094
|
-
https://setuptools.pypa.io/en/latest/references/keywords.html
|
|
4095
|
-
https://packaging.python.org/en/latest/specifications/pyproject-toml
|
|
4096
4266
|
|
|
4097
|
-
|
|
4098
|
-
|
|
4267
|
+
class InterpProvider(abc.ABC):
|
|
4268
|
+
name: ta.ClassVar[str]
|
|
4099
4269
|
|
|
4100
|
-
|
|
4270
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
|
4271
|
+
super().__init_subclass__(**kwargs)
|
|
4272
|
+
if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
|
|
4273
|
+
sfx = 'InterpProvider'
|
|
4274
|
+
if not cls.__name__.endswith(sfx):
|
|
4275
|
+
raise NameError(cls)
|
|
4276
|
+
setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
|
|
4101
4277
|
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
""" # noqa
|
|
4278
|
+
@abc.abstractmethod
|
|
4279
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
4280
|
+
raise NotImplementedError
|
|
4106
4281
|
|
|
4282
|
+
@abc.abstractmethod
|
|
4283
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
4284
|
+
raise NotImplementedError
|
|
4107
4285
|
|
|
4108
|
-
|
|
4286
|
+
def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
4287
|
+
return []
|
|
4109
4288
|
|
|
4289
|
+
def install_version(self, version: InterpVersion) -> Interp:
|
|
4290
|
+
raise TypeError
|
|
4110
4291
|
|
|
4111
|
-
class BasePyprojectPackageGenerator(abc.ABC):
|
|
4112
|
-
def __init__(
|
|
4113
|
-
self,
|
|
4114
|
-
dir_name: str,
|
|
4115
|
-
pkgs_root: str,
|
|
4116
|
-
*,
|
|
4117
|
-
pkg_suffix: str = '',
|
|
4118
|
-
) -> None:
|
|
4119
|
-
super().__init__()
|
|
4120
|
-
self._dir_name = dir_name
|
|
4121
|
-
self._pkgs_root = pkgs_root
|
|
4122
|
-
self._pkg_suffix = pkg_suffix
|
|
4123
4292
|
|
|
4124
|
-
|
|
4293
|
+
##
|
|
4125
4294
|
|
|
4295
|
+
|
|
4296
|
+
class RunningInterpProvider(InterpProvider):
|
|
4126
4297
|
@cached_nullary
|
|
4127
|
-
def
|
|
4128
|
-
return
|
|
4298
|
+
def version(self) -> InterpVersion:
|
|
4299
|
+
return InterpInspector.running().iv
|
|
4129
4300
|
|
|
4130
|
-
|
|
4301
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
4302
|
+
return [self.version()]
|
|
4131
4303
|
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4304
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
4305
|
+
if version != self.version():
|
|
4306
|
+
raise KeyError(version)
|
|
4307
|
+
return Interp(
|
|
4308
|
+
exe=sys.executable,
|
|
4309
|
+
version=self.version(),
|
|
4310
|
+
)
|
|
4139
4311
|
|
|
4140
|
-
#
|
|
4141
4312
|
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4313
|
+
########################################
|
|
4314
|
+
# ../../revisions.py
|
|
4315
|
+
"""
|
|
4316
|
+
TODO:
|
|
4317
|
+
- omlish-lite, move to pyproject/
|
|
4318
|
+
- vendor-lite wheel.wheelfile
|
|
4319
|
+
"""
|
|
4146
4320
|
|
|
4147
|
-
def _write_git_ignore(self) -> None:
|
|
4148
|
-
with open(os.path.join(self._pkg_dir(), '.gitignore'), 'w') as f:
|
|
4149
|
-
f.write('\n'.join(self._GIT_IGNORE))
|
|
4150
4321
|
|
|
4151
|
-
|
|
4322
|
+
##
|
|
4152
4323
|
|
|
4153
|
-
def _symlink_source_dir(self) -> None:
|
|
4154
|
-
os.symlink(
|
|
4155
|
-
os.path.relpath(self._dir_name, self._pkg_dir()),
|
|
4156
|
-
os.path.join(self._pkg_dir(), self._dir_name),
|
|
4157
|
-
)
|
|
4158
4324
|
|
|
4159
|
-
|
|
4325
|
+
class GitRevisionAdder:
|
|
4326
|
+
def __init__(
|
|
4327
|
+
self,
|
|
4328
|
+
revision: ta.Optional[str] = None,
|
|
4329
|
+
output_suffix: ta.Optional[str] = None,
|
|
4330
|
+
) -> None:
|
|
4331
|
+
super().__init__()
|
|
4332
|
+
self._given_revision = revision
|
|
4333
|
+
self._output_suffix = output_suffix
|
|
4160
4334
|
|
|
4161
4335
|
@cached_nullary
|
|
4162
|
-
def
|
|
4163
|
-
|
|
4336
|
+
def revision(self) -> str:
|
|
4337
|
+
if self._given_revision is not None:
|
|
4338
|
+
return self._given_revision
|
|
4339
|
+
return check_non_empty_str(get_git_revision())
|
|
4164
4340
|
|
|
4165
|
-
|
|
4166
|
-
def setuptools_cls(self) -> type:
|
|
4167
|
-
return self.about().Setuptools
|
|
4341
|
+
REVISION_ATTR = '__revision__'
|
|
4168
4342
|
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4343
|
+
def add_to_contents(self, dct: ta.Dict[str, bytes]) -> bool:
|
|
4344
|
+
changed = False
|
|
4345
|
+
for n in dct:
|
|
4346
|
+
if not n.endswith('__about__.py'):
|
|
4347
|
+
continue
|
|
4348
|
+
src = dct[n].decode('utf-8')
|
|
4349
|
+
lines = src.splitlines(keepends=True)
|
|
4350
|
+
for i, l in enumerate(lines):
|
|
4351
|
+
if l != f'{self.REVISION_ATTR} = None\n':
|
|
4175
4352
|
continue
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
def _move_dict_key(
|
|
4181
|
-
sd: ta.Dict[str, ta.Any],
|
|
4182
|
-
sk: str,
|
|
4183
|
-
dd: ta.Dict[str, ta.Any],
|
|
4184
|
-
dk: str,
|
|
4185
|
-
) -> None:
|
|
4186
|
-
if sk in sd:
|
|
4187
|
-
dd[dk] = sd.pop(sk)
|
|
4353
|
+
lines[i] = f"{self.REVISION_ATTR} = '{self.revision()}'\n"
|
|
4354
|
+
changed = True
|
|
4355
|
+
dct[n] = ''.join(lines).encode('utf-8')
|
|
4356
|
+
return changed
|
|
4188
4357
|
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4358
|
+
def add_to_wheel(self, f: str) -> None:
|
|
4359
|
+
if not f.endswith('.whl'):
|
|
4360
|
+
raise Exception(f)
|
|
4361
|
+
log.info('Scanning wheel %s', f)
|
|
4193
4362
|
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4363
|
+
zis: ta.Dict[str, zipfile.ZipInfo] = {}
|
|
4364
|
+
dct: ta.Dict[str, bytes] = {}
|
|
4365
|
+
with WheelFile(f) as wf:
|
|
4366
|
+
for zi in wf.filelist:
|
|
4367
|
+
if zi.filename == wf.record_path:
|
|
4368
|
+
continue
|
|
4369
|
+
zis[zi.filename] = zi
|
|
4370
|
+
dct[zi.filename] = wf.read(zi.filename)
|
|
4199
4371
|
|
|
4200
|
-
|
|
4372
|
+
if self.add_to_contents(dct):
|
|
4373
|
+
of = f[:-4] + (self._output_suffix or '') + '.whl'
|
|
4374
|
+
log.info('Repacking wheel %s', of)
|
|
4375
|
+
with WheelFile(of, 'w') as wf:
|
|
4376
|
+
for n, d in dct.items():
|
|
4377
|
+
log.info('Adding zipinfo %s', n)
|
|
4378
|
+
wf.writestr(zis[n], d)
|
|
4201
4379
|
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4380
|
+
def add_to_tgz(self, f: str) -> None:
|
|
4381
|
+
if not f.endswith('.tar.gz'):
|
|
4382
|
+
raise Exception(f)
|
|
4383
|
+
log.info('Scanning tgz %s', f)
|
|
4205
4384
|
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4385
|
+
tis: ta.Dict[str, tarfile.TarInfo] = {}
|
|
4386
|
+
dct: ta.Dict[str, bytes] = {}
|
|
4387
|
+
with tarfile.open(f, 'r:gz') as tf:
|
|
4388
|
+
for ti in tf:
|
|
4389
|
+
tis[ti.name] = ti
|
|
4390
|
+
if ti.type == tarfile.REGTYPE:
|
|
4391
|
+
with tf.extractfile(ti.name) as tif: # type: ignore
|
|
4392
|
+
dct[ti.name] = tif.read()
|
|
4210
4393
|
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
continue
|
|
4222
|
-
if l.startswith('!'):
|
|
4223
|
-
exc.append(os.path.join(rp, l[1:]))
|
|
4394
|
+
if self.add_to_contents(dct):
|
|
4395
|
+
of = f[:-7] + (self._output_suffix or '') + '.tar.gz'
|
|
4396
|
+
log.info('Repacking tgz %s', of)
|
|
4397
|
+
with tarfile.open(of, 'w:gz') as tf:
|
|
4398
|
+
for n, ti in tis.items():
|
|
4399
|
+
log.info('Adding tarinfo %s', n)
|
|
4400
|
+
if n in dct:
|
|
4401
|
+
data = dct[n]
|
|
4402
|
+
ti.size = len(data)
|
|
4403
|
+
fo = io.BytesIO(data)
|
|
4224
4404
|
else:
|
|
4225
|
-
|
|
4405
|
+
fo = None
|
|
4406
|
+
tf.addfile(ti, fileobj=fo)
|
|
4226
4407
|
|
|
4227
|
-
|
|
4408
|
+
EXTS = ('.tar.gz', '.whl')
|
|
4228
4409
|
|
|
4229
|
-
|
|
4410
|
+
def add_to_file(self, f: str) -> None:
|
|
4411
|
+
if f.endswith('.whl'):
|
|
4412
|
+
self.add_to_wheel(f)
|
|
4230
4413
|
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
raise NotImplementedError
|
|
4414
|
+
elif f.endswith('.tar.gz'):
|
|
4415
|
+
self.add_to_tgz(f)
|
|
4234
4416
|
|
|
4235
|
-
|
|
4417
|
+
def add_to(self, tgt: str) -> None:
|
|
4418
|
+
log.info('Using revision %s', self.revision())
|
|
4236
4419
|
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
'README.rst',
|
|
4240
|
-
]
|
|
4420
|
+
if os.path.isfile(tgt):
|
|
4421
|
+
self.add_to_file(tgt)
|
|
4241
4422
|
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4423
|
+
elif os.path.isdir(tgt):
|
|
4424
|
+
for dp, dns, fns in os.walk(tgt): # noqa
|
|
4425
|
+
for f in fns:
|
|
4426
|
+
if any(f.endswith(ext) for ext in self.EXTS):
|
|
4427
|
+
self.add_to_file(os.path.join(dp, f))
|
|
4246
4428
|
|
|
4247
|
-
#
|
|
4248
4429
|
|
|
4249
|
-
|
|
4250
|
-
return []
|
|
4430
|
+
#
|
|
4251
4431
|
|
|
4252
|
-
#
|
|
4253
4432
|
|
|
4254
|
-
|
|
4255
|
-
|
|
4433
|
+
########################################
|
|
4434
|
+
# ../../interp/pyenv.py
|
|
4435
|
+
"""
|
|
4436
|
+
TODO:
|
|
4437
|
+
- custom tags
|
|
4438
|
+
- 'aliases'
|
|
4439
|
+
- https://github.com/pyenv/pyenv/pull/2966
|
|
4440
|
+
- https://github.com/pyenv/pyenv/issues/218 (lol)
|
|
4441
|
+
- probably need custom (temp?) definition file
|
|
4442
|
+
- *or* python-build directly just into the versions dir?
|
|
4443
|
+
- optionally install / upgrade pyenv itself
|
|
4444
|
+
- new vers dont need these custom mac opts, only run on old vers
|
|
4445
|
+
"""
|
|
4256
4446
|
|
|
4257
|
-
self._pkg_dir()
|
|
4258
|
-
self._write_git_ignore()
|
|
4259
|
-
self._symlink_source_dir()
|
|
4260
|
-
self._write_file_contents()
|
|
4261
|
-
self._symlink_standard_files()
|
|
4262
4447
|
|
|
4263
|
-
|
|
4448
|
+
##
|
|
4264
4449
|
|
|
4265
|
-
#
|
|
4266
4450
|
|
|
4267
|
-
|
|
4268
|
-
class BuildOpts:
|
|
4269
|
-
add_revision: bool = False
|
|
4270
|
-
test: bool = False
|
|
4451
|
+
class Pyenv:
|
|
4271
4452
|
|
|
4272
|
-
def
|
|
4453
|
+
def __init__(
|
|
4273
4454
|
self,
|
|
4274
|
-
|
|
4275
|
-
|
|
4455
|
+
*,
|
|
4456
|
+
root: ta.Optional[str] = None,
|
|
4276
4457
|
) -> None:
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
'-m',
|
|
4280
|
-
'build',
|
|
4281
|
-
cwd=self._pkg_dir(),
|
|
4282
|
-
)
|
|
4458
|
+
if root is not None and not (isinstance(root, str) and root):
|
|
4459
|
+
raise ValueError(f'pyenv_root: {root!r}')
|
|
4283
4460
|
|
|
4284
|
-
|
|
4461
|
+
super().__init__()
|
|
4285
4462
|
|
|
4286
|
-
|
|
4287
|
-
GitRevisionAdder().add_to(dist_dir)
|
|
4463
|
+
self._root_kw = root
|
|
4288
4464
|
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4465
|
+
@cached_nullary
|
|
4466
|
+
def root(self) -> ta.Optional[str]:
|
|
4467
|
+
if self._root_kw is not None:
|
|
4468
|
+
return self._root_kw
|
|
4292
4469
|
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
'-m', 'venv',
|
|
4296
|
-
'test-install',
|
|
4297
|
-
cwd=tmp_dir,
|
|
4298
|
-
)
|
|
4470
|
+
if shutil.which('pyenv'):
|
|
4471
|
+
return subprocess_check_output_str('pyenv', 'root')
|
|
4299
4472
|
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
'install',
|
|
4304
|
-
os.path.abspath(os.path.join(dist_dir, fn)),
|
|
4305
|
-
cwd=tmp_dir,
|
|
4306
|
-
)
|
|
4473
|
+
d = os.path.expanduser('~/.pyenv')
|
|
4474
|
+
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
|
4475
|
+
return d
|
|
4307
4476
|
|
|
4308
|
-
|
|
4309
|
-
for fn in os.listdir(dist_dir):
|
|
4310
|
-
shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(output_dir, fn))
|
|
4477
|
+
return None
|
|
4311
4478
|
|
|
4479
|
+
@cached_nullary
|
|
4480
|
+
def exe(self) -> str:
|
|
4481
|
+
return os.path.join(check_not_none(self.root()), 'bin', 'pyenv')
|
|
4312
4482
|
|
|
4313
|
-
|
|
4483
|
+
def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
|
|
4484
|
+
if (root := self.root()) is None:
|
|
4485
|
+
return []
|
|
4486
|
+
ret = []
|
|
4487
|
+
vp = os.path.join(root, 'versions')
|
|
4488
|
+
if os.path.isdir(vp):
|
|
4489
|
+
for dn in os.listdir(vp):
|
|
4490
|
+
ep = os.path.join(vp, dn, 'bin', 'python')
|
|
4491
|
+
if not os.path.isfile(ep):
|
|
4492
|
+
continue
|
|
4493
|
+
ret.append((dn, ep))
|
|
4494
|
+
return ret
|
|
4314
4495
|
|
|
4496
|
+
def installable_versions(self) -> ta.List[str]:
|
|
4497
|
+
if self.root() is None:
|
|
4498
|
+
return []
|
|
4499
|
+
ret = []
|
|
4500
|
+
s = subprocess_check_output_str(self.exe(), 'install', '--list')
|
|
4501
|
+
for l in s.splitlines():
|
|
4502
|
+
if not l.startswith(' '):
|
|
4503
|
+
continue
|
|
4504
|
+
l = l.strip()
|
|
4505
|
+
if not l:
|
|
4506
|
+
continue
|
|
4507
|
+
ret.append(l)
|
|
4508
|
+
return ret
|
|
4315
4509
|
|
|
4316
|
-
|
|
4510
|
+
def update(self) -> bool:
|
|
4511
|
+
if (root := self.root()) is None:
|
|
4512
|
+
return False
|
|
4513
|
+
if not os.path.isdir(os.path.join(root, '.git')):
|
|
4514
|
+
return False
|
|
4515
|
+
subprocess_check_call('git', 'pull', cwd=root)
|
|
4516
|
+
return True
|
|
4317
4517
|
|
|
4318
|
-
#
|
|
4319
4518
|
|
|
4320
|
-
|
|
4321
|
-
class FileContents:
|
|
4322
|
-
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
4323
|
-
manifest_in: ta.Optional[ta.Sequence[str]]
|
|
4519
|
+
##
|
|
4324
4520
|
|
|
4325
|
-
@cached_nullary
|
|
4326
|
-
def file_contents(self) -> FileContents:
|
|
4327
|
-
specs = self.build_specs()
|
|
4328
4521
|
|
|
4329
|
-
|
|
4522
|
+
@dc.dataclass(frozen=True)
|
|
4523
|
+
class PyenvInstallOpts:
|
|
4524
|
+
opts: ta.Sequence[str] = ()
|
|
4525
|
+
conf_opts: ta.Sequence[str] = ()
|
|
4526
|
+
cflags: ta.Sequence[str] = ()
|
|
4527
|
+
ldflags: ta.Sequence[str] = ()
|
|
4528
|
+
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
|
4330
4529
|
|
|
4331
|
-
|
|
4530
|
+
def merge(self, *others: 'PyenvInstallOpts') -> 'PyenvInstallOpts':
|
|
4531
|
+
return PyenvInstallOpts(
|
|
4532
|
+
opts=list(itertools.chain.from_iterable(o.opts for o in [self, *others])),
|
|
4533
|
+
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
|
4534
|
+
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
|
4535
|
+
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
|
4536
|
+
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
|
4537
|
+
)
|
|
4332
4538
|
|
|
4333
|
-
pyp_dct['build-system'] = {
|
|
4334
|
-
'requires': ['setuptools'],
|
|
4335
|
-
'build-backend': 'setuptools.build_meta',
|
|
4336
|
-
}
|
|
4337
4539
|
|
|
4338
|
-
|
|
4339
|
-
|
|
4540
|
+
# TODO: https://github.com/pyenv/pyenv/blob/master/plugins/python-build/README.md#building-for-maximum-performance
|
|
4541
|
+
DEFAULT_PYENV_INSTALL_OPTS = PyenvInstallOpts(
|
|
4542
|
+
opts=[
|
|
4543
|
+
'-s',
|
|
4544
|
+
'-v',
|
|
4545
|
+
'-k',
|
|
4546
|
+
],
|
|
4547
|
+
conf_opts=[
|
|
4548
|
+
'--enable-loadable-sqlite-extensions',
|
|
4340
4549
|
|
|
4341
|
-
|
|
4550
|
+
# '--enable-shared',
|
|
4342
4551
|
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
pyp_dct[extrask] = {
|
|
4346
|
-
'all': [
|
|
4347
|
-
e
|
|
4348
|
-
for lst in extras.values()
|
|
4349
|
-
for e in lst
|
|
4350
|
-
],
|
|
4351
|
-
**extras,
|
|
4352
|
-
}
|
|
4552
|
+
'--enable-optimizations',
|
|
4553
|
+
'--with-lto',
|
|
4353
4554
|
|
|
4354
|
-
|
|
4355
|
-
pyp_dct['project.entry-points'] = {TomlWriter.Literal(f"'{k}'"): v for k, v in eps.items()} # type: ignore # noqa
|
|
4555
|
+
# '--enable-profiling', # ?
|
|
4356
4556
|
|
|
4357
|
-
|
|
4358
|
-
|
|
4557
|
+
# '--enable-ipv6', # ?
|
|
4558
|
+
],
|
|
4559
|
+
cflags=[
|
|
4560
|
+
# '-march=native',
|
|
4561
|
+
# '-mtune=native',
|
|
4562
|
+
],
|
|
4563
|
+
)
|
|
4359
4564
|
|
|
4360
|
-
|
|
4565
|
+
DEBUG_PYENV_INSTALL_OPTS = PyenvInstallOpts(opts=['-g'])
|
|
4361
4566
|
|
|
4362
|
-
|
|
4567
|
+
THREADED_PYENV_INSTALL_OPTS = PyenvInstallOpts(conf_opts=['--disable-gil'])
|
|
4363
4568
|
|
|
4364
|
-
st = dict(specs.setuptools)
|
|
4365
|
-
pyp_dct['tool.setuptools'] = st
|
|
4366
4569
|
|
|
4367
|
-
|
|
4570
|
+
#
|
|
4368
4571
|
|
|
4369
|
-
#
|
|
4370
4572
|
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
# }
|
|
4573
|
+
class PyenvInstallOptsProvider(abc.ABC):
|
|
4574
|
+
@abc.abstractmethod
|
|
4575
|
+
def opts(self) -> PyenvInstallOpts:
|
|
4576
|
+
raise NotImplementedError
|
|
4376
4577
|
|
|
4377
|
-
fp = dict(st.pop('find_packages', {}))
|
|
4378
4578
|
|
|
4379
|
-
|
|
4579
|
+
class LinuxPyenvInstallOpts(PyenvInstallOptsProvider):
|
|
4580
|
+
def opts(self) -> PyenvInstallOpts:
|
|
4581
|
+
return PyenvInstallOpts()
|
|
4380
4582
|
|
|
4381
|
-
#
|
|
4382
4583
|
|
|
4383
|
-
|
|
4384
|
-
# package_data = {
|
|
4385
|
-
# '*': [
|
|
4386
|
-
# '*.c',
|
|
4387
|
-
# '*.cc',
|
|
4388
|
-
# '*.h',
|
|
4389
|
-
# '.manifests.json',
|
|
4390
|
-
# 'LICENSE',
|
|
4391
|
-
# ],
|
|
4392
|
-
# }
|
|
4584
|
+
class DarwinPyenvInstallOpts(PyenvInstallOptsProvider):
|
|
4393
4585
|
|
|
4394
|
-
|
|
4395
|
-
|
|
4586
|
+
@cached_nullary
|
|
4587
|
+
def framework_opts(self) -> PyenvInstallOpts:
|
|
4588
|
+
return PyenvInstallOpts(conf_opts=['--enable-framework'])
|
|
4396
4589
|
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
if cpd.exc:
|
|
4401
|
-
epd['*'] = [*epd.get('*', []), *sorted(set(cpd.exc))]
|
|
4590
|
+
@cached_nullary
|
|
4591
|
+
def has_brew(self) -> bool:
|
|
4592
|
+
return shutil.which('brew') is not None
|
|
4402
4593
|
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4594
|
+
BREW_DEPS: ta.Sequence[str] = [
|
|
4595
|
+
'openssl',
|
|
4596
|
+
'readline',
|
|
4597
|
+
'sqlite3',
|
|
4598
|
+
'zlib',
|
|
4599
|
+
]
|
|
4407
4600
|
|
|
4408
|
-
|
|
4601
|
+
@cached_nullary
|
|
4602
|
+
def brew_deps_opts(self) -> PyenvInstallOpts:
|
|
4603
|
+
cflags = []
|
|
4604
|
+
ldflags = []
|
|
4605
|
+
for dep in self.BREW_DEPS:
|
|
4606
|
+
dep_prefix = subprocess_check_output_str('brew', '--prefix', dep)
|
|
4607
|
+
cflags.append(f'-I{dep_prefix}/include')
|
|
4608
|
+
ldflags.append(f'-L{dep_prefix}/lib')
|
|
4609
|
+
return PyenvInstallOpts(
|
|
4610
|
+
cflags=cflags,
|
|
4611
|
+
ldflags=ldflags,
|
|
4612
|
+
)
|
|
4409
4613
|
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4614
|
+
@cached_nullary
|
|
4615
|
+
def brew_tcl_opts(self) -> PyenvInstallOpts:
|
|
4616
|
+
if subprocess_try_output('brew', '--prefix', 'tcl-tk') is None:
|
|
4617
|
+
return PyenvInstallOpts()
|
|
4414
4618
|
|
|
4415
|
-
|
|
4619
|
+
tcl_tk_prefix = subprocess_check_output_str('brew', '--prefix', 'tcl-tk')
|
|
4620
|
+
tcl_tk_ver_str = subprocess_check_output_str('brew', 'ls', '--versions', 'tcl-tk')
|
|
4621
|
+
tcl_tk_ver = '.'.join(tcl_tk_ver_str.split()[1].split('.')[:2])
|
|
4416
4622
|
|
|
4417
|
-
|
|
4623
|
+
return PyenvInstallOpts(conf_opts=[
|
|
4624
|
+
f"--with-tcltk-includes='-I{tcl_tk_prefix}/include'",
|
|
4625
|
+
f"--with-tcltk-libs='-L{tcl_tk_prefix}/lib -ltcl{tcl_tk_ver} -ltk{tcl_tk_ver}'",
|
|
4626
|
+
])
|
|
4418
4627
|
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4628
|
+
# @cached_nullary
|
|
4629
|
+
# def brew_ssl_opts(self) -> PyenvInstallOpts:
|
|
4630
|
+
# pkg_config_path = subprocess_check_output_str('brew', '--prefix', 'openssl')
|
|
4631
|
+
# if 'PKG_CONFIG_PATH' in os.environ:
|
|
4632
|
+
# pkg_config_path += ':' + os.environ['PKG_CONFIG_PATH']
|
|
4633
|
+
# return PyenvInstallOpts(env={'PKG_CONFIG_PATH': pkg_config_path})
|
|
4634
|
+
|
|
4635
|
+
def opts(self) -> PyenvInstallOpts:
|
|
4636
|
+
return PyenvInstallOpts().merge(
|
|
4637
|
+
self.framework_opts(),
|
|
4638
|
+
self.brew_deps_opts(),
|
|
4639
|
+
self.brew_tcl_opts(),
|
|
4640
|
+
# self.brew_ssl_opts(),
|
|
4422
4641
|
)
|
|
4423
4642
|
|
|
4424
|
-
def _write_file_contents(self) -> None:
|
|
4425
|
-
fc = self.file_contents()
|
|
4426
4643
|
|
|
4427
|
-
|
|
4428
|
-
|
|
4644
|
+
PLATFORM_PYENV_INSTALL_OPTS: ta.Dict[str, PyenvInstallOptsProvider] = {
|
|
4645
|
+
'darwin': DarwinPyenvInstallOpts(),
|
|
4646
|
+
'linux': LinuxPyenvInstallOpts(),
|
|
4647
|
+
}
|
|
4429
4648
|
|
|
4430
|
-
if fc.manifest_in:
|
|
4431
|
-
with open(os.path.join(self._pkg_dir(), 'MANIFEST.in'), 'w') as f:
|
|
4432
|
-
f.write('\n'.join(fc.manifest_in)) # noqa
|
|
4433
4649
|
|
|
4434
|
-
|
|
4650
|
+
##
|
|
4435
4651
|
|
|
4436
|
-
@cached_nullary
|
|
4437
|
-
def children(self) -> ta.Sequence[BasePyprojectPackageGenerator]:
|
|
4438
|
-
out: ta.List[BasePyprojectPackageGenerator] = []
|
|
4439
4652
|
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
))
|
|
4446
|
-
|
|
4447
|
-
if self.build_specs().pyproject.get('cli_scripts'):
|
|
4448
|
-
out.append(_PyprojectCliPackageGenerator(
|
|
4449
|
-
self._dir_name,
|
|
4450
|
-
self._pkgs_root,
|
|
4451
|
-
pkg_suffix='-cli',
|
|
4452
|
-
))
|
|
4653
|
+
class PyenvVersionInstaller:
|
|
4654
|
+
"""
|
|
4655
|
+
Messy: can install freethreaded build with a 't' suffixed version str _or_ by THREADED_PYENV_INSTALL_OPTS - need
|
|
4656
|
+
latter to build custom interp with ft, need former to use canned / blessed interps. Muh.
|
|
4657
|
+
"""
|
|
4453
4658
|
|
|
4454
|
-
|
|
4659
|
+
def __init__(
|
|
4660
|
+
self,
|
|
4661
|
+
version: str,
|
|
4662
|
+
opts: ta.Optional[PyenvInstallOpts] = None,
|
|
4663
|
+
interp_opts: InterpOpts = InterpOpts(),
|
|
4664
|
+
*,
|
|
4665
|
+
install_name: ta.Optional[str] = None,
|
|
4666
|
+
no_default_opts: bool = False,
|
|
4667
|
+
pyenv: Pyenv = Pyenv(),
|
|
4668
|
+
) -> None:
|
|
4669
|
+
super().__init__()
|
|
4455
4670
|
|
|
4671
|
+
if no_default_opts:
|
|
4672
|
+
if opts is None:
|
|
4673
|
+
opts = PyenvInstallOpts()
|
|
4674
|
+
else:
|
|
4675
|
+
lst = [opts if opts is not None else DEFAULT_PYENV_INSTALL_OPTS]
|
|
4676
|
+
if interp_opts.debug:
|
|
4677
|
+
lst.append(DEBUG_PYENV_INSTALL_OPTS)
|
|
4678
|
+
if interp_opts.threaded:
|
|
4679
|
+
lst.append(THREADED_PYENV_INSTALL_OPTS)
|
|
4680
|
+
lst.append(PLATFORM_PYENV_INSTALL_OPTS[sys.platform].opts())
|
|
4681
|
+
opts = PyenvInstallOpts().merge(*lst)
|
|
4456
4682
|
|
|
4457
|
-
|
|
4683
|
+
self._version = version
|
|
4684
|
+
self._opts = opts
|
|
4685
|
+
self._interp_opts = interp_opts
|
|
4686
|
+
self._given_install_name = install_name
|
|
4458
4687
|
|
|
4688
|
+
self._no_default_opts = no_default_opts
|
|
4689
|
+
self._pyenv = pyenv
|
|
4459
4690
|
|
|
4460
|
-
|
|
4691
|
+
@property
|
|
4692
|
+
def version(self) -> str:
|
|
4693
|
+
return self._version
|
|
4461
4694
|
|
|
4462
|
-
|
|
4695
|
+
@property
|
|
4696
|
+
def opts(self) -> PyenvInstallOpts:
|
|
4697
|
+
return self._opts
|
|
4463
4698
|
|
|
4464
4699
|
@cached_nullary
|
|
4465
|
-
def
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
keys=[CextMagic.KEY],
|
|
4470
|
-
))
|
|
4471
|
-
|
|
4472
|
-
#
|
|
4700
|
+
def install_name(self) -> str:
|
|
4701
|
+
if self._given_install_name is not None:
|
|
4702
|
+
return self._given_install_name
|
|
4703
|
+
return self._version + ('-debug' if self._interp_opts.debug else '')
|
|
4473
4704
|
|
|
4474
|
-
@
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
setup_py: str
|
|
4705
|
+
@cached_nullary
|
|
4706
|
+
def install_dir(self) -> str:
|
|
4707
|
+
return str(os.path.join(check_not_none(self._pyenv.root()), 'versions', self.install_name()))
|
|
4478
4708
|
|
|
4479
4709
|
@cached_nullary
|
|
4480
|
-
def
|
|
4481
|
-
|
|
4710
|
+
def install(self) -> str:
|
|
4711
|
+
env = {**os.environ, **self._opts.env}
|
|
4712
|
+
for k, l in [
|
|
4713
|
+
('CFLAGS', self._opts.cflags),
|
|
4714
|
+
('LDFLAGS', self._opts.ldflags),
|
|
4715
|
+
('PYTHON_CONFIGURE_OPTS', self._opts.conf_opts),
|
|
4716
|
+
]:
|
|
4717
|
+
v = ' '.join(l)
|
|
4718
|
+
if k in os.environ:
|
|
4719
|
+
v += ' ' + os.environ[k]
|
|
4720
|
+
env[k] = v
|
|
4482
4721
|
|
|
4483
|
-
|
|
4722
|
+
conf_args = [
|
|
4723
|
+
*self._opts.opts,
|
|
4724
|
+
self._version,
|
|
4725
|
+
]
|
|
4484
4726
|
|
|
4485
|
-
|
|
4727
|
+
if self._given_install_name is not None:
|
|
4728
|
+
full_args = [
|
|
4729
|
+
os.path.join(check_not_none(self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'),
|
|
4730
|
+
*conf_args,
|
|
4731
|
+
self.install_dir(),
|
|
4732
|
+
]
|
|
4733
|
+
else:
|
|
4734
|
+
full_args = [
|
|
4735
|
+
self._pyenv.exe(),
|
|
4736
|
+
'install',
|
|
4737
|
+
*conf_args,
|
|
4738
|
+
]
|
|
4486
4739
|
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4740
|
+
subprocess_check_call(
|
|
4741
|
+
*full_args,
|
|
4742
|
+
env=env,
|
|
4743
|
+
)
|
|
4491
4744
|
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
'optional_dependencies',
|
|
4497
|
-
'entry_points',
|
|
4498
|
-
'scripts',
|
|
4499
|
-
'cli_scripts',
|
|
4500
|
-
]:
|
|
4501
|
-
prj.pop(k, None)
|
|
4745
|
+
exe = os.path.join(self.install_dir(), 'bin', 'python')
|
|
4746
|
+
if not os.path.isfile(exe):
|
|
4747
|
+
raise RuntimeError(f'Interpreter not found: {exe}')
|
|
4748
|
+
return exe
|
|
4502
4749
|
|
|
4503
|
-
pyp_dct['project'] = prj
|
|
4504
4750
|
|
|
4505
|
-
|
|
4751
|
+
##
|
|
4506
4752
|
|
|
4507
|
-
st = dict(specs.setuptools)
|
|
4508
|
-
pyp_dct['tool.setuptools'] = st
|
|
4509
4753
|
|
|
4510
|
-
|
|
4511
|
-
'cexts',
|
|
4754
|
+
class PyenvInterpProvider(InterpProvider):
|
|
4512
4755
|
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
]:
|
|
4517
|
-
st.pop(k, None)
|
|
4756
|
+
def __init__(
|
|
4757
|
+
self,
|
|
4758
|
+
pyenv: Pyenv = Pyenv(),
|
|
4518
4759
|
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
}
|
|
4760
|
+
inspect: bool = False,
|
|
4761
|
+
inspector: InterpInspector = INTERP_INSPECTOR,
|
|
4522
4762
|
|
|
4523
|
-
|
|
4763
|
+
*,
|
|
4524
4764
|
|
|
4525
|
-
|
|
4765
|
+
try_update: bool = False,
|
|
4766
|
+
) -> None:
|
|
4767
|
+
super().__init__()
|
|
4526
4768
|
|
|
4527
|
-
|
|
4528
|
-
ext_name = ext_src.rpartition('.')[0].replace(os.sep, '.')
|
|
4529
|
-
ext_lines.extend([
|
|
4530
|
-
'st.Extension(',
|
|
4531
|
-
f" name='{ext_name}',",
|
|
4532
|
-
f" sources=['{ext_src}'],",
|
|
4533
|
-
" extra_compile_args=['-std=c++20'],",
|
|
4534
|
-
'),',
|
|
4535
|
-
])
|
|
4769
|
+
self._pyenv = pyenv
|
|
4536
4770
|
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
'',
|
|
4540
|
-
'',
|
|
4541
|
-
'st.setup(',
|
|
4542
|
-
' ext_modules=[',
|
|
4543
|
-
*[' ' + l for l in ext_lines],
|
|
4544
|
-
' ]',
|
|
4545
|
-
')',
|
|
4546
|
-
'',
|
|
4547
|
-
])
|
|
4771
|
+
self._inspect = inspect
|
|
4772
|
+
self._inspector = inspector
|
|
4548
4773
|
|
|
4549
|
-
|
|
4774
|
+
self._try_update = try_update
|
|
4550
4775
|
|
|
4551
|
-
|
|
4552
|
-
pyp_dct,
|
|
4553
|
-
src,
|
|
4554
|
-
)
|
|
4776
|
+
#
|
|
4555
4777
|
|
|
4556
|
-
|
|
4557
|
-
|
|
4778
|
+
@staticmethod
|
|
4779
|
+
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
|
4780
|
+
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
|
4781
|
+
if s.endswith(sfx):
|
|
4782
|
+
return s[:-len(sfx)], True
|
|
4783
|
+
return s, False
|
|
4784
|
+
ok = {}
|
|
4785
|
+
s, ok['debug'] = strip_sfx(s, '-debug')
|
|
4786
|
+
s, ok['threaded'] = strip_sfx(s, 't')
|
|
4787
|
+
try:
|
|
4788
|
+
v = Version(s)
|
|
4789
|
+
except InvalidVersion:
|
|
4790
|
+
return None
|
|
4791
|
+
return InterpVersion(v, InterpOpts(**ok))
|
|
4558
4792
|
|
|
4559
|
-
|
|
4560
|
-
|
|
4793
|
+
class Installed(ta.NamedTuple):
|
|
4794
|
+
name: str
|
|
4795
|
+
exe: str
|
|
4796
|
+
version: InterpVersion
|
|
4561
4797
|
|
|
4562
|
-
|
|
4563
|
-
|
|
4798
|
+
def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
|
4799
|
+
iv: ta.Optional[InterpVersion]
|
|
4800
|
+
if self._inspect:
|
|
4801
|
+
try:
|
|
4802
|
+
iv = check_not_none(self._inspector.inspect(ep)).iv
|
|
4803
|
+
except Exception as e: # noqa
|
|
4804
|
+
return None
|
|
4805
|
+
else:
|
|
4806
|
+
iv = self.guess_version(vn)
|
|
4807
|
+
if iv is None:
|
|
4808
|
+
return None
|
|
4809
|
+
return PyenvInterpProvider.Installed(
|
|
4810
|
+
name=vn,
|
|
4811
|
+
exe=ep,
|
|
4812
|
+
version=iv,
|
|
4813
|
+
)
|
|
4564
4814
|
|
|
4815
|
+
def installed(self) -> ta.Sequence[Installed]:
|
|
4816
|
+
ret: ta.List[PyenvInterpProvider.Installed] = []
|
|
4817
|
+
for vn, ep in self._pyenv.version_exes():
|
|
4818
|
+
if (i := self._make_installed(vn, ep)) is None:
|
|
4819
|
+
log.debug('Invalid pyenv version: %s', vn)
|
|
4820
|
+
continue
|
|
4821
|
+
ret.append(i)
|
|
4822
|
+
return ret
|
|
4565
4823
|
|
|
4566
|
-
|
|
4824
|
+
#
|
|
4567
4825
|
|
|
4826
|
+
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
4827
|
+
return [i.version for i in self.installed()]
|
|
4568
4828
|
|
|
4569
|
-
|
|
4829
|
+
def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
4830
|
+
for i in self.installed():
|
|
4831
|
+
if i.version == version:
|
|
4832
|
+
return Interp(
|
|
4833
|
+
exe=i.exe,
|
|
4834
|
+
version=i.version,
|
|
4835
|
+
)
|
|
4836
|
+
raise KeyError(version)
|
|
4570
4837
|
|
|
4571
4838
|
#
|
|
4572
4839
|
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
4576
|
-
|
|
4577
|
-
@cached_nullary
|
|
4578
|
-
def file_contents(self) -> FileContents:
|
|
4579
|
-
specs = self.build_specs()
|
|
4840
|
+
def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
4841
|
+
lst = []
|
|
4580
4842
|
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
}
|
|
4589
|
-
|
|
4590
|
-
prj = specs.pyproject
|
|
4591
|
-
prj['dependencies'] = [f'{prj["name"]} == {prj["version"]}']
|
|
4592
|
-
prj['name'] += self._pkg_suffix
|
|
4593
|
-
for k in [
|
|
4594
|
-
'optional_dependencies',
|
|
4595
|
-
'entry_points',
|
|
4596
|
-
'scripts',
|
|
4597
|
-
]:
|
|
4598
|
-
prj.pop(k, None)
|
|
4599
|
-
|
|
4600
|
-
pyp_dct['project'] = prj
|
|
4601
|
-
|
|
4602
|
-
if (scs := prj.pop('cli_scripts', None)):
|
|
4603
|
-
pyp_dct['project.scripts'] = scs
|
|
4604
|
-
|
|
4605
|
-
#
|
|
4843
|
+
for vs in self._pyenv.installable_versions():
|
|
4844
|
+
if (iv := self.guess_version(vs)) is None:
|
|
4845
|
+
continue
|
|
4846
|
+
if iv.opts.debug:
|
|
4847
|
+
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
|
4848
|
+
for d in [False, True]:
|
|
4849
|
+
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
|
4606
4850
|
|
|
4607
|
-
|
|
4608
|
-
pyp_dct['tool.setuptools'] = st
|
|
4851
|
+
return lst
|
|
4609
4852
|
|
|
4610
|
-
|
|
4611
|
-
|
|
4853
|
+
def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
4854
|
+
lst = self._get_installable_versions(spec)
|
|
4612
4855
|
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
]:
|
|
4617
|
-
st.pop(k, None)
|
|
4856
|
+
if self._try_update and not any(v in spec for v in lst):
|
|
4857
|
+
if self._pyenv.update():
|
|
4858
|
+
lst = self._get_installable_versions(spec)
|
|
4618
4859
|
|
|
4619
|
-
|
|
4620
|
-
'include': [],
|
|
4621
|
-
}
|
|
4860
|
+
return lst
|
|
4622
4861
|
|
|
4623
|
-
|
|
4862
|
+
def install_version(self, version: InterpVersion) -> Interp:
|
|
4863
|
+
inst_version = str(version.version)
|
|
4864
|
+
inst_opts = version.opts
|
|
4865
|
+
if inst_opts.threaded:
|
|
4866
|
+
inst_version += 't'
|
|
4867
|
+
inst_opts = dc.replace(inst_opts, threaded=False)
|
|
4624
4868
|
|
|
4625
|
-
|
|
4626
|
-
|
|
4869
|
+
installer = PyenvVersionInstaller(
|
|
4870
|
+
inst_version,
|
|
4871
|
+
interp_opts=inst_opts,
|
|
4627
4872
|
)
|
|
4628
4873
|
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
with open(os.path.join(self._pkg_dir(), 'pyproject.toml'), 'w') as f:
|
|
4633
|
-
TomlWriter(f).write_root(fc.pyproject_dct)
|
|
4874
|
+
exe = installer.install()
|
|
4875
|
+
return Interp(exe, version)
|
|
4634
4876
|
|
|
4635
4877
|
|
|
4636
4878
|
########################################
|
|
4637
|
-
# ../../interp/
|
|
4879
|
+
# ../../interp/system.py
|
|
4638
4880
|
"""
|
|
4639
4881
|
TODO:
|
|
4640
|
-
-
|
|
4641
|
-
|
|
4642
|
-
- deadsnakes?
|
|
4643
|
-
- uv
|
|
4644
|
-
- loose versions
|
|
4882
|
+
- python, python3, python3.12, ...
|
|
4883
|
+
- check if path py's are venvs: sys.prefix != sys.base_prefix
|
|
4645
4884
|
"""
|
|
4646
4885
|
|
|
4647
4886
|
|
|
4648
4887
|
##
|
|
4649
4888
|
|
|
4650
4889
|
|
|
4651
|
-
|
|
4652
|
-
|
|
4890
|
+
@dc.dataclass(frozen=True)
|
|
4891
|
+
class SystemInterpProvider(InterpProvider):
|
|
4892
|
+
cmd: str = 'python3'
|
|
4893
|
+
path: ta.Optional[str] = None
|
|
4653
4894
|
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
if abc.ABC not in cls.__bases__ and 'name' not in cls.__dict__:
|
|
4657
|
-
sfx = 'InterpProvider'
|
|
4658
|
-
if not cls.__name__.endswith(sfx):
|
|
4659
|
-
raise NameError(cls)
|
|
4660
|
-
setattr(cls, 'name', snake_case(cls.__name__[:-len(sfx)]))
|
|
4895
|
+
inspect: bool = False
|
|
4896
|
+
inspector: InterpInspector = INTERP_INSPECTOR
|
|
4661
4897
|
|
|
4662
|
-
|
|
4663
|
-
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
4664
|
-
raise NotImplementedError
|
|
4898
|
+
#
|
|
4665
4899
|
|
|
4666
|
-
@
|
|
4667
|
-
def
|
|
4668
|
-
|
|
4900
|
+
@staticmethod
|
|
4901
|
+
def _re_which(
|
|
4902
|
+
pat: re.Pattern,
|
|
4903
|
+
*,
|
|
4904
|
+
mode: int = os.F_OK | os.X_OK,
|
|
4905
|
+
path: ta.Optional[str] = None,
|
|
4906
|
+
) -> ta.List[str]:
|
|
4907
|
+
if path is None:
|
|
4908
|
+
path = os.environ.get('PATH', None)
|
|
4909
|
+
if path is None:
|
|
4910
|
+
try:
|
|
4911
|
+
path = os.confstr('CS_PATH')
|
|
4912
|
+
except (AttributeError, ValueError):
|
|
4913
|
+
path = os.defpath
|
|
4669
4914
|
|
|
4670
|
-
|
|
4671
|
-
|
|
4915
|
+
if not path:
|
|
4916
|
+
return []
|
|
4672
4917
|
|
|
4673
|
-
|
|
4674
|
-
|
|
4918
|
+
path = os.fsdecode(path)
|
|
4919
|
+
pathlst = path.split(os.pathsep)
|
|
4675
4920
|
|
|
4921
|
+
def _access_check(fn: str, mode: int) -> bool:
|
|
4922
|
+
return os.path.exists(fn) and os.access(fn, mode)
|
|
4676
4923
|
|
|
4677
|
-
|
|
4924
|
+
out = []
|
|
4925
|
+
seen = set()
|
|
4926
|
+
for d in pathlst:
|
|
4927
|
+
normdir = os.path.normcase(d)
|
|
4928
|
+
if normdir not in seen:
|
|
4929
|
+
seen.add(normdir)
|
|
4930
|
+
if not _access_check(normdir, mode):
|
|
4931
|
+
continue
|
|
4932
|
+
for thefile in os.listdir(d):
|
|
4933
|
+
name = os.path.join(d, thefile)
|
|
4934
|
+
if not (
|
|
4935
|
+
os.path.isfile(name) and
|
|
4936
|
+
pat.fullmatch(thefile) and
|
|
4937
|
+
_access_check(name, mode)
|
|
4938
|
+
):
|
|
4939
|
+
continue
|
|
4940
|
+
out.append(name)
|
|
4678
4941
|
|
|
4942
|
+
return out
|
|
4679
4943
|
|
|
4680
|
-
class RunningInterpProvider(InterpProvider):
|
|
4681
4944
|
@cached_nullary
|
|
4682
|
-
def
|
|
4683
|
-
return
|
|
4945
|
+
def exes(self) -> ta.List[str]:
|
|
4946
|
+
return self._re_which(
|
|
4947
|
+
re.compile(r'python3(\.\d+)?'),
|
|
4948
|
+
path=self.path,
|
|
4949
|
+
)
|
|
4950
|
+
|
|
4951
|
+
#
|
|
4952
|
+
|
|
4953
|
+
def get_exe_version(self, exe: str) -> ta.Optional[InterpVersion]:
|
|
4954
|
+
if not self.inspect:
|
|
4955
|
+
s = os.path.basename(exe)
|
|
4956
|
+
if s.startswith('python'):
|
|
4957
|
+
s = s[len('python'):]
|
|
4958
|
+
if '.' in s:
|
|
4959
|
+
try:
|
|
4960
|
+
return InterpVersion.parse(s)
|
|
4961
|
+
except InvalidVersion:
|
|
4962
|
+
pass
|
|
4963
|
+
ii = self.inspector.inspect(exe)
|
|
4964
|
+
return ii.iv if ii is not None else None
|
|
4965
|
+
|
|
4966
|
+
def exe_versions(self) -> ta.Sequence[ta.Tuple[str, InterpVersion]]:
|
|
4967
|
+
lst = []
|
|
4968
|
+
for e in self.exes():
|
|
4969
|
+
if (ev := self.get_exe_version(e)) is None:
|
|
4970
|
+
log.debug('Invalid system version: %s', e)
|
|
4971
|
+
continue
|
|
4972
|
+
lst.append((e, ev))
|
|
4973
|
+
return lst
|
|
4974
|
+
|
|
4975
|
+
#
|
|
4684
4976
|
|
|
4685
4977
|
def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
|
4686
|
-
return [self.
|
|
4978
|
+
return [ev for e, ev in self.exe_versions()]
|
|
4687
4979
|
|
|
4688
4980
|
def get_installed_version(self, version: InterpVersion) -> Interp:
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4981
|
+
for e, ev in self.exe_versions():
|
|
4982
|
+
if ev != version:
|
|
4983
|
+
continue
|
|
4984
|
+
return Interp(
|
|
4985
|
+
exe=e,
|
|
4986
|
+
version=ev,
|
|
4987
|
+
)
|
|
4988
|
+
raise KeyError(version)
|
|
4695
4989
|
|
|
4696
4990
|
|
|
4697
4991
|
########################################
|
|
4698
|
-
#
|
|
4992
|
+
# ../pkg.py
|
|
4699
4993
|
"""
|
|
4700
4994
|
TODO:
|
|
4701
|
-
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4995
|
+
- ext scanning
|
|
4996
|
+
- __revision__
|
|
4997
|
+
- entry_points
|
|
4998
|
+
|
|
4999
|
+
** NOTE **
|
|
5000
|
+
setuptools now (2024/09/02) has experimental support for extensions in pure pyproject.toml - but we still want a
|
|
5001
|
+
separate '-cext' package
|
|
5002
|
+
https://setuptools.pypa.io/en/latest/userguide/ext_modules.html
|
|
5003
|
+
https://github.com/pypa/setuptools/commit/1a9d87308dc0d8aabeaae0dce989b35dfb7699f0#diff-61d113525e9cc93565799a4bb8b34a68e2945b8a3f7d90c81380614a4ea39542R7-R8
|
|
4710
5004
|
|
|
5005
|
+
--
|
|
4711
5006
|
|
|
4712
|
-
|
|
5007
|
+
https://setuptools.pypa.io/en/latest/references/keywords.html
|
|
5008
|
+
https://packaging.python.org/en/latest/specifications/pyproject-toml
|
|
4713
5009
|
|
|
5010
|
+
How to build a C extension in keeping with PEP 517, i.e. with pyproject.toml instead of setup.py?
|
|
5011
|
+
https://stackoverflow.com/a/66479252
|
|
5012
|
+
|
|
5013
|
+
https://github.com/pypa/sampleproject/blob/db5806e0a3204034c51b1c00dde7d5eb3fa2532e/setup.py
|
|
5014
|
+
|
|
5015
|
+
https://pip.pypa.io/en/stable/cli/pip_install/#vcs-support
|
|
5016
|
+
vcs+protocol://repo_url/#egg=pkg&subdirectory=pkg_dir
|
|
5017
|
+
'git+https://github.com/wrmsr/omlish@master#subdirectory=.pip/omlish'
|
|
5018
|
+
""" # noqa
|
|
5019
|
+
|
|
5020
|
+
|
|
5021
|
+
#
|
|
4714
5022
|
|
|
4715
|
-
class Pyenv:
|
|
4716
5023
|
|
|
5024
|
+
class BasePyprojectPackageGenerator(abc.ABC):
|
|
4717
5025
|
def __init__(
|
|
4718
5026
|
self,
|
|
5027
|
+
dir_name: str,
|
|
5028
|
+
pkgs_root: str,
|
|
4719
5029
|
*,
|
|
4720
|
-
|
|
5030
|
+
pkg_suffix: str = '',
|
|
4721
5031
|
) -> None:
|
|
4722
|
-
if root is not None and not (isinstance(root, str) and root):
|
|
4723
|
-
raise ValueError(f'pyenv_root: {root!r}')
|
|
4724
|
-
|
|
4725
5032
|
super().__init__()
|
|
5033
|
+
self._dir_name = dir_name
|
|
5034
|
+
self._pkgs_root = pkgs_root
|
|
5035
|
+
self._pkg_suffix = pkg_suffix
|
|
4726
5036
|
|
|
4727
|
-
|
|
5037
|
+
#
|
|
4728
5038
|
|
|
4729
5039
|
@cached_nullary
|
|
4730
|
-
def
|
|
4731
|
-
|
|
4732
|
-
return self._root_kw
|
|
4733
|
-
|
|
4734
|
-
if shutil.which('pyenv'):
|
|
4735
|
-
return subprocess_check_output_str('pyenv', 'root')
|
|
5040
|
+
def about(self) -> types.ModuleType:
|
|
5041
|
+
return importlib.import_module(f'{self._dir_name}.__about__')
|
|
4736
5042
|
|
|
4737
|
-
|
|
4738
|
-
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
|
4739
|
-
return d
|
|
4740
|
-
|
|
4741
|
-
return None
|
|
5043
|
+
#
|
|
4742
5044
|
|
|
4743
5045
|
@cached_nullary
|
|
4744
|
-
def
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
ret = []
|
|
4751
|
-
vp = os.path.join(root, 'versions')
|
|
4752
|
-
if os.path.isdir(vp):
|
|
4753
|
-
for dn in os.listdir(vp):
|
|
4754
|
-
ep = os.path.join(vp, dn, 'bin', 'python')
|
|
4755
|
-
if not os.path.isfile(ep):
|
|
4756
|
-
continue
|
|
4757
|
-
ret.append((dn, ep))
|
|
4758
|
-
return ret
|
|
5046
|
+
def _pkg_dir(self) -> str:
|
|
5047
|
+
pkg_dir: str = os.path.join(self._pkgs_root, self._dir_name + self._pkg_suffix)
|
|
5048
|
+
if os.path.isdir(pkg_dir):
|
|
5049
|
+
shutil.rmtree(pkg_dir)
|
|
5050
|
+
os.makedirs(pkg_dir)
|
|
5051
|
+
return pkg_dir
|
|
4759
5052
|
|
|
4760
|
-
|
|
4761
|
-
if self.root() is None:
|
|
4762
|
-
return []
|
|
4763
|
-
ret = []
|
|
4764
|
-
s = subprocess_check_output_str(self.exe(), 'install', '--list')
|
|
4765
|
-
for l in s.splitlines():
|
|
4766
|
-
if not l.startswith(' '):
|
|
4767
|
-
continue
|
|
4768
|
-
l = l.strip()
|
|
4769
|
-
if not l:
|
|
4770
|
-
continue
|
|
4771
|
-
ret.append(l)
|
|
4772
|
-
return ret
|
|
5053
|
+
#
|
|
4773
5054
|
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
return False
|
|
4779
|
-
subprocess_check_call('git', 'pull', cwd=root)
|
|
4780
|
-
return True
|
|
5055
|
+
_GIT_IGNORE: ta.Sequence[str] = [
|
|
5056
|
+
'/*.egg-info/',
|
|
5057
|
+
'/dist',
|
|
5058
|
+
]
|
|
4781
5059
|
|
|
5060
|
+
def _write_git_ignore(self) -> None:
|
|
5061
|
+
with open(os.path.join(self._pkg_dir(), '.gitignore'), 'w') as f:
|
|
5062
|
+
f.write('\n'.join(self._GIT_IGNORE))
|
|
4782
5063
|
|
|
4783
|
-
|
|
5064
|
+
#
|
|
4784
5065
|
|
|
5066
|
+
def _symlink_source_dir(self) -> None:
|
|
5067
|
+
os.symlink(
|
|
5068
|
+
os.path.relpath(self._dir_name, self._pkg_dir()),
|
|
5069
|
+
os.path.join(self._pkg_dir(), self._dir_name),
|
|
5070
|
+
)
|
|
4785
5071
|
|
|
4786
|
-
|
|
4787
|
-
class PyenvInstallOpts:
|
|
4788
|
-
opts: ta.Sequence[str] = ()
|
|
4789
|
-
conf_opts: ta.Sequence[str] = ()
|
|
4790
|
-
cflags: ta.Sequence[str] = ()
|
|
4791
|
-
ldflags: ta.Sequence[str] = ()
|
|
4792
|
-
env: ta.Mapping[str, str] = dc.field(default_factory=dict)
|
|
5072
|
+
#
|
|
4793
5073
|
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
conf_opts=list(itertools.chain.from_iterable(o.conf_opts for o in [self, *others])),
|
|
4798
|
-
cflags=list(itertools.chain.from_iterable(o.cflags for o in [self, *others])),
|
|
4799
|
-
ldflags=list(itertools.chain.from_iterable(o.ldflags for o in [self, *others])),
|
|
4800
|
-
env=dict(itertools.chain.from_iterable(o.env.items() for o in [self, *others])),
|
|
4801
|
-
)
|
|
5074
|
+
@cached_nullary
|
|
5075
|
+
def project_cls(self) -> type:
|
|
5076
|
+
return self.about().Project
|
|
4802
5077
|
|
|
5078
|
+
@cached_nullary
|
|
5079
|
+
def setuptools_cls(self) -> type:
|
|
5080
|
+
return self.about().Setuptools
|
|
4803
5081
|
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
5082
|
+
@staticmethod
|
|
5083
|
+
def _build_cls_dct(cls: type) -> ta.Dict[str, ta.Any]: # noqa
|
|
5084
|
+
dct = {}
|
|
5085
|
+
for b in reversed(cls.__mro__):
|
|
5086
|
+
for k, v in b.__dict__.items():
|
|
5087
|
+
if k.startswith('_'):
|
|
5088
|
+
continue
|
|
5089
|
+
dct[k] = v
|
|
5090
|
+
return dct
|
|
4813
5091
|
|
|
4814
|
-
|
|
5092
|
+
@staticmethod
|
|
5093
|
+
def _move_dict_key(
|
|
5094
|
+
sd: ta.Dict[str, ta.Any],
|
|
5095
|
+
sk: str,
|
|
5096
|
+
dd: ta.Dict[str, ta.Any],
|
|
5097
|
+
dk: str,
|
|
5098
|
+
) -> None:
|
|
5099
|
+
if sk in sd:
|
|
5100
|
+
dd[dk] = sd.pop(sk)
|
|
4815
5101
|
|
|
4816
|
-
|
|
4817
|
-
|
|
5102
|
+
@dc.dataclass(frozen=True)
|
|
5103
|
+
class Specs:
|
|
5104
|
+
pyproject: ta.Dict[str, ta.Any]
|
|
5105
|
+
setuptools: ta.Dict[str, ta.Any]
|
|
4818
5106
|
|
|
4819
|
-
|
|
5107
|
+
def build_specs(self) -> Specs:
|
|
5108
|
+
return self.Specs(
|
|
5109
|
+
self._build_cls_dct(self.project_cls()),
|
|
5110
|
+
self._build_cls_dct(self.setuptools_cls()),
|
|
5111
|
+
)
|
|
4820
5112
|
|
|
4821
|
-
|
|
4822
|
-
],
|
|
4823
|
-
cflags=[
|
|
4824
|
-
# '-march=native',
|
|
4825
|
-
# '-mtune=native',
|
|
4826
|
-
],
|
|
4827
|
-
)
|
|
5113
|
+
#
|
|
4828
5114
|
|
|
4829
|
-
|
|
5115
|
+
class _PkgData(ta.NamedTuple):
|
|
5116
|
+
inc: ta.List[str]
|
|
5117
|
+
exc: ta.List[str]
|
|
4830
5118
|
|
|
4831
|
-
|
|
5119
|
+
@cached_nullary
|
|
5120
|
+
def _collect_pkg_data(self) -> _PkgData:
|
|
5121
|
+
inc: ta.List[str] = []
|
|
5122
|
+
exc: ta.List[str] = []
|
|
4832
5123
|
|
|
5124
|
+
for p, ds, fs in os.walk(self._dir_name): # noqa
|
|
5125
|
+
for f in fs:
|
|
5126
|
+
if f != '.pkgdata':
|
|
5127
|
+
continue
|
|
5128
|
+
rp = os.path.relpath(p, self._dir_name)
|
|
5129
|
+
log.info('Found pkgdata %s for pkg %s', rp, self._dir_name)
|
|
5130
|
+
with open(os.path.join(p, f)) as fo:
|
|
5131
|
+
src = fo.read()
|
|
5132
|
+
for l in src.splitlines():
|
|
5133
|
+
if not (l := l.strip()):
|
|
5134
|
+
continue
|
|
5135
|
+
if l.startswith('!'):
|
|
5136
|
+
exc.append(os.path.join(rp, l[1:]))
|
|
5137
|
+
else:
|
|
5138
|
+
inc.append(os.path.join(rp, l))
|
|
4833
5139
|
|
|
4834
|
-
|
|
5140
|
+
return self._PkgData(inc, exc)
|
|
4835
5141
|
|
|
5142
|
+
#
|
|
4836
5143
|
|
|
4837
|
-
class PyenvInstallOptsProvider(abc.ABC):
|
|
4838
5144
|
@abc.abstractmethod
|
|
4839
|
-
def
|
|
5145
|
+
def _write_file_contents(self) -> None:
|
|
4840
5146
|
raise NotImplementedError
|
|
4841
5147
|
|
|
5148
|
+
#
|
|
4842
5149
|
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
5150
|
+
_STANDARD_FILES: ta.Sequence[str] = [
|
|
5151
|
+
'LICENSE',
|
|
5152
|
+
'README.rst',
|
|
5153
|
+
]
|
|
4847
5154
|
|
|
4848
|
-
|
|
5155
|
+
def _symlink_standard_files(self) -> None:
|
|
5156
|
+
for fn in self._STANDARD_FILES:
|
|
5157
|
+
if os.path.exists(fn):
|
|
5158
|
+
os.symlink(os.path.relpath(fn, self._pkg_dir()), os.path.join(self._pkg_dir(), fn))
|
|
4849
5159
|
|
|
4850
|
-
|
|
4851
|
-
def framework_opts(self) -> PyenvInstallOpts:
|
|
4852
|
-
return PyenvInstallOpts(conf_opts=['--enable-framework'])
|
|
5160
|
+
#
|
|
4853
5161
|
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
return shutil.which('brew') is not None
|
|
5162
|
+
def children(self) -> ta.Sequence['BasePyprojectPackageGenerator']:
|
|
5163
|
+
return []
|
|
4857
5164
|
|
|
4858
|
-
|
|
4859
|
-
'openssl',
|
|
4860
|
-
'readline',
|
|
4861
|
-
'sqlite3',
|
|
4862
|
-
'zlib',
|
|
4863
|
-
]
|
|
5165
|
+
#
|
|
4864
5166
|
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
cflags = []
|
|
4868
|
-
ldflags = []
|
|
4869
|
-
for dep in self.BREW_DEPS:
|
|
4870
|
-
dep_prefix = subprocess_check_output_str('brew', '--prefix', dep)
|
|
4871
|
-
cflags.append(f'-I{dep_prefix}/include')
|
|
4872
|
-
ldflags.append(f'-L{dep_prefix}/lib')
|
|
4873
|
-
return PyenvInstallOpts(
|
|
4874
|
-
cflags=cflags,
|
|
4875
|
-
ldflags=ldflags,
|
|
4876
|
-
)
|
|
5167
|
+
def gen(self) -> str:
|
|
5168
|
+
log.info('Generating pyproject package: %s -> %s (%s)', self._dir_name, self._pkgs_root, self._pkg_suffix)
|
|
4877
5169
|
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
5170
|
+
self._pkg_dir()
|
|
5171
|
+
self._write_git_ignore()
|
|
5172
|
+
self._symlink_source_dir()
|
|
5173
|
+
self._write_file_contents()
|
|
5174
|
+
self._symlink_standard_files()
|
|
4882
5175
|
|
|
4883
|
-
|
|
4884
|
-
tcl_tk_ver_str = subprocess_check_output_str('brew', 'ls', '--versions', 'tcl-tk')
|
|
4885
|
-
tcl_tk_ver = '.'.join(tcl_tk_ver_str.split()[1].split('.')[:2])
|
|
5176
|
+
return self._pkg_dir()
|
|
4886
5177
|
|
|
4887
|
-
|
|
4888
|
-
f"--with-tcltk-includes='-I{tcl_tk_prefix}/include'",
|
|
4889
|
-
f"--with-tcltk-libs='-L{tcl_tk_prefix}/lib -ltcl{tcl_tk_ver} -ltk{tcl_tk_ver}'",
|
|
4890
|
-
])
|
|
5178
|
+
#
|
|
4891
5179
|
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
# pkg_config_path += ':' + os.environ['PKG_CONFIG_PATH']
|
|
4897
|
-
# return PyenvInstallOpts(env={'PKG_CONFIG_PATH': pkg_config_path})
|
|
5180
|
+
@dc.dataclass(frozen=True)
|
|
5181
|
+
class BuildOpts:
|
|
5182
|
+
add_revision: bool = False
|
|
5183
|
+
test: bool = False
|
|
4898
5184
|
|
|
4899
|
-
def
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
5185
|
+
def build(
|
|
5186
|
+
self,
|
|
5187
|
+
output_dir: ta.Optional[str] = None,
|
|
5188
|
+
opts: BuildOpts = BuildOpts(),
|
|
5189
|
+
) -> None:
|
|
5190
|
+
subprocess_check_call(
|
|
5191
|
+
sys.executable,
|
|
5192
|
+
'-m',
|
|
5193
|
+
'build',
|
|
5194
|
+
cwd=self._pkg_dir(),
|
|
4905
5195
|
)
|
|
4906
5196
|
|
|
5197
|
+
dist_dir = os.path.join(self._pkg_dir(), 'dist')
|
|
4907
5198
|
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
'linux': LinuxPyenvInstallOpts(),
|
|
4911
|
-
}
|
|
5199
|
+
if opts.add_revision:
|
|
5200
|
+
GitRevisionAdder().add_to(dist_dir)
|
|
4912
5201
|
|
|
5202
|
+
if opts.test:
|
|
5203
|
+
for fn in os.listdir(dist_dir):
|
|
5204
|
+
tmp_dir = tempfile.mkdtemp()
|
|
4913
5205
|
|
|
4914
|
-
|
|
5206
|
+
subprocess_check_call(
|
|
5207
|
+
sys.executable,
|
|
5208
|
+
'-m', 'venv',
|
|
5209
|
+
'test-install',
|
|
5210
|
+
cwd=tmp_dir,
|
|
5211
|
+
)
|
|
4915
5212
|
|
|
5213
|
+
subprocess_check_call(
|
|
5214
|
+
os.path.join(tmp_dir, 'test-install', 'bin', 'python3'),
|
|
5215
|
+
'-m', 'pip',
|
|
5216
|
+
'install',
|
|
5217
|
+
os.path.abspath(os.path.join(dist_dir, fn)),
|
|
5218
|
+
cwd=tmp_dir,
|
|
5219
|
+
)
|
|
4916
5220
|
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
latter to build custom interp with ft, need former to use canned / blessed interps. Muh.
|
|
4921
|
-
"""
|
|
5221
|
+
if output_dir is not None:
|
|
5222
|
+
for fn in os.listdir(dist_dir):
|
|
5223
|
+
shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(output_dir, fn))
|
|
4922
5224
|
|
|
4923
|
-
def __init__(
|
|
4924
|
-
self,
|
|
4925
|
-
version: str,
|
|
4926
|
-
opts: ta.Optional[PyenvInstallOpts] = None,
|
|
4927
|
-
interp_opts: InterpOpts = InterpOpts(),
|
|
4928
|
-
*,
|
|
4929
|
-
install_name: ta.Optional[str] = None,
|
|
4930
|
-
no_default_opts: bool = False,
|
|
4931
|
-
pyenv: Pyenv = Pyenv(),
|
|
4932
|
-
) -> None:
|
|
4933
|
-
super().__init__()
|
|
4934
5225
|
|
|
4935
|
-
|
|
4936
|
-
if opts is None:
|
|
4937
|
-
opts = PyenvInstallOpts()
|
|
4938
|
-
else:
|
|
4939
|
-
lst = [opts if opts is not None else DEFAULT_PYENV_INSTALL_OPTS]
|
|
4940
|
-
if interp_opts.debug:
|
|
4941
|
-
lst.append(DEBUG_PYENV_INSTALL_OPTS)
|
|
4942
|
-
if interp_opts.threaded:
|
|
4943
|
-
lst.append(THREADED_PYENV_INSTALL_OPTS)
|
|
4944
|
-
lst.append(PLATFORM_PYENV_INSTALL_OPTS[sys.platform].opts())
|
|
4945
|
-
opts = PyenvInstallOpts().merge(*lst)
|
|
5226
|
+
#
|
|
4946
5227
|
|
|
4947
|
-
self._version = version
|
|
4948
|
-
self._opts = opts
|
|
4949
|
-
self._interp_opts = interp_opts
|
|
4950
|
-
self._given_install_name = install_name
|
|
4951
5228
|
|
|
4952
|
-
|
|
4953
|
-
self._pyenv = pyenv
|
|
5229
|
+
class PyprojectPackageGenerator(BasePyprojectPackageGenerator):
|
|
4954
5230
|
|
|
4955
|
-
|
|
4956
|
-
def version(self) -> str:
|
|
4957
|
-
return self._version
|
|
5231
|
+
#
|
|
4958
5232
|
|
|
4959
|
-
@
|
|
4960
|
-
|
|
4961
|
-
|
|
5233
|
+
@dc.dataclass(frozen=True)
|
|
5234
|
+
class FileContents:
|
|
5235
|
+
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
5236
|
+
manifest_in: ta.Optional[ta.Sequence[str]]
|
|
4962
5237
|
|
|
4963
5238
|
@cached_nullary
|
|
4964
|
-
def
|
|
4965
|
-
|
|
4966
|
-
return self._given_install_name
|
|
4967
|
-
return self._version + ('-debug' if self._interp_opts.debug else '')
|
|
5239
|
+
def file_contents(self) -> FileContents:
|
|
5240
|
+
specs = self.build_specs()
|
|
4968
5241
|
|
|
4969
|
-
|
|
4970
|
-
def install_dir(self) -> str:
|
|
4971
|
-
return str(os.path.join(check_not_none(self._pyenv.root()), 'versions', self.install_name()))
|
|
5242
|
+
#
|
|
4972
5243
|
|
|
4973
|
-
|
|
4974
|
-
def install(self) -> str:
|
|
4975
|
-
env = {**os.environ, **self._opts.env}
|
|
4976
|
-
for k, l in [
|
|
4977
|
-
('CFLAGS', self._opts.cflags),
|
|
4978
|
-
('LDFLAGS', self._opts.ldflags),
|
|
4979
|
-
('PYTHON_CONFIGURE_OPTS', self._opts.conf_opts),
|
|
4980
|
-
]:
|
|
4981
|
-
v = ' '.join(l)
|
|
4982
|
-
if k in os.environ:
|
|
4983
|
-
v += ' ' + os.environ[k]
|
|
4984
|
-
env[k] = v
|
|
5244
|
+
pyp_dct = {}
|
|
4985
5245
|
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
5246
|
+
pyp_dct['build-system'] = {
|
|
5247
|
+
'requires': ['setuptools'],
|
|
5248
|
+
'build-backend': 'setuptools.build_meta',
|
|
5249
|
+
}
|
|
4990
5250
|
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
os.path.join(check_not_none(self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'),
|
|
4994
|
-
*conf_args,
|
|
4995
|
-
self.install_dir(),
|
|
4996
|
-
]
|
|
4997
|
-
else:
|
|
4998
|
-
full_args = [
|
|
4999
|
-
self._pyenv.exe(),
|
|
5000
|
-
'install',
|
|
5001
|
-
*conf_args,
|
|
5002
|
-
]
|
|
5251
|
+
prj = specs.pyproject
|
|
5252
|
+
prj['name'] += self._pkg_suffix
|
|
5003
5253
|
|
|
5004
|
-
|
|
5005
|
-
*full_args,
|
|
5006
|
-
env=env,
|
|
5007
|
-
)
|
|
5254
|
+
pyp_dct['project'] = prj
|
|
5008
5255
|
|
|
5009
|
-
|
|
5010
|
-
if
|
|
5011
|
-
|
|
5012
|
-
|
|
5256
|
+
self._move_dict_key(prj, 'optional_dependencies', pyp_dct, extrask := 'project.optional-dependencies')
|
|
5257
|
+
if (extras := pyp_dct.get(extrask)):
|
|
5258
|
+
pyp_dct[extrask] = {
|
|
5259
|
+
'all': [
|
|
5260
|
+
e
|
|
5261
|
+
for lst in extras.values()
|
|
5262
|
+
for e in lst
|
|
5263
|
+
],
|
|
5264
|
+
**extras,
|
|
5265
|
+
}
|
|
5013
5266
|
|
|
5267
|
+
if (eps := prj.pop('entry_points', None)):
|
|
5268
|
+
pyp_dct['project.entry-points'] = {TomlWriter.Literal(f"'{k}'"): v for k, v in eps.items()} # type: ignore # noqa
|
|
5014
5269
|
|
|
5015
|
-
|
|
5270
|
+
if (scs := prj.pop('scripts', None)):
|
|
5271
|
+
pyp_dct['project.scripts'] = scs
|
|
5016
5272
|
|
|
5273
|
+
prj.pop('cli_scripts', None)
|
|
5017
5274
|
|
|
5018
|
-
|
|
5275
|
+
##
|
|
5019
5276
|
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
pyenv: Pyenv = Pyenv(),
|
|
5277
|
+
st = dict(specs.setuptools)
|
|
5278
|
+
pyp_dct['tool.setuptools'] = st
|
|
5023
5279
|
|
|
5024
|
-
|
|
5025
|
-
inspector: InterpInspector = INTERP_INSPECTOR,
|
|
5280
|
+
st.pop('cexts', None)
|
|
5026
5281
|
|
|
5027
|
-
|
|
5282
|
+
#
|
|
5028
5283
|
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5284
|
+
# TODO: default
|
|
5285
|
+
# find_packages = {
|
|
5286
|
+
# 'include': [Project.name, f'{Project.name}.*'],
|
|
5287
|
+
# 'exclude': [*SetuptoolsBase.find_packages['exclude']],
|
|
5288
|
+
# }
|
|
5032
5289
|
|
|
5033
|
-
|
|
5290
|
+
fp = dict(st.pop('find_packages', {}))
|
|
5034
5291
|
|
|
5035
|
-
|
|
5036
|
-
self._inspector = inspector
|
|
5292
|
+
pyp_dct['tool.setuptools.packages.find'] = fp
|
|
5037
5293
|
|
|
5038
|
-
|
|
5294
|
+
#
|
|
5039
5295
|
|
|
5040
|
-
|
|
5296
|
+
# TODO: default
|
|
5297
|
+
# package_data = {
|
|
5298
|
+
# '*': [
|
|
5299
|
+
# '*.c',
|
|
5300
|
+
# '*.cc',
|
|
5301
|
+
# '*.h',
|
|
5302
|
+
# '.manifests.json',
|
|
5303
|
+
# 'LICENSE',
|
|
5304
|
+
# ],
|
|
5305
|
+
# }
|
|
5041
5306
|
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
|
5045
|
-
if s.endswith(sfx):
|
|
5046
|
-
return s[:-len(sfx)], True
|
|
5047
|
-
return s, False
|
|
5048
|
-
ok = {}
|
|
5049
|
-
s, ok['debug'] = strip_sfx(s, '-debug')
|
|
5050
|
-
s, ok['threaded'] = strip_sfx(s, 't')
|
|
5051
|
-
try:
|
|
5052
|
-
v = Version(s)
|
|
5053
|
-
except InvalidVersion:
|
|
5054
|
-
return None
|
|
5055
|
-
return InterpVersion(v, InterpOpts(**ok))
|
|
5307
|
+
pd = dict(st.pop('package_data', {}))
|
|
5308
|
+
epd = dict(st.pop('exclude_package_data', {}))
|
|
5056
5309
|
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5310
|
+
cpd = self._collect_pkg_data()
|
|
5311
|
+
if cpd.inc:
|
|
5312
|
+
pd['*'] = [*pd.get('*', []), *sorted(set(cpd.inc))]
|
|
5313
|
+
if cpd.exc:
|
|
5314
|
+
epd['*'] = [*epd.get('*', []), *sorted(set(cpd.exc))]
|
|
5061
5315
|
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
if
|
|
5065
|
-
|
|
5066
|
-
iv = check_not_none(self._inspector.inspect(ep)).iv
|
|
5067
|
-
except Exception as e: # noqa
|
|
5068
|
-
return None
|
|
5069
|
-
else:
|
|
5070
|
-
iv = self.guess_version(vn)
|
|
5071
|
-
if iv is None:
|
|
5072
|
-
return None
|
|
5073
|
-
return PyenvInterpProvider.Installed(
|
|
5074
|
-
name=vn,
|
|
5075
|
-
exe=ep,
|
|
5076
|
-
version=iv,
|
|
5077
|
-
)
|
|
5316
|
+
if pd:
|
|
5317
|
+
pyp_dct['tool.setuptools.package-data'] = pd
|
|
5318
|
+
if epd:
|
|
5319
|
+
pyp_dct['tool.setuptools.exclude-package-data'] = epd
|
|
5078
5320
|
|
|
5079
|
-
|
|
5080
|
-
ret: ta.List[PyenvInterpProvider.Installed] = []
|
|
5081
|
-
for vn, ep in self._pyenv.version_exes():
|
|
5082
|
-
if (i := self._make_installed(vn, ep)) is None:
|
|
5083
|
-
log.debug('Invalid pyenv version: %s', vn)
|
|
5084
|
-
continue
|
|
5085
|
-
ret.append(i)
|
|
5086
|
-
return ret
|
|
5321
|
+
#
|
|
5087
5322
|
|
|
5088
|
-
|
|
5323
|
+
# TODO: default
|
|
5324
|
+
# manifest_in = [
|
|
5325
|
+
# 'global-exclude **/conftest.py',
|
|
5326
|
+
# ]
|
|
5089
5327
|
|
|
5090
|
-
|
|
5091
|
-
return [i.version for i in self.installed()]
|
|
5328
|
+
mani_in = st.pop('manifest_in', None)
|
|
5092
5329
|
|
|
5093
|
-
|
|
5094
|
-
for i in self.installed():
|
|
5095
|
-
if i.version == version:
|
|
5096
|
-
return Interp(
|
|
5097
|
-
exe=i.exe,
|
|
5098
|
-
version=i.version,
|
|
5099
|
-
)
|
|
5100
|
-
raise KeyError(version)
|
|
5330
|
+
#
|
|
5101
5331
|
|
|
5102
|
-
|
|
5332
|
+
return self.FileContents(
|
|
5333
|
+
pyp_dct,
|
|
5334
|
+
mani_in,
|
|
5335
|
+
)
|
|
5103
5336
|
|
|
5104
|
-
def
|
|
5105
|
-
|
|
5337
|
+
def _write_file_contents(self) -> None:
|
|
5338
|
+
fc = self.file_contents()
|
|
5106
5339
|
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
continue
|
|
5110
|
-
if iv.opts.debug:
|
|
5111
|
-
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
|
5112
|
-
for d in [False, True]:
|
|
5113
|
-
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
|
5340
|
+
with open(os.path.join(self._pkg_dir(), 'pyproject.toml'), 'w') as f:
|
|
5341
|
+
TomlWriter(f).write_root(fc.pyproject_dct)
|
|
5114
5342
|
|
|
5115
|
-
|
|
5343
|
+
if fc.manifest_in:
|
|
5344
|
+
with open(os.path.join(self._pkg_dir(), 'MANIFEST.in'), 'w') as f:
|
|
5345
|
+
f.write('\n'.join(fc.manifest_in)) # noqa
|
|
5116
5346
|
|
|
5117
|
-
|
|
5118
|
-
lst = self._get_installable_versions(spec)
|
|
5347
|
+
#
|
|
5119
5348
|
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5349
|
+
@cached_nullary
|
|
5350
|
+
def children(self) -> ta.Sequence[BasePyprojectPackageGenerator]:
|
|
5351
|
+
out: ta.List[BasePyprojectPackageGenerator] = []
|
|
5123
5352
|
|
|
5124
|
-
|
|
5353
|
+
if self.build_specs().setuptools.get('cexts'):
|
|
5354
|
+
out.append(_PyprojectCextPackageGenerator(
|
|
5355
|
+
self._dir_name,
|
|
5356
|
+
self._pkgs_root,
|
|
5357
|
+
pkg_suffix='-cext',
|
|
5358
|
+
))
|
|
5125
5359
|
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5360
|
+
if self.build_specs().pyproject.get('cli_scripts'):
|
|
5361
|
+
out.append(_PyprojectCliPackageGenerator(
|
|
5362
|
+
self._dir_name,
|
|
5363
|
+
self._pkgs_root,
|
|
5364
|
+
pkg_suffix='-cli',
|
|
5365
|
+
))
|
|
5132
5366
|
|
|
5133
|
-
|
|
5134
|
-
inst_version,
|
|
5135
|
-
interp_opts=inst_opts,
|
|
5136
|
-
)
|
|
5367
|
+
return out
|
|
5137
5368
|
|
|
5138
|
-
exe = installer.install()
|
|
5139
|
-
return Interp(exe, version)
|
|
5140
5369
|
|
|
5370
|
+
#
|
|
5141
5371
|
|
|
5142
|
-
########################################
|
|
5143
|
-
# ../../interp/system.py
|
|
5144
|
-
"""
|
|
5145
|
-
TODO:
|
|
5146
|
-
- python, python3, python3.12, ...
|
|
5147
|
-
- check if path py's are venvs: sys.prefix != sys.base_prefix
|
|
5148
|
-
"""
|
|
5149
5372
|
|
|
5373
|
+
class _PyprojectCextPackageGenerator(BasePyprojectPackageGenerator):
|
|
5150
5374
|
|
|
5151
|
-
|
|
5375
|
+
#
|
|
5152
5376
|
|
|
5377
|
+
@cached_nullary
|
|
5378
|
+
def find_cext_srcs(self) -> ta.Sequence[str]:
|
|
5379
|
+
return sorted(find_magic_files(
|
|
5380
|
+
CextMagic.STYLE,
|
|
5381
|
+
[self._dir_name],
|
|
5382
|
+
keys=[CextMagic.KEY],
|
|
5383
|
+
))
|
|
5153
5384
|
|
|
5154
|
-
|
|
5155
|
-
class SystemInterpProvider(InterpProvider):
|
|
5156
|
-
cmd: str = 'python3'
|
|
5157
|
-
path: ta.Optional[str] = None
|
|
5385
|
+
#
|
|
5158
5386
|
|
|
5159
|
-
|
|
5160
|
-
|
|
5387
|
+
@dc.dataclass(frozen=True)
|
|
5388
|
+
class FileContents:
|
|
5389
|
+
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
5390
|
+
setup_py: str
|
|
5161
5391
|
|
|
5162
|
-
|
|
5392
|
+
@cached_nullary
|
|
5393
|
+
def file_contents(self) -> FileContents:
|
|
5394
|
+
specs = self.build_specs()
|
|
5163
5395
|
|
|
5164
|
-
|
|
5165
|
-
def _re_which(
|
|
5166
|
-
pat: re.Pattern,
|
|
5167
|
-
*,
|
|
5168
|
-
mode: int = os.F_OK | os.X_OK,
|
|
5169
|
-
path: ta.Optional[str] = None,
|
|
5170
|
-
) -> ta.List[str]:
|
|
5171
|
-
if path is None:
|
|
5172
|
-
path = os.environ.get('PATH', None)
|
|
5173
|
-
if path is None:
|
|
5174
|
-
try:
|
|
5175
|
-
path = os.confstr('CS_PATH')
|
|
5176
|
-
except (AttributeError, ValueError):
|
|
5177
|
-
path = os.defpath
|
|
5396
|
+
#
|
|
5178
5397
|
|
|
5179
|
-
|
|
5180
|
-
return []
|
|
5398
|
+
pyp_dct = {}
|
|
5181
5399
|
|
|
5182
|
-
|
|
5183
|
-
|
|
5400
|
+
pyp_dct['build-system'] = {
|
|
5401
|
+
'requires': ['setuptools'],
|
|
5402
|
+
'build-backend': 'setuptools.build_meta',
|
|
5403
|
+
}
|
|
5184
5404
|
|
|
5185
|
-
|
|
5186
|
-
|
|
5405
|
+
prj = specs.pyproject
|
|
5406
|
+
prj['dependencies'] = [f'{prj["name"]} == {prj["version"]}']
|
|
5407
|
+
prj['name'] += self._pkg_suffix
|
|
5408
|
+
for k in [
|
|
5409
|
+
'optional_dependencies',
|
|
5410
|
+
'entry_points',
|
|
5411
|
+
'scripts',
|
|
5412
|
+
'cli_scripts',
|
|
5413
|
+
]:
|
|
5414
|
+
prj.pop(k, None)
|
|
5187
5415
|
|
|
5188
|
-
|
|
5189
|
-
seen = set()
|
|
5190
|
-
for d in pathlst:
|
|
5191
|
-
normdir = os.path.normcase(d)
|
|
5192
|
-
if normdir not in seen:
|
|
5193
|
-
seen.add(normdir)
|
|
5194
|
-
if not _access_check(normdir, mode):
|
|
5195
|
-
continue
|
|
5196
|
-
for thefile in os.listdir(d):
|
|
5197
|
-
name = os.path.join(d, thefile)
|
|
5198
|
-
if not (
|
|
5199
|
-
os.path.isfile(name) and
|
|
5200
|
-
pat.fullmatch(thefile) and
|
|
5201
|
-
_access_check(name, mode)
|
|
5202
|
-
):
|
|
5203
|
-
continue
|
|
5204
|
-
out.append(name)
|
|
5416
|
+
pyp_dct['project'] = prj
|
|
5205
5417
|
|
|
5206
|
-
|
|
5418
|
+
#
|
|
5207
5419
|
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5420
|
+
st = dict(specs.setuptools)
|
|
5421
|
+
pyp_dct['tool.setuptools'] = st
|
|
5422
|
+
|
|
5423
|
+
for k in [
|
|
5424
|
+
'cexts',
|
|
5425
|
+
|
|
5426
|
+
'find_packages',
|
|
5427
|
+
'package_data',
|
|
5428
|
+
'manifest_in',
|
|
5429
|
+
]:
|
|
5430
|
+
st.pop(k, None)
|
|
5431
|
+
|
|
5432
|
+
pyp_dct['tool.setuptools.packages.find'] = {
|
|
5433
|
+
'include': [],
|
|
5434
|
+
}
|
|
5435
|
+
|
|
5436
|
+
#
|
|
5437
|
+
|
|
5438
|
+
ext_lines = []
|
|
5439
|
+
|
|
5440
|
+
for ext_src in self.find_cext_srcs():
|
|
5441
|
+
ext_name = ext_src.rpartition('.')[0].replace(os.sep, '.')
|
|
5442
|
+
ext_lines.extend([
|
|
5443
|
+
'st.Extension(',
|
|
5444
|
+
f" name='{ext_name}',",
|
|
5445
|
+
f" sources=['{ext_src}'],",
|
|
5446
|
+
" extra_compile_args=['-std=c++20'],",
|
|
5447
|
+
'),',
|
|
5448
|
+
])
|
|
5449
|
+
|
|
5450
|
+
src = '\n'.join([
|
|
5451
|
+
'import setuptools as st',
|
|
5452
|
+
'',
|
|
5453
|
+
'',
|
|
5454
|
+
'st.setup(',
|
|
5455
|
+
' ext_modules=[',
|
|
5456
|
+
*[' ' + l for l in ext_lines],
|
|
5457
|
+
' ]',
|
|
5458
|
+
')',
|
|
5459
|
+
'',
|
|
5460
|
+
])
|
|
5461
|
+
|
|
5462
|
+
#
|
|
5463
|
+
|
|
5464
|
+
return self.FileContents(
|
|
5465
|
+
pyp_dct,
|
|
5466
|
+
src,
|
|
5213
5467
|
)
|
|
5214
5468
|
|
|
5215
|
-
|
|
5469
|
+
def _write_file_contents(self) -> None:
|
|
5470
|
+
fc = self.file_contents()
|
|
5216
5471
|
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
s = os.path.basename(exe)
|
|
5220
|
-
if s.startswith('python'):
|
|
5221
|
-
s = s[len('python'):]
|
|
5222
|
-
if '.' in s:
|
|
5223
|
-
try:
|
|
5224
|
-
return InterpVersion.parse(s)
|
|
5225
|
-
except InvalidVersion:
|
|
5226
|
-
pass
|
|
5227
|
-
ii = self.inspector.inspect(exe)
|
|
5228
|
-
return ii.iv if ii is not None else None
|
|
5472
|
+
with open(os.path.join(self._pkg_dir(), 'pyproject.toml'), 'w') as f:
|
|
5473
|
+
TomlWriter(f).write_root(fc.pyproject_dct)
|
|
5229
5474
|
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5475
|
+
with open(os.path.join(self._pkg_dir(), 'setup.py'), 'w') as f:
|
|
5476
|
+
f.write(fc.setup_py)
|
|
5477
|
+
|
|
5478
|
+
|
|
5479
|
+
##
|
|
5480
|
+
|
|
5481
|
+
|
|
5482
|
+
class _PyprojectCliPackageGenerator(BasePyprojectPackageGenerator):
|
|
5238
5483
|
|
|
5239
5484
|
#
|
|
5240
5485
|
|
|
5241
|
-
|
|
5242
|
-
|
|
5486
|
+
@dc.dataclass(frozen=True)
|
|
5487
|
+
class FileContents:
|
|
5488
|
+
pyproject_dct: ta.Mapping[str, ta.Any]
|
|
5243
5489
|
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5490
|
+
@cached_nullary
|
|
5491
|
+
def file_contents(self) -> FileContents:
|
|
5492
|
+
specs = self.build_specs()
|
|
5493
|
+
|
|
5494
|
+
#
|
|
5495
|
+
|
|
5496
|
+
pyp_dct = {}
|
|
5497
|
+
|
|
5498
|
+
pyp_dct['build-system'] = {
|
|
5499
|
+
'requires': ['setuptools'],
|
|
5500
|
+
'build-backend': 'setuptools.build_meta',
|
|
5501
|
+
}
|
|
5502
|
+
|
|
5503
|
+
prj = specs.pyproject
|
|
5504
|
+
prj['dependencies'] = [f'{prj["name"]} == {prj["version"]}']
|
|
5505
|
+
prj['name'] += self._pkg_suffix
|
|
5506
|
+
for k in [
|
|
5507
|
+
'optional_dependencies',
|
|
5508
|
+
'entry_points',
|
|
5509
|
+
'scripts',
|
|
5510
|
+
]:
|
|
5511
|
+
prj.pop(k, None)
|
|
5512
|
+
|
|
5513
|
+
pyp_dct['project'] = prj
|
|
5514
|
+
|
|
5515
|
+
if (scs := prj.pop('cli_scripts', None)):
|
|
5516
|
+
pyp_dct['project.scripts'] = scs
|
|
5517
|
+
|
|
5518
|
+
#
|
|
5519
|
+
|
|
5520
|
+
st = dict(specs.setuptools)
|
|
5521
|
+
pyp_dct['tool.setuptools'] = st
|
|
5522
|
+
|
|
5523
|
+
for k in [
|
|
5524
|
+
'cexts',
|
|
5525
|
+
|
|
5526
|
+
'find_packages',
|
|
5527
|
+
'package_data',
|
|
5528
|
+
'manifest_in',
|
|
5529
|
+
]:
|
|
5530
|
+
st.pop(k, None)
|
|
5531
|
+
|
|
5532
|
+
pyp_dct['tool.setuptools.packages.find'] = {
|
|
5533
|
+
'include': [],
|
|
5534
|
+
}
|
|
5535
|
+
|
|
5536
|
+
#
|
|
5537
|
+
|
|
5538
|
+
return self.FileContents(
|
|
5539
|
+
pyp_dct,
|
|
5540
|
+
)
|
|
5541
|
+
|
|
5542
|
+
def _write_file_contents(self) -> None:
|
|
5543
|
+
fc = self.file_contents()
|
|
5544
|
+
|
|
5545
|
+
with open(os.path.join(self._pkg_dir(), 'pyproject.toml'), 'w') as f:
|
|
5546
|
+
TomlWriter(f).write_root(fc.pyproject_dct)
|
|
5253
5547
|
|
|
5254
5548
|
|
|
5255
5549
|
########################################
|