omdev 0.0.0.dev11__py3-none-any.whl → 0.0.0.dev13__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/__about__.py +9 -5
- omdev/amalg/amalg.py +13 -1
- omdev/cmake.py +1 -2
- omdev/exts/cmake.py +297 -163
- omdev/exts/scan.py +2 -2
- omdev/scripts/findmagic.py +5 -2
- omdev/scripts/interp.py +5 -0
- omdev/scripts/pyproject.py +7 -0
- omdev/tools/revisions.py +173 -0
- omdev/wheelfile.py +246 -0
- omdev-0.0.0.dev13.dist-info/METADATA +33 -0
- {omdev-0.0.0.dev11.dist-info → omdev-0.0.0.dev13.dist-info}/RECORD +15 -13
- {omdev-0.0.0.dev11.dist-info → omdev-0.0.0.dev13.dist-info}/WHEEL +1 -1
- omdev-0.0.0.dev11.dist-info/METADATA +0 -30
- {omdev-0.0.0.dev11.dist-info → omdev-0.0.0.dev13.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev11.dist-info → omdev-0.0.0.dev13.dist-info}/top_level.txt +0 -0
omdev/__about__.py
CHANGED
|
@@ -13,17 +13,21 @@ class Project(ProjectBase):
|
|
|
13
13
|
|
|
14
14
|
optional_dependencies = {
|
|
15
15
|
'c': [
|
|
16
|
-
'pycparser
|
|
17
|
-
'cffi
|
|
18
|
-
'pcpp
|
|
16
|
+
'pycparser ~= 2.22',
|
|
17
|
+
'cffi ~= 1.17',
|
|
18
|
+
'pcpp ~= 1.30',
|
|
19
19
|
],
|
|
20
20
|
|
|
21
21
|
'mypy': [
|
|
22
|
-
'mypy
|
|
22
|
+
'mypy ~= 1.11',
|
|
23
23
|
],
|
|
24
24
|
|
|
25
25
|
'tokens': [
|
|
26
|
-
'tokenize_rt
|
|
26
|
+
'tokenize_rt ~= 6.0',
|
|
27
|
+
],
|
|
28
|
+
|
|
29
|
+
'wheel': [
|
|
30
|
+
'wheel ~= 0.44',
|
|
27
31
|
],
|
|
28
32
|
}
|
|
29
33
|
|
omdev/amalg/amalg.py
CHANGED
|
@@ -363,13 +363,25 @@ def gen_amalg(
|
|
|
363
363
|
|
|
364
364
|
##
|
|
365
365
|
|
|
366
|
+
tyd: dict[str, list[Typing]] = {}
|
|
366
367
|
tys = set()
|
|
367
368
|
for sf in sfs:
|
|
368
369
|
f = src_files[sf]
|
|
369
370
|
for ty in f.typings:
|
|
370
371
|
if ty.src not in tys:
|
|
371
|
-
|
|
372
|
+
tyd.setdefault(f.path, []).append(ty)
|
|
372
373
|
tys.add(ty.src)
|
|
374
|
+
for i, (sf, ftys) in enumerate(tyd.items()):
|
|
375
|
+
f = src_files[sf]
|
|
376
|
+
if i:
|
|
377
|
+
out.write('\n')
|
|
378
|
+
if f is not mf:
|
|
379
|
+
rp = os.path.relpath(f.path, mf.path)
|
|
380
|
+
else:
|
|
381
|
+
rp = os.path.basename(f.path)
|
|
382
|
+
out.write(f'# {rp}\n')
|
|
383
|
+
for ty in ftys:
|
|
384
|
+
out.write(ty.src)
|
|
373
385
|
if tys:
|
|
374
386
|
out.write('\n\n')
|
|
375
387
|
|
omdev/cmake.py
CHANGED
omdev/exts/cmake.py
CHANGED
|
@@ -9,6 +9,7 @@ TODO:
|
|
|
9
9
|
- json? https://github.com/nlohmann/json
|
|
10
10
|
- FindPackages? FetchContent? built_ext won't have that
|
|
11
11
|
- move omml git / data retriever stuff into omdev, get just the one header file from git via sha?
|
|
12
|
+
- support local built pys
|
|
12
13
|
|
|
13
14
|
==
|
|
14
15
|
|
|
@@ -17,191 +18,324 @@ Done:
|
|
|
17
18
|
- aight, generate a whole cmake subdir with symlinks to src files lol
|
|
18
19
|
|
|
19
20
|
""" # noqa
|
|
21
|
+
import argparse
|
|
22
|
+
import dataclasses as dc
|
|
20
23
|
import io
|
|
21
24
|
import logging
|
|
22
25
|
import os.path
|
|
23
26
|
import shutil
|
|
24
27
|
import sys
|
|
25
28
|
import sysconfig
|
|
29
|
+
import typing as ta
|
|
26
30
|
|
|
27
31
|
from omlish import check
|
|
32
|
+
from omlish import lang
|
|
28
33
|
from omlish import logs
|
|
29
34
|
|
|
30
35
|
from .. import cmake
|
|
36
|
+
from ..scripts import findmagic
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
log = logging.getLogger(__name__)
|
|
34
40
|
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
##
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
MAGIC = '@omdev-ext'
|
|
46
|
+
MAGIC_COMMENT = f'// {MAGIC}'
|
|
47
|
+
|
|
48
|
+
FILE_EXTENSIONS = ('c', 'cc', 'cpp')
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _sep_str_grps(*ls: ta.Sequence[str]) -> list[str]:
|
|
52
|
+
o = []
|
|
53
|
+
for i, l in enumerate(ls):
|
|
54
|
+
if not l:
|
|
55
|
+
continue
|
|
56
|
+
if i:
|
|
57
|
+
o.append('')
|
|
58
|
+
o.extend(check.not_isinstance(l, str))
|
|
59
|
+
return o
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class CmakeProjectGen:
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
exts: ta.Sequence[str],
|
|
66
|
+
prj_root: str | None = None,
|
|
67
|
+
) -> None:
|
|
68
|
+
super().__init__()
|
|
69
|
+
self._exts = check.not_isinstance(exts, str)
|
|
70
|
+
self._prj_root = os.path.abspath(prj_root) if prj_root is not None else os.getcwd()
|
|
71
|
+
|
|
72
|
+
#
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def prj_root(self) -> str:
|
|
76
|
+
return self._prj_root
|
|
77
|
+
|
|
78
|
+
@lang.cached_function
|
|
79
|
+
def prj_name(self) -> str:
|
|
80
|
+
return os.path.basename(self.prj_root)
|
|
81
|
+
|
|
82
|
+
@lang.cached_function
|
|
83
|
+
def cmake_dir(self) -> str:
|
|
84
|
+
cmake_dir = os.path.join(self.prj_root, 'cmake')
|
|
85
|
+
if os.path.exists(cmake_dir):
|
|
86
|
+
for e in os.listdir(cmake_dir):
|
|
87
|
+
if e == '.idea':
|
|
88
|
+
continue
|
|
89
|
+
ep = os.path.join(cmake_dir, e)
|
|
90
|
+
if os.path.isfile(ep):
|
|
91
|
+
os.unlink(ep)
|
|
92
|
+
else:
|
|
93
|
+
shutil.rmtree(ep)
|
|
94
|
+
else:
|
|
95
|
+
os.mkdir(cmake_dir)
|
|
96
|
+
return cmake_dir
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
|
|
100
|
+
def write_git_ignore(self) -> None:
|
|
101
|
+
with open(os.path.join(self.cmake_dir(), '.gitignore'), 'w') as f:
|
|
102
|
+
f.write('\n'.join(sorted(['/cmake-*', '/build'])))
|
|
103
|
+
|
|
104
|
+
#
|
|
105
|
+
|
|
106
|
+
def write_idea_name(self) -> None:
|
|
107
|
+
idea_dir = os.path.join(self.cmake_dir(), '.idea')
|
|
108
|
+
if not os.path.isdir(idea_dir):
|
|
109
|
+
os.mkdir(idea_dir)
|
|
110
|
+
idea_name_file = os.path.join(idea_dir, '.name')
|
|
111
|
+
if not os.path.isfile(idea_name_file):
|
|
112
|
+
with open(idea_name_file, 'w') as f:
|
|
113
|
+
f.write(self.prj_name())
|
|
38
114
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
115
|
+
#
|
|
116
|
+
|
|
117
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
118
|
+
class PyInfo:
|
|
119
|
+
venv_exe: str
|
|
120
|
+
venv_root: str
|
|
121
|
+
real_exe: str
|
|
122
|
+
root: str
|
|
123
|
+
suffix: str
|
|
124
|
+
|
|
125
|
+
@lang.cached_function
|
|
126
|
+
def py_info(self) -> PyInfo:
|
|
127
|
+
venv_exe = sys.executable
|
|
128
|
+
real_exe = os.path.realpath(venv_exe)
|
|
129
|
+
return self.PyInfo(
|
|
130
|
+
venv_exe=venv_exe,
|
|
131
|
+
venv_root=os.path.abspath(os.path.join(os.path.dirname(venv_exe), '..')),
|
|
132
|
+
real_exe=real_exe,
|
|
133
|
+
root=os.path.abspath(os.path.join(os.path.dirname(real_exe), '..')),
|
|
134
|
+
suffix='.'.join(map(str, sys.version_info[:2])),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
#
|
|
138
|
+
|
|
139
|
+
@lang.cached_function
|
|
140
|
+
def ext_files(self) -> ta.Sequence[str]:
|
|
141
|
+
out = []
|
|
142
|
+
for e in self._exts:
|
|
143
|
+
e = os.path.abspath(e)
|
|
144
|
+
if os.path.isfile(e):
|
|
145
|
+
out.append(e)
|
|
146
|
+
elif os.path.isdir(e):
|
|
147
|
+
out.extend(
|
|
148
|
+
findmagic.find_magic(
|
|
149
|
+
[e],
|
|
150
|
+
[MAGIC_COMMENT],
|
|
151
|
+
FILE_EXTENSIONS,
|
|
152
|
+
),
|
|
153
|
+
)
|
|
51
154
|
else:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
'.',
|
|
159
|
-
sysconfig.get_config_var('SOABI'),
|
|
160
|
-
sysconfig.get_config_var('SHLIB_SUFFIX'),
|
|
161
|
-
])
|
|
162
|
-
|
|
163
|
-
sl = os.path.join(cmake_dir, ext_src)
|
|
164
|
-
sal = os.path.abspath(sl)
|
|
165
|
-
sd = os.path.dirname(sal)
|
|
166
|
-
os.makedirs(sd, exist_ok=True)
|
|
167
|
-
rp = os.path.relpath(os.path.abspath(ext_src), sd)
|
|
168
|
-
os.symlink(rp, sal)
|
|
169
|
-
|
|
170
|
-
gen.write_target(cmake.ModuleLibrary(
|
|
171
|
-
ext_name,
|
|
172
|
-
src_files=[
|
|
173
|
-
sl,
|
|
174
|
-
],
|
|
175
|
-
include_dirs=[
|
|
176
|
-
f'${{{var_prefix}_INCLUDE_DIRECTORIES}}',
|
|
177
|
-
],
|
|
178
|
-
compile_opts=[
|
|
179
|
-
f'${{{var_prefix}_COMPILE_OPTIONS}}',
|
|
180
|
-
],
|
|
181
|
-
link_dirs=[
|
|
182
|
-
f'${{{var_prefix}_LINK_DIRECTORIES}}',
|
|
183
|
-
],
|
|
184
|
-
link_libs=[
|
|
185
|
-
f'${{{var_prefix}_LINK_LIBRARIES}}',
|
|
186
|
-
],
|
|
187
|
-
extra_cmds=[
|
|
188
|
-
cmake.Command(
|
|
189
|
-
'add_custom_command',
|
|
190
|
-
['TARGET', ext_name, 'POST_BUILD'],
|
|
155
|
+
raise KeyError(e)
|
|
156
|
+
return out
|
|
157
|
+
|
|
158
|
+
#
|
|
159
|
+
|
|
160
|
+
class _CmakeListsGen:
|
|
161
|
+
def __init__(
|
|
162
|
+
self,
|
|
163
|
+
p: 'CmakeProjectGen',
|
|
164
|
+
out: ta.TextIO,
|
|
165
|
+
) -> None:
|
|
166
|
+
super().__init__()
|
|
167
|
+
self.p = p
|
|
168
|
+
self.g = cmake.CmakeGen(out)
|
|
169
|
+
|
|
170
|
+
@lang.cached_property
|
|
171
|
+
def var_prefix(self) -> str:
|
|
172
|
+
return self.p.prj_name().upper()
|
|
173
|
+
|
|
174
|
+
@lang.cached_property
|
|
175
|
+
def py(self) -> 'CmakeProjectGen.PyInfo':
|
|
176
|
+
return self.p.py_info()
|
|
177
|
+
|
|
178
|
+
def _add_ext(self, ext_src: str) -> None:
|
|
179
|
+
ext_name = ext_src.rpartition('.')[0].replace('/', '__')
|
|
180
|
+
|
|
181
|
+
log.info('Adding cmake c extension: %s -> %s', ext_src, ext_name)
|
|
182
|
+
|
|
183
|
+
so_name = ''.join([
|
|
184
|
+
os.path.basename(ext_src).split('.')[0],
|
|
185
|
+
'.',
|
|
186
|
+
sysconfig.get_config_var('SOABI'),
|
|
187
|
+
sysconfig.get_config_var('SHLIB_SUFFIX'),
|
|
188
|
+
])
|
|
189
|
+
|
|
190
|
+
sl = os.path.join(self.p.cmake_dir(), ext_src)
|
|
191
|
+
sal = os.path.abspath(sl)
|
|
192
|
+
sd = os.path.dirname(sal)
|
|
193
|
+
os.makedirs(sd, exist_ok=True)
|
|
194
|
+
rp = os.path.relpath(os.path.abspath(ext_src), sd)
|
|
195
|
+
os.symlink(rp, sal)
|
|
196
|
+
|
|
197
|
+
ml = cmake.ModuleLibrary(
|
|
198
|
+
ext_name,
|
|
199
|
+
src_files=[
|
|
200
|
+
sl,
|
|
201
|
+
],
|
|
202
|
+
include_dirs=[
|
|
203
|
+
f'${{{self.var_prefix}_INCLUDE_DIRECTORIES}}',
|
|
204
|
+
],
|
|
205
|
+
compile_opts=[
|
|
206
|
+
f'${{{self.var_prefix}_COMPILE_OPTIONS}}',
|
|
207
|
+
],
|
|
208
|
+
link_dirs=[
|
|
209
|
+
f'${{{self.var_prefix}_LINK_DIRECTORIES}}',
|
|
210
|
+
],
|
|
211
|
+
link_libs=[
|
|
212
|
+
f'${{{self.var_prefix}_LINK_LIBRARIES}}',
|
|
213
|
+
],
|
|
214
|
+
extra_cmds=[
|
|
215
|
+
cmake.Command(
|
|
216
|
+
'add_custom_command',
|
|
217
|
+
['TARGET', ext_name, 'POST_BUILD'],
|
|
218
|
+
[
|
|
219
|
+
' '.join([
|
|
220
|
+
'COMMAND ${CMAKE_COMMAND} -E ',
|
|
221
|
+
f'copy $<TARGET_FILE_NAME:{ext_name}> ../../{os.path.dirname(ext_src)}/{so_name}',
|
|
222
|
+
]),
|
|
223
|
+
'COMMAND_EXPAND_LISTS',
|
|
224
|
+
],
|
|
225
|
+
),
|
|
226
|
+
],
|
|
227
|
+
)
|
|
228
|
+
self.g.write_target(ml)
|
|
229
|
+
|
|
230
|
+
def run(self) -> None:
|
|
231
|
+
self.g.write(self.g.preamble)
|
|
232
|
+
self.g.write('')
|
|
233
|
+
|
|
234
|
+
self.g.write(f'project({self.p.prj_name()})')
|
|
235
|
+
self.g.write('')
|
|
236
|
+
|
|
237
|
+
self.g.write_var(cmake.Var(
|
|
238
|
+
f'{self.var_prefix}_INCLUDE_DIRECTORIES',
|
|
239
|
+
_sep_str_grps(
|
|
240
|
+
[f'{self.py.venv_root}/include'],
|
|
241
|
+
[f'{self.py.root}/include/python{self.py.suffix}'],
|
|
242
|
+
[
|
|
243
|
+
# $ENV{HOME}/src/python/cpython
|
|
244
|
+
# $ENV{HOME}/src/python/cpython/include
|
|
245
|
+
],
|
|
246
|
+
),
|
|
247
|
+
))
|
|
248
|
+
|
|
249
|
+
self.g.write_var(cmake.Var(
|
|
250
|
+
f'{self.var_prefix}_COMPILE_OPTIONS',
|
|
251
|
+
_sep_str_grps(
|
|
252
|
+
[
|
|
253
|
+
'-Wsign-compare',
|
|
254
|
+
'-Wunreachable-code',
|
|
255
|
+
'-DNDEBUG',
|
|
256
|
+
'-g',
|
|
257
|
+
'-fwrapv',
|
|
258
|
+
'-O3',
|
|
259
|
+
'-Wall',
|
|
260
|
+
],
|
|
191
261
|
[
|
|
192
|
-
'
|
|
193
|
-
|
|
194
|
-
f'copy $<TARGET_FILE_NAME:{ext_name}> ../../{os.path.dirname(ext_src)}/{so_name}',
|
|
195
|
-
]),
|
|
196
|
-
'COMMAND_EXPAND_LISTS',
|
|
262
|
+
'-g',
|
|
263
|
+
'-c',
|
|
197
264
|
],
|
|
265
|
+
['-std=c++20'],
|
|
266
|
+
),
|
|
267
|
+
))
|
|
268
|
+
|
|
269
|
+
self.g.write_var(cmake.Var(
|
|
270
|
+
f'{self.var_prefix}_LINK_DIRECTORIES',
|
|
271
|
+
_sep_str_grps(
|
|
272
|
+
[f'{self.py.root}/lib'],
|
|
273
|
+
# ['$ENV{HOME}/src/python/cpython'],
|
|
274
|
+
),
|
|
275
|
+
))
|
|
276
|
+
|
|
277
|
+
self.g.write_var(cmake.Var(
|
|
278
|
+
f'{self.var_prefix}_LINK_LIBRARIES',
|
|
279
|
+
_sep_str_grps(
|
|
280
|
+
*([[
|
|
281
|
+
'-bundle',
|
|
282
|
+
'"-undefined dynamic_lookup"',
|
|
283
|
+
]] if sys.platform == 'darwin' else []),
|
|
198
284
|
),
|
|
199
|
-
|
|
200
|
-
|
|
285
|
+
))
|
|
286
|
+
|
|
287
|
+
for ext_src in self.p.ext_files():
|
|
288
|
+
self._add_ext(os.path.relpath(ext_src, self.p.prj_root))
|
|
289
|
+
|
|
290
|
+
#
|
|
291
|
+
|
|
292
|
+
def run(self) -> None:
|
|
293
|
+
if not os.path.isfile(os.path.join(self._prj_root, 'pyproject.toml')):
|
|
294
|
+
raise Exception('Must be run in project root')
|
|
295
|
+
|
|
296
|
+
self.ext_files()
|
|
297
|
+
|
|
298
|
+
log.info('Generating cmake project %s', self.prj_name())
|
|
201
299
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
300
|
+
self.cmake_dir()
|
|
301
|
+
self.write_git_ignore()
|
|
302
|
+
self.write_idea_name()
|
|
303
|
+
|
|
304
|
+
out = io.StringIO()
|
|
305
|
+
clg = self._CmakeListsGen(self, out)
|
|
306
|
+
clg.run()
|
|
307
|
+
|
|
308
|
+
with open(os.path.join(self.cmake_dir(), 'CMakeLists.txt'), 'w') as f:
|
|
309
|
+
f.write(out.getvalue())
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
##
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _gen_cmd(args) -> None:
|
|
316
|
+
if not args.exts:
|
|
317
|
+
raise Exception('must specify exts')
|
|
318
|
+
|
|
319
|
+
cpg = CmakeProjectGen(args.exts)
|
|
320
|
+
cpg.run()
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _main(argv=None) -> None:
|
|
324
|
+
logs.configure_standard_logging('INFO')
|
|
325
|
+
|
|
326
|
+
parser = argparse.ArgumentParser()
|
|
327
|
+
|
|
328
|
+
subparsers = parser.add_subparsers()
|
|
329
|
+
|
|
330
|
+
parser_gen = subparsers.add_parser('gen')
|
|
331
|
+
parser_gen.add_argument('exts', nargs='*')
|
|
332
|
+
parser_gen.set_defaults(func=_gen_cmd)
|
|
333
|
+
|
|
334
|
+
args = parser.parse_args(argv)
|
|
335
|
+
if not getattr(args, 'func', None):
|
|
336
|
+
parser.print_help()
|
|
337
|
+
else:
|
|
338
|
+
args.func(args)
|
|
205
339
|
|
|
206
340
|
|
|
207
341
|
if __name__ == '__main__':
|
omdev/exts/scan.py
CHANGED
|
@@ -12,7 +12,7 @@ log = logging.getLogger(__name__)
|
|
|
12
12
|
SCAN_COMMENT = '// @omdev-ext'
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
def
|
|
15
|
+
def scan_one(
|
|
16
16
|
input_path: str,
|
|
17
17
|
**kwargs: ta.Any,
|
|
18
18
|
) -> None:
|
|
@@ -42,7 +42,7 @@ def _scan_cmd(args) -> None:
|
|
|
42
42
|
log.info('Scanning %s', i)
|
|
43
43
|
for we_dirpath, we_dirnames, we_filenames in os.walk(i): # noqa
|
|
44
44
|
for fname in we_filenames:
|
|
45
|
-
|
|
45
|
+
scan_one(
|
|
46
46
|
os.path.abspath(os.path.join(we_dirpath, fname)),
|
|
47
47
|
)
|
|
48
48
|
|
omdev/scripts/findmagic.py
CHANGED
|
@@ -28,8 +28,11 @@ def find_magic(
|
|
|
28
28
|
continue
|
|
29
29
|
|
|
30
30
|
fp = os.path.join(dp, fn)
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
try:
|
|
32
|
+
with open(fp) as f:
|
|
33
|
+
src = f.read()
|
|
34
|
+
except UnicodeDecodeError:
|
|
35
|
+
continue
|
|
33
36
|
|
|
34
37
|
if not any(
|
|
35
38
|
any(pat.fullmatch(l) for pat in pats)
|
omdev/scripts/interp.py
CHANGED
|
@@ -29,13 +29,18 @@ import threading
|
|
|
29
29
|
import typing as ta
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
# ../../versioning/versions.py
|
|
32
33
|
VersionLocalType = ta.Tuple[ta.Union[int, str], ...]
|
|
33
34
|
VersionCmpPrePostDevType = ta.Union['InfinityVersionType', 'NegativeInfinityVersionType', ta.Tuple[str, int]]
|
|
34
35
|
_VersionCmpLocalType0 = ta.Tuple[ta.Union[ta.Tuple[int, str], ta.Tuple['NegativeInfinityVersionType', ta.Union[int, str]]], ...] # noqa
|
|
35
36
|
VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalType0]
|
|
36
37
|
VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
|
|
37
38
|
VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
|
|
39
|
+
|
|
40
|
+
# ../../../omlish/lite/check.py
|
|
38
41
|
T = ta.TypeVar('T')
|
|
42
|
+
|
|
43
|
+
# ../../versioning/specifiers.py
|
|
39
44
|
UnparsedVersion = ta.Union['Version', str]
|
|
40
45
|
UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
|
|
41
46
|
CallableVersionOperator = ta.Callable[['Version', str], bool]
|
omdev/scripts/pyproject.py
CHANGED
|
@@ -55,16 +55,23 @@ import uuid
|
|
|
55
55
|
import weakref # noqa
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
# ../../toml/parser.py
|
|
58
59
|
TomlParseFloat = ta.Callable[[str], ta.Any]
|
|
59
60
|
TomlKey = ta.Tuple[str, ...]
|
|
60
61
|
TomlPos = int # ta.TypeAlias
|
|
62
|
+
|
|
63
|
+
# ../../versioning/versions.py
|
|
61
64
|
VersionLocalType = ta.Tuple[ta.Union[int, str], ...]
|
|
62
65
|
VersionCmpPrePostDevType = ta.Union['InfinityVersionType', 'NegativeInfinityVersionType', ta.Tuple[str, int]]
|
|
63
66
|
_VersionCmpLocalType0 = ta.Tuple[ta.Union[ta.Tuple[int, str], ta.Tuple['NegativeInfinityVersionType', ta.Union[int, str]]], ...] # noqa
|
|
64
67
|
VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalType0]
|
|
65
68
|
VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
|
|
66
69
|
VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
|
|
70
|
+
|
|
71
|
+
# ../../../omlish/lite/check.py
|
|
67
72
|
T = ta.TypeVar('T')
|
|
73
|
+
|
|
74
|
+
# ../../versioning/specifiers.py
|
|
68
75
|
UnparsedVersion = ta.Union['Version', str]
|
|
69
76
|
UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
|
|
70
77
|
CallableVersionOperator = ta.Callable[['Version', str], bool]
|
omdev/tools/revisions.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- omlish-lite, move to pyproject/
|
|
4
|
+
- vendor-lite wheel.wheelfile
|
|
5
|
+
"""
|
|
6
|
+
# ruff: noqa: TCH003 UP006 UP007
|
|
7
|
+
# @omlish-lite
|
|
8
|
+
import argparse
|
|
9
|
+
import io
|
|
10
|
+
import os.path
|
|
11
|
+
import subprocess
|
|
12
|
+
import tarfile
|
|
13
|
+
import typing as ta
|
|
14
|
+
import zipfile
|
|
15
|
+
|
|
16
|
+
from omlish.lite.logs import configure_standard_logging
|
|
17
|
+
from omlish.lite.logs import log
|
|
18
|
+
|
|
19
|
+
from ..wheelfile import WheelFile
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RevisionAdder:
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
revision: str,
|
|
26
|
+
output_suffix: ta.Optional[str] = None,
|
|
27
|
+
) -> None:
|
|
28
|
+
super().__init__()
|
|
29
|
+
self._revision = revision
|
|
30
|
+
self._output_suffix = output_suffix
|
|
31
|
+
|
|
32
|
+
REVISION_ATTR = '__revision__'
|
|
33
|
+
|
|
34
|
+
def add_to_contents(self, dct: ta.Dict[str, bytes]) -> bool:
|
|
35
|
+
changed = False
|
|
36
|
+
for n in dct:
|
|
37
|
+
if not n.endswith('__about__.py'):
|
|
38
|
+
continue
|
|
39
|
+
src = dct[n].decode('utf-8')
|
|
40
|
+
lines = src.splitlines(keepends=True)
|
|
41
|
+
for i, l in enumerate(lines):
|
|
42
|
+
if l != f'{self.REVISION_ATTR} = None\n':
|
|
43
|
+
continue
|
|
44
|
+
lines[i] = f"{self.REVISION_ATTR} = '{self._revision}'\n"
|
|
45
|
+
changed = True
|
|
46
|
+
dct[n] = ''.join(lines).encode('utf-8')
|
|
47
|
+
return changed
|
|
48
|
+
|
|
49
|
+
def add_to_wheel(self, f: str) -> None:
|
|
50
|
+
if not f.endswith('.whl'):
|
|
51
|
+
raise Exception(f)
|
|
52
|
+
log.info('Scanning wheel %s', f)
|
|
53
|
+
|
|
54
|
+
zis: ta.Dict[str, zipfile.ZipInfo] = {}
|
|
55
|
+
dct: ta.Dict[str, bytes] = {}
|
|
56
|
+
with WheelFile(f) as wf:
|
|
57
|
+
for zi in wf.filelist:
|
|
58
|
+
if zi.filename == wf.record_path:
|
|
59
|
+
continue
|
|
60
|
+
zis[zi.filename] = zi
|
|
61
|
+
dct[zi.filename] = wf.read(zi.filename)
|
|
62
|
+
|
|
63
|
+
if self.add_to_contents(dct):
|
|
64
|
+
of = f[:-4] + (self._output_suffix or '') + '.whl'
|
|
65
|
+
log.info('Repacking wheel %s', of)
|
|
66
|
+
with WheelFile(of, 'w') as wf:
|
|
67
|
+
for n, d in dct.items():
|
|
68
|
+
log.info('Adding zipinfo %s', n)
|
|
69
|
+
wf.writestr(zis[n], d)
|
|
70
|
+
|
|
71
|
+
def add_to_tgz(self, f: str) -> None:
|
|
72
|
+
if not f.endswith('.tar.gz'):
|
|
73
|
+
raise Exception(f)
|
|
74
|
+
log.info('Scanning tgz %s', f)
|
|
75
|
+
|
|
76
|
+
tis: ta.Dict[str, tarfile.TarInfo] = {}
|
|
77
|
+
dct: ta.Dict[str, bytes] = {}
|
|
78
|
+
with tarfile.open(f, 'r:gz') as tf:
|
|
79
|
+
for ti in tf:
|
|
80
|
+
tis[ti.name] = ti
|
|
81
|
+
if ti.type == tarfile.REGTYPE:
|
|
82
|
+
with tf.extractfile(ti.name) as tif: # type: ignore
|
|
83
|
+
dct[ti.name] = tif.read()
|
|
84
|
+
|
|
85
|
+
if self.add_to_contents(dct):
|
|
86
|
+
of = f[:-7] + (self._output_suffix or '') + '.tar.gz'
|
|
87
|
+
log.info('Repacking tgz %s', of)
|
|
88
|
+
with tarfile.open(of, 'w:gz') as tf:
|
|
89
|
+
for n, ti in tis.items():
|
|
90
|
+
log.info('Adding tarinfo %s', n)
|
|
91
|
+
if n in dct:
|
|
92
|
+
data = dct[n]
|
|
93
|
+
ti.size = len(data)
|
|
94
|
+
fo = io.BytesIO(data)
|
|
95
|
+
else:
|
|
96
|
+
fo = None
|
|
97
|
+
tf.addfile(ti, fileobj=fo)
|
|
98
|
+
|
|
99
|
+
EXTS = ('.tar.gz', '.whl')
|
|
100
|
+
|
|
101
|
+
def add_to_file(self, f: str) -> None:
|
|
102
|
+
if f.endswith('.whl'):
|
|
103
|
+
self.add_to_wheel(f)
|
|
104
|
+
|
|
105
|
+
elif f.endswith('.tar.gz'):
|
|
106
|
+
self.add_to_tgz(f)
|
|
107
|
+
|
|
108
|
+
def add_to(self, tgt: str) -> None:
|
|
109
|
+
if os.path.isfile(tgt):
|
|
110
|
+
self.add_to_file(tgt)
|
|
111
|
+
|
|
112
|
+
elif os.path.isdir(tgt):
|
|
113
|
+
for dp, dns, fns in os.walk(tgt): # noqa
|
|
114
|
+
for f in fns:
|
|
115
|
+
if any(f.endswith(ext) for ext in self.EXTS):
|
|
116
|
+
self.add_to_file(os.path.join(dp, f))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_revision() -> str:
|
|
123
|
+
return subprocess.check_output([
|
|
124
|
+
'git',
|
|
125
|
+
'describe',
|
|
126
|
+
'--match=NeVeRmAtCh',
|
|
127
|
+
'--always',
|
|
128
|
+
'--abbrev=40',
|
|
129
|
+
'--dirty',
|
|
130
|
+
]).decode().strip()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
#
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _add_cmd(args) -> None:
|
|
137
|
+
if (revision := args.revision) is None:
|
|
138
|
+
revision = get_revision()
|
|
139
|
+
log.info('Using revision %s', revision)
|
|
140
|
+
|
|
141
|
+
if not args.targets:
|
|
142
|
+
raise Exception('must specify targets')
|
|
143
|
+
|
|
144
|
+
ra = RevisionAdder(
|
|
145
|
+
revision,
|
|
146
|
+
output_suffix=args.suffix,
|
|
147
|
+
)
|
|
148
|
+
for tgt in args.targets:
|
|
149
|
+
ra.add_to(tgt)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _main(argv=None) -> None:
|
|
153
|
+
configure_standard_logging('INFO')
|
|
154
|
+
|
|
155
|
+
parser = argparse.ArgumentParser()
|
|
156
|
+
|
|
157
|
+
subparsers = parser.add_subparsers()
|
|
158
|
+
|
|
159
|
+
parser_add = subparsers.add_parser('add')
|
|
160
|
+
parser_add.add_argument('-r', '--revision')
|
|
161
|
+
parser_add.add_argument('-s', '--suffix')
|
|
162
|
+
parser_add.add_argument('targets', nargs='*')
|
|
163
|
+
parser_add.set_defaults(func=_add_cmd)
|
|
164
|
+
|
|
165
|
+
args = parser.parse_args(argv)
|
|
166
|
+
if not getattr(args, 'func', None):
|
|
167
|
+
parser.print_help()
|
|
168
|
+
else:
|
|
169
|
+
args.func(args)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == '__main__':
|
|
173
|
+
_main()
|
omdev/wheelfile.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# @omlish-lite
|
|
2
|
+
# ruff: noqa: UP006 UP007
|
|
3
|
+
# https://github.com/pypa/wheel/blob/7bb46d7727e6e89fe56b3c78297b3af2672bbbe2/src/wheel/wheelfile.py
|
|
4
|
+
# MIT License
|
|
5
|
+
#
|
|
6
|
+
# Copyright (c) 2012 Daniel Holth <dholth@fastmail.fm> and contributors
|
|
7
|
+
#
|
|
8
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
9
|
+
# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
|
10
|
+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
11
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
|
14
|
+
# Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
17
|
+
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
18
|
+
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
19
|
+
# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
20
|
+
import base64
|
|
21
|
+
import csv
|
|
22
|
+
import hashlib
|
|
23
|
+
import io
|
|
24
|
+
import os.path
|
|
25
|
+
import re
|
|
26
|
+
import stat
|
|
27
|
+
import time
|
|
28
|
+
import typing as ta
|
|
29
|
+
import zipfile
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class WheelError(Exception):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Non-greedy matching of an optional build number may be too clever (more invalid wheel filenames will match). Separate
|
|
37
|
+
# regex for .dist-info?
|
|
38
|
+
WHEEL_INFO_RE = re.compile(
|
|
39
|
+
r'^'
|
|
40
|
+
r'(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]+?))'
|
|
41
|
+
r'(-(?P<build>\d[^\s-]*))?-'
|
|
42
|
+
r'(?P<pyver>[^\s-]+?)-'
|
|
43
|
+
r'(?P<abi>[^\s-]+?)-'
|
|
44
|
+
r'(?P<plat>\S+)'
|
|
45
|
+
r'\.whl$',
|
|
46
|
+
re.VERBOSE,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class WheelFile(zipfile.ZipFile):
|
|
51
|
+
"""
|
|
52
|
+
A ZipFile derivative class that also reads SHA-256 hashes from .dist-info/RECORD and checks any read files against
|
|
53
|
+
those.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
_default_algorithm = hashlib.sha256
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
file: str,
|
|
61
|
+
mode: str = 'r', # ta.Literal["r", "w", "x", "a"]
|
|
62
|
+
compression: int = zipfile.ZIP_DEFLATED,
|
|
63
|
+
) -> None:
|
|
64
|
+
basename = os.path.basename(file)
|
|
65
|
+
self.parsed_filename = WHEEL_INFO_RE.match(basename)
|
|
66
|
+
if not basename.endswith('.whl') or self.parsed_filename is None:
|
|
67
|
+
raise WheelError(f'Bad wheel filename {basename!r}')
|
|
68
|
+
|
|
69
|
+
super().__init__( # type: ignore
|
|
70
|
+
file,
|
|
71
|
+
mode,
|
|
72
|
+
compression=compression,
|
|
73
|
+
allowZip64=True,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self.dist_info_path = '{}.dist-info'.format(self.parsed_filename.group('namever'))
|
|
77
|
+
self.record_path = self.dist_info_path + '/RECORD'
|
|
78
|
+
self._file_hashes: ta.Dict[str, ta.Union[ta.Tuple[None, None], ta.Tuple[int, bytes]]] = {}
|
|
79
|
+
self._file_sizes: ta.Dict[str, int] = {}
|
|
80
|
+
|
|
81
|
+
if mode == 'r':
|
|
82
|
+
# Ignore RECORD and any embedded wheel signatures
|
|
83
|
+
self._file_hashes[self.record_path] = None, None
|
|
84
|
+
self._file_hashes[self.record_path + '.jws'] = None, None
|
|
85
|
+
self._file_hashes[self.record_path + '.p7s'] = None, None
|
|
86
|
+
|
|
87
|
+
# Fill in the expected hashes by reading them from RECORD
|
|
88
|
+
try:
|
|
89
|
+
record = self.open(self.record_path)
|
|
90
|
+
except KeyError:
|
|
91
|
+
raise WheelError(f'Missing {self.record_path} file') from None
|
|
92
|
+
|
|
93
|
+
with record:
|
|
94
|
+
for line in csv.reader(io.TextIOWrapper(record, newline='', encoding='utf-8')):
|
|
95
|
+
path, hash_sum, size = line
|
|
96
|
+
if not hash_sum:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
algorithm, hash_sum = hash_sum.split('=')
|
|
100
|
+
try:
|
|
101
|
+
hashlib.new(algorithm)
|
|
102
|
+
except ValueError:
|
|
103
|
+
raise WheelError(f'Unsupported hash algorithm: {algorithm}') from None
|
|
104
|
+
|
|
105
|
+
if algorithm.lower() in {'md5', 'sha1'}:
|
|
106
|
+
raise WheelError(f'Weak hash algorithm ({algorithm}) is not permitted by PEP 427')
|
|
107
|
+
|
|
108
|
+
self._file_hashes[path] = ( # type: ignore
|
|
109
|
+
algorithm,
|
|
110
|
+
self._urlsafe_b64decode(hash_sum.encode('ascii')),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
@staticmethod
|
|
114
|
+
def _urlsafe_b64encode(data: bytes) -> bytes:
|
|
115
|
+
"""urlsafe_b64encode without padding"""
|
|
116
|
+
return base64.urlsafe_b64encode(data).rstrip(b'=')
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def _urlsafe_b64decode(data: bytes) -> bytes:
|
|
120
|
+
"""urlsafe_b64decode without padding"""
|
|
121
|
+
pad = b'=' * (4 - (len(data) & 3))
|
|
122
|
+
return base64.urlsafe_b64decode(data + pad)
|
|
123
|
+
|
|
124
|
+
def open( # type: ignore # noqa
|
|
125
|
+
self,
|
|
126
|
+
name_or_info: ta.Union[str, zipfile.ZipInfo],
|
|
127
|
+
mode: str = 'r', # ta.Literal["r", "w"]
|
|
128
|
+
pwd: ta.Optional[bytes] = None,
|
|
129
|
+
) -> ta.IO[bytes]:
|
|
130
|
+
def _update_crc(newdata: bytes) -> None:
|
|
131
|
+
eof = ef._eof # type: ignore # noqa
|
|
132
|
+
update_crc_orig(newdata)
|
|
133
|
+
running_hash.update(newdata)
|
|
134
|
+
if eof and running_hash.digest() != expected_hash:
|
|
135
|
+
raise WheelError(f"Hash mismatch for file '{ef_name}'")
|
|
136
|
+
|
|
137
|
+
ef_name = name_or_info.filename if isinstance(name_or_info, zipfile.ZipInfo) else name_or_info
|
|
138
|
+
if (
|
|
139
|
+
mode == 'r'
|
|
140
|
+
and not ef_name.endswith('/')
|
|
141
|
+
and ef_name not in self._file_hashes
|
|
142
|
+
):
|
|
143
|
+
raise WheelError(f"No hash found for file '{ef_name}'")
|
|
144
|
+
|
|
145
|
+
ef = super().open(name_or_info, mode, pwd) # noqa
|
|
146
|
+
if mode == 'r' and not ef_name.endswith('/'):
|
|
147
|
+
algorithm, expected_hash = self._file_hashes[ef_name]
|
|
148
|
+
if expected_hash is not None:
|
|
149
|
+
# Monkey patch the _update_crc method to also check for the hash from RECORD
|
|
150
|
+
running_hash = hashlib.new(algorithm) # type: ignore
|
|
151
|
+
update_crc_orig, ef._update_crc = ef._update_crc, _update_crc # type: ignore # noqa
|
|
152
|
+
|
|
153
|
+
return ef
|
|
154
|
+
|
|
155
|
+
def write_files(self, base_dir: str) -> None:
|
|
156
|
+
deferred: list[tuple[str, str]] = []
|
|
157
|
+
for root, dirnames, filenames in os.walk(base_dir):
|
|
158
|
+
# Sort the directory names so that `os.walk` will walk them in a defined order on the next iteration.
|
|
159
|
+
dirnames.sort()
|
|
160
|
+
for name in sorted(filenames):
|
|
161
|
+
path = os.path.normpath(os.path.join(root, name))
|
|
162
|
+
if os.path.isfile(path):
|
|
163
|
+
arcname = os.path.relpath(path, base_dir).replace(os.path.sep, '/')
|
|
164
|
+
if arcname == self.record_path:
|
|
165
|
+
pass
|
|
166
|
+
elif root.endswith('.dist-info'):
|
|
167
|
+
deferred.append((path, arcname))
|
|
168
|
+
else:
|
|
169
|
+
self.write(path, arcname)
|
|
170
|
+
|
|
171
|
+
deferred.sort()
|
|
172
|
+
for path, arcname in deferred:
|
|
173
|
+
self.write(path, arcname)
|
|
174
|
+
|
|
175
|
+
def write( # type: ignore # noqa
|
|
176
|
+
self,
|
|
177
|
+
filename: str,
|
|
178
|
+
arcname: ta.Optional[str] = None,
|
|
179
|
+
compress_type: ta.Optional[int] = None,
|
|
180
|
+
) -> None:
|
|
181
|
+
with open(filename, 'rb') as f:
|
|
182
|
+
st = os.fstat(f.fileno())
|
|
183
|
+
data = f.read()
|
|
184
|
+
|
|
185
|
+
zinfo = zipfile.ZipInfo(
|
|
186
|
+
arcname or filename,
|
|
187
|
+
date_time=self._get_zipinfo_datetime(st.st_mtime),
|
|
188
|
+
)
|
|
189
|
+
zinfo.external_attr = (stat.S_IMODE(st.st_mode) | stat.S_IFMT(st.st_mode)) << 16
|
|
190
|
+
zinfo.compress_type = compress_type or self.compression
|
|
191
|
+
self.writestr(zinfo, data, compress_type)
|
|
192
|
+
|
|
193
|
+
_MINIMUM_TIMESTAMP = 315532800 # 1980-01-01 00:00:00 UTC
|
|
194
|
+
|
|
195
|
+
@classmethod
|
|
196
|
+
def _get_zipinfo_datetime(cls, timestamp: ta.Optional[float] = None) -> ta.Any:
|
|
197
|
+
# Some applications need reproducible .whl files, but they can't do this without forcing the timestamp of the
|
|
198
|
+
# individual ZipInfo objects. See issue #143.
|
|
199
|
+
timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timestamp or time.time()))
|
|
200
|
+
timestamp = max(timestamp, cls._MINIMUM_TIMESTAMP)
|
|
201
|
+
return time.gmtime(timestamp)[0:6]
|
|
202
|
+
|
|
203
|
+
def writestr( # type: ignore # noqa
|
|
204
|
+
self,
|
|
205
|
+
zinfo_or_arcname: ta.Union[str, zipfile.ZipInfo],
|
|
206
|
+
data: ta.Any, # SizedBuffer | str,
|
|
207
|
+
compress_type: ta.Optional[int] = None,
|
|
208
|
+
) -> None:
|
|
209
|
+
if isinstance(zinfo_or_arcname, str):
|
|
210
|
+
zinfo_or_arcname = zipfile.ZipInfo(
|
|
211
|
+
zinfo_or_arcname,
|
|
212
|
+
date_time=self._get_zipinfo_datetime(),
|
|
213
|
+
)
|
|
214
|
+
zinfo_or_arcname.compress_type = self.compression
|
|
215
|
+
zinfo_or_arcname.external_attr = (0o664 | stat.S_IFREG) << 16
|
|
216
|
+
|
|
217
|
+
if isinstance(data, str):
|
|
218
|
+
data = data.encode('utf-8')
|
|
219
|
+
|
|
220
|
+
super().writestr(zinfo_or_arcname, data, compress_type)
|
|
221
|
+
fname = (
|
|
222
|
+
zinfo_or_arcname.filename
|
|
223
|
+
if isinstance(zinfo_or_arcname, zipfile.ZipInfo)
|
|
224
|
+
else zinfo_or_arcname
|
|
225
|
+
)
|
|
226
|
+
if fname != self.record_path:
|
|
227
|
+
hash_ = self._default_algorithm(data) # type: ignore
|
|
228
|
+
self._file_hashes[fname] = ( # type: ignore
|
|
229
|
+
hash_.name,
|
|
230
|
+
self._urlsafe_b64encode(hash_.digest()).decode('ascii'),
|
|
231
|
+
)
|
|
232
|
+
self._file_sizes[fname] = len(data)
|
|
233
|
+
|
|
234
|
+
def close(self) -> None:
|
|
235
|
+
# Write RECORD
|
|
236
|
+
if self.fp is not None and self.mode == 'w' and self._file_hashes:
|
|
237
|
+
data = io.StringIO()
|
|
238
|
+
writer = csv.writer(data, delimiter=',', quotechar='"', lineterminator='\n')
|
|
239
|
+
writer.writerows((
|
|
240
|
+
(fname, algorithm + '=' + hash_, self._file_sizes[fname]) # type: ignore
|
|
241
|
+
for fname, (algorithm, hash_) in self._file_hashes.items()
|
|
242
|
+
))
|
|
243
|
+
writer.writerow((format(self.record_path), '', ''))
|
|
244
|
+
self.writestr(self.record_path, data.getvalue())
|
|
245
|
+
|
|
246
|
+
super().close()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: omdev
|
|
3
|
+
Version: 0.0.0.dev13
|
|
4
|
+
Summary: omdev
|
|
5
|
+
Author: wrmsr
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: source, https://github.com/wrmsr/omlish
|
|
8
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Operating System :: POSIX
|
|
13
|
+
Requires-Python: ~=3.12
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: omlish ==0.0.0.dev13
|
|
16
|
+
Provides-Extra: all
|
|
17
|
+
Requires-Dist: pycparser ~=2.22 ; extra == 'all'
|
|
18
|
+
Requires-Dist: cffi ~=1.17 ; extra == 'all'
|
|
19
|
+
Requires-Dist: pcpp ~=1.30 ; extra == 'all'
|
|
20
|
+
Requires-Dist: mypy ~=1.11 ; extra == 'all'
|
|
21
|
+
Requires-Dist: tokenize-rt ~=6.0 ; extra == 'all'
|
|
22
|
+
Requires-Dist: wheel ~=0.44 ; extra == 'all'
|
|
23
|
+
Provides-Extra: c
|
|
24
|
+
Requires-Dist: pycparser ~=2.22 ; extra == 'c'
|
|
25
|
+
Requires-Dist: cffi ~=1.17 ; extra == 'c'
|
|
26
|
+
Requires-Dist: pcpp ~=1.30 ; extra == 'c'
|
|
27
|
+
Provides-Extra: mypy
|
|
28
|
+
Requires-Dist: mypy ~=1.11 ; extra == 'mypy'
|
|
29
|
+
Provides-Extra: tokens
|
|
30
|
+
Requires-Dist: tokenize-rt ~=6.0 ; extra == 'tokens'
|
|
31
|
+
Provides-Extra: wheel
|
|
32
|
+
Requires-Dist: wheel ~=0.44 ; extra == 'wheel'
|
|
33
|
+
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
omdev/__about__.py,sha256=
|
|
1
|
+
omdev/__about__.py,sha256=VuY8DTDYJifMW_xeF21JIhFM-MwVp1F0vWUhzIqomJA,784
|
|
2
2
|
omdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
omdev/classdot.py,sha256=urN5Pzd2ooAwnfkH0z-muQxdO90IMo-sX2WB-A37lVU,1533
|
|
4
|
-
omdev/cmake.py,sha256=
|
|
4
|
+
omdev/cmake.py,sha256=Diy2ry65806dQP125DAstD3w46z_wszMH7PwC2-6iik,4578
|
|
5
5
|
omdev/tokens.py,sha256=GusxQ1Cd_eiScuR8XTTtc9QFhOgYviYGBZmFnn3Hj7s,756
|
|
6
|
+
omdev/wheelfile.py,sha256=yfupGcGkbFlmzGzKU64k_vmOKpaKnUlDWxeGn2KdekU,10005
|
|
6
7
|
omdev/amalg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
8
|
omdev/amalg/__main__.py,sha256=OE1udULO1g4McUbeg1CoHbSm4hbQ2kcE3ffEGxlnPh4,69
|
|
8
|
-
omdev/amalg/amalg.py,sha256=
|
|
9
|
+
omdev/amalg/amalg.py,sha256=q1YysHehhJtHuf9znZ2cpPT5nFz1WQLXWVcIv99BVyI,11867
|
|
9
10
|
omdev/exts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
11
|
omdev/exts/build.py,sha256=zViF1wYx6z5ACyifgNjlCAVoPAMCKpTr_VoAvwtmvtY,1013
|
|
11
|
-
omdev/exts/cmake.py,sha256=
|
|
12
|
+
omdev/exts/cmake.py,sha256=D1RCmOAowxunEq8HAlIL_8514SBniDPlfynVdAzHS3c,9584
|
|
12
13
|
omdev/exts/importhook.py,sha256=HgEM6UD52BoQi4dhSSk8buoqV6YRUvzQOOWzX6W1aic,2306
|
|
13
|
-
omdev/exts/scan.py,sha256=
|
|
14
|
+
omdev/exts/scan.py,sha256=ivk_Zh_AnOu8jHeaFZnITwMSwaQqOcHxwOjzsAyL1Gc,1640
|
|
14
15
|
omdev/exts/_distutils/__init__.py,sha256=c1zImtnPh3uY8BUTV4RLKtGKqTPx3c_pBbhA6tPtNsE,297
|
|
15
16
|
omdev/exts/_distutils/build_ext.py,sha256=STHl9Rq2KeWJ3dQ8j8LwIQ-vFc4-3XsYWQ8Qc5_VByU,13833
|
|
16
17
|
omdev/exts/_distutils/dir_util.py,sha256=xxfAIPHbjlh-aW9OX6UGDrXiXfB5biG4xEC5RA6oszM,2882
|
|
@@ -48,9 +49,9 @@ omdev/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
48
49
|
omdev/scripts/bracepy.py,sha256=CUg3nNKE8n34a5j77SUli8ivsOj5ks6I_5VIF8bl-zY,2562
|
|
49
50
|
omdev/scripts/execrss.py,sha256=wBpdhWuQj_4EnEmvK_aLQHxaE-J52G9F6g0210fTNe8,307
|
|
50
51
|
omdev/scripts/findimports.py,sha256=d8Xf2TcE7hHh_lXUx2V1APF4Jggu2uO2K0ex6bfmqyQ,2030
|
|
51
|
-
omdev/scripts/findmagic.py,sha256=
|
|
52
|
-
omdev/scripts/interp.py,sha256=
|
|
53
|
-
omdev/scripts/pyproject.py,sha256=
|
|
52
|
+
omdev/scripts/findmagic.py,sha256=z_D9zzZJBuWPbR20NEh8i8NJBnCYWlttg2GTJpmDM4s,1994
|
|
53
|
+
omdev/scripts/interp.py,sha256=itY5pq1ahDjw8mYZcHec5qQRbAZezliPUyyZ1VD7zTg,63037
|
|
54
|
+
omdev/scripts/pyproject.py,sha256=NuWW0nu897jaydR-1f-Gz7pxl-zzTOjNwKiCK3uzjDY,119370
|
|
54
55
|
omdev/scripts/traceimport.py,sha256=JmUgLEQLY7r3QNQ14Agarqh5qtMyRNenCb_OF5EIikY,13392
|
|
55
56
|
omdev/toml/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
|
|
56
57
|
omdev/toml/parser.py,sha256=84bn09uhYHwQGyfww6Rw6y1RxPAE_HDltODOSakcqDM,29186
|
|
@@ -58,12 +59,13 @@ omdev/toml/writer.py,sha256=StGYPvqgN__A2IxTI4rYeHMx8dLAFt8uogHG8dJdShs,2781
|
|
|
58
59
|
omdev/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
60
|
omdev/tools/dockertools.py,sha256=3844AhUst6kYo2xKNn-2Npi-f6r4rocxEOx0tHjE0dk,2063
|
|
60
61
|
omdev/tools/gittools.py,sha256=zPy2D5WDs-CbwT86_T_hbaq5yCuss5e-ouUccXC6xlg,578
|
|
62
|
+
omdev/tools/revisions.py,sha256=LSW3IK5P4FFcZyD0f4hcObnf9cWv3_OoTqeP85UiX8E,4842
|
|
61
63
|
omdev/tools/sqlrepl.py,sha256=v9uVQ4nvquSXcQVYIFq34ikumSILvKqzD6lUKLcncCE,5646
|
|
62
64
|
omdev/versioning/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
|
|
63
65
|
omdev/versioning/specifiers.py,sha256=6Odf9e6farwlPRsD_YqwTfYKG-BXn_dIcKtqfkhfodI,17432
|
|
64
66
|
omdev/versioning/versions.py,sha256=ei2eopEsJq3zSMJmezK1nzZgikgCdxFtnF3f69nCRZQ,12246
|
|
65
|
-
omdev-0.0.0.
|
|
66
|
-
omdev-0.0.0.
|
|
67
|
-
omdev-0.0.0.
|
|
68
|
-
omdev-0.0.0.
|
|
69
|
-
omdev-0.0.0.
|
|
67
|
+
omdev-0.0.0.dev13.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
|
68
|
+
omdev-0.0.0.dev13.dist-info/METADATA,sha256=EWbybLAXeBMkrn_XTfZlFQYsGO837utkj7Qgs1rXGXo,1126
|
|
69
|
+
omdev-0.0.0.dev13.dist-info/WHEEL,sha256=uCRv0ZEik_232NlR4YDw4Pv3Ajt5bKvMH13NUU7hFuI,91
|
|
70
|
+
omdev-0.0.0.dev13.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
|
|
71
|
+
omdev-0.0.0.dev13.dist-info/RECORD,,
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: omdev
|
|
3
|
-
Version: 0.0.0.dev11
|
|
4
|
-
Summary: omdev
|
|
5
|
-
Author: wrmsr
|
|
6
|
-
License: BSD-3-Clause
|
|
7
|
-
Project-URL: source, https://github.com/wrmsr/omlish
|
|
8
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
9
|
-
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
|
-
Classifier: Operating System :: POSIX
|
|
13
|
-
Requires-Python: >=3.12
|
|
14
|
-
License-File: LICENSE
|
|
15
|
-
Requires-Dist: omlish ==0.0.0.dev11
|
|
16
|
-
Provides-Extra: all
|
|
17
|
-
Requires-Dist: pycparser >=2.22 ; extra == 'all'
|
|
18
|
-
Requires-Dist: cffi >=1.17 ; extra == 'all'
|
|
19
|
-
Requires-Dist: pcpp >=1.30 ; extra == 'all'
|
|
20
|
-
Requires-Dist: mypy >=1.11 ; extra == 'all'
|
|
21
|
-
Requires-Dist: tokenize-rt >=6 ; extra == 'all'
|
|
22
|
-
Provides-Extra: c
|
|
23
|
-
Requires-Dist: pycparser >=2.22 ; extra == 'c'
|
|
24
|
-
Requires-Dist: cffi >=1.17 ; extra == 'c'
|
|
25
|
-
Requires-Dist: pcpp >=1.30 ; extra == 'c'
|
|
26
|
-
Provides-Extra: mypy
|
|
27
|
-
Requires-Dist: mypy >=1.11 ; extra == 'mypy'
|
|
28
|
-
Provides-Extra: tokens
|
|
29
|
-
Requires-Dist: tokenize-rt >=6 ; extra == 'tokens'
|
|
30
|
-
|
|
File without changes
|
|
File without changes
|