omdev 0.0.0.dev13__py3-none-any.whl → 0.0.0.dev14__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/amalg/amalg.py +53 -3
- omdev/{scripts/bracepy.py → bracepy.py} +14 -14
- omdev/exts/cmake.py +25 -9
- omdev/exts/importhook.py +84 -38
- omdev/findimports.py +88 -0
- omdev/{scripts/findmagic.py → findmagic.py} +24 -20
- omdev/precheck/__init__.py +0 -0
- omdev/precheck/__main__.py +4 -0
- omdev/precheck/precheck.py +316 -0
- omdev/pyproject/cli.py +3 -0
- omdev/pyproject/pkg.py +13 -2
- omdev/scripts/execrss.py +1 -0
- omdev/scripts/interp.py +2 -0
- omdev/scripts/pyproject.py +590 -184
- omdev/tools/revisions.py +65 -48
- omdev/{scripts → tools}/traceimport.py +3 -1
- {omdev-0.0.0.dev13.dist-info → omdev-0.0.0.dev14.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev13.dist-info → omdev-0.0.0.dev14.dist-info}/RECORD +21 -18
- omdev/scripts/findimports.py +0 -62
- {omdev-0.0.0.dev13.dist-info → omdev-0.0.0.dev14.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev13.dist-info → omdev-0.0.0.dev14.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev13.dist-info → omdev-0.0.0.dev14.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tiny pre-commit
|
|
3
|
+
|
|
4
|
+
TODO:
|
|
5
|
+
- global config
|
|
6
|
+
- global analyses - FilesWithShebang
|
|
7
|
+
- shebang files have no relative imports
|
|
8
|
+
- parallelize (asyncio)
|
|
9
|
+
- anyio? aiofiles? :| nonblock open().read()
|
|
10
|
+
- debug log
|
|
11
|
+
- omlish-lite - no non-lite deps, etc etc
|
|
12
|
+
- omlish-script - no deps, shebang, executable, can be 3.12
|
|
13
|
+
- big git files https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#check-added-large-files
|
|
14
|
+
- https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#check-case-conflict
|
|
15
|
+
- https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#check-symlinks
|
|
16
|
+
- https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#detect-aws-credentials
|
|
17
|
+
- https://github.com/pre-commit/pre-commit-hooks?tab=readme-ov-file#forbid-new-submodules
|
|
18
|
+
- don't check in .o's (omdev.ext import hook is dumb w build dir)
|
|
19
|
+
"""
|
|
20
|
+
import abc
|
|
21
|
+
import argparse
|
|
22
|
+
import asyncio
|
|
23
|
+
import dataclasses as dc
|
|
24
|
+
import glob
|
|
25
|
+
import inspect
|
|
26
|
+
import logging
|
|
27
|
+
import os.path
|
|
28
|
+
import stat
|
|
29
|
+
import subprocess
|
|
30
|
+
import sys
|
|
31
|
+
import textwrap
|
|
32
|
+
import typing as ta
|
|
33
|
+
|
|
34
|
+
from omdev import findimports
|
|
35
|
+
from omdev import findmagic
|
|
36
|
+
from omlish import cached
|
|
37
|
+
from omlish import logs
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
T = ta.TypeVar('T')
|
|
41
|
+
PrecheckConfigT = ta.TypeVar('PrecheckConfigT', bound='Precheck.Config')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
log = logging.getLogger(__name__)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dc.dataclass(frozen=True, kw_only=True)
|
|
51
|
+
class PrecheckContext:
|
|
52
|
+
src_roots: ta.Sequence[str]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Precheck(abc.ABC, ta.Generic[PrecheckConfigT]):
|
|
59
|
+
@dc.dataclass(frozen=True)
|
|
60
|
+
class Config:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
def __init__(self, context: PrecheckContext, config: PrecheckConfigT) -> None:
|
|
64
|
+
super().__init__()
|
|
65
|
+
self._context = context
|
|
66
|
+
self._config = config
|
|
67
|
+
|
|
68
|
+
@dc.dataclass(frozen=True)
|
|
69
|
+
class Violation:
|
|
70
|
+
pc: 'Precheck'
|
|
71
|
+
msg: str
|
|
72
|
+
|
|
73
|
+
@abc.abstractmethod
|
|
74
|
+
def run(self) -> ta.AsyncIterator[Violation]:
|
|
75
|
+
raise NotImplementedError
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class GitBlacklistPrecheck(Precheck['GitBlacklistPrecheck.Config']):
|
|
82
|
+
"""
|
|
83
|
+
TODO:
|
|
84
|
+
- globs
|
|
85
|
+
- regex
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
@dc.dataclass(frozen=True)
|
|
89
|
+
class Config(Precheck.Config):
|
|
90
|
+
files: ta.Sequence[str] = (
|
|
91
|
+
'.env',
|
|
92
|
+
'secrets.yml',
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def __init__(self, context: PrecheckContext, config: Config = Config()) -> None:
|
|
96
|
+
super().__init__(context, config)
|
|
97
|
+
|
|
98
|
+
async def run(self) -> ta.AsyncGenerator[Precheck.Violation, None]:
|
|
99
|
+
for f in self._config.files:
|
|
100
|
+
proc = await asyncio.create_subprocess_exec('git', 'status', '-s', f)
|
|
101
|
+
await proc.communicate()
|
|
102
|
+
if proc.returncode:
|
|
103
|
+
yield Precheck.Violation(self, f)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ScriptDepsPrecheck(Precheck['ScriptDepsPrecheck.Config']):
|
|
110
|
+
@dc.dataclass(frozen=True)
|
|
111
|
+
class Config(Precheck.Config):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
def __init__(self, context: PrecheckContext, config: Config = Config()) -> None:
|
|
115
|
+
super().__init__(context, config)
|
|
116
|
+
|
|
117
|
+
async def run(self) -> ta.AsyncGenerator[Precheck.Violation, None]:
|
|
118
|
+
for fp in findmagic.find_magic(
|
|
119
|
+
self._context.src_roots,
|
|
120
|
+
['# @omlish-script'],
|
|
121
|
+
['py'],
|
|
122
|
+
):
|
|
123
|
+
if not (stat.S_IXUSR & os.stat(fp).st_mode):
|
|
124
|
+
yield Precheck.Violation(self, f'script {fp} is not executable')
|
|
125
|
+
|
|
126
|
+
with open(fp) as f: # noqa # FIXME
|
|
127
|
+
src = f.read()
|
|
128
|
+
|
|
129
|
+
if not src.startswith('#!/usr/bin/env python3\n'):
|
|
130
|
+
yield Precheck.Violation(self, f'script {fp} lacks correct shebang')
|
|
131
|
+
|
|
132
|
+
imps = findimports.find_imports(fp)
|
|
133
|
+
deps = findimports.get_import_deps(imps)
|
|
134
|
+
if deps:
|
|
135
|
+
yield Precheck.Violation(self, f'script {fp} has deps: {deps}')
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
##
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class LitePython8Precheck(Precheck['LitePython8Precheck.Config']):
|
|
142
|
+
@dc.dataclass(frozen=True)
|
|
143
|
+
class Config(Precheck.Config):
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
def __init__(self, context: PrecheckContext, config: Config = Config()) -> None:
|
|
147
|
+
super().__init__(context, config)
|
|
148
|
+
|
|
149
|
+
#
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def _load_file_module(fp: str) -> None:
|
|
153
|
+
import os.path # noqa
|
|
154
|
+
import types # noqa
|
|
155
|
+
|
|
156
|
+
fp = os.path.abspath(fp)
|
|
157
|
+
|
|
158
|
+
with open(fp) as f:
|
|
159
|
+
src = f.read()
|
|
160
|
+
|
|
161
|
+
mn = os.path.basename(fp).rpartition('.')[0]
|
|
162
|
+
|
|
163
|
+
mod = types.ModuleType(mn)
|
|
164
|
+
mod.__name__ = mn
|
|
165
|
+
mod.__file__ = fp
|
|
166
|
+
mod.__builtins__ = __builtins__ # type: ignore
|
|
167
|
+
mod.__spec__ = None
|
|
168
|
+
|
|
169
|
+
code = compile(src, fp, 'exec')
|
|
170
|
+
exec(code, mod.__dict__, mod.__dict__)
|
|
171
|
+
|
|
172
|
+
@cached.function
|
|
173
|
+
def _load_file_module_payload(self) -> str:
|
|
174
|
+
return '\n'.join([
|
|
175
|
+
'import sys',
|
|
176
|
+
'fp = sys.argv[-1]',
|
|
177
|
+
'',
|
|
178
|
+
textwrap.dedent('\n'.join(inspect.getsource(LitePython8Precheck._load_file_module).splitlines()[2:])),
|
|
179
|
+
])
|
|
180
|
+
|
|
181
|
+
#
|
|
182
|
+
|
|
183
|
+
async def _run_script(self, fp: str) -> list[Precheck.Violation]:
|
|
184
|
+
log.debug('%s: loading script %s', self.__class__.__name__, fp)
|
|
185
|
+
|
|
186
|
+
vs: list[Precheck.Violation] = []
|
|
187
|
+
|
|
188
|
+
proc = await asyncio.create_subprocess_exec(
|
|
189
|
+
'.venvs/8/bin/python',
|
|
190
|
+
'-c',
|
|
191
|
+
self._load_file_module_payload(),
|
|
192
|
+
fp,
|
|
193
|
+
stderr=subprocess.PIPE,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
_, stderr = await proc.communicate()
|
|
197
|
+
if proc.returncode != 0:
|
|
198
|
+
vs.append(Precheck.Violation(self, f'lite script {fp} failed to load in python8: {stderr.decode()}'))
|
|
199
|
+
|
|
200
|
+
return vs
|
|
201
|
+
|
|
202
|
+
async def _run_one_module(self, fp: str) -> list[Precheck.Violation]:
|
|
203
|
+
vs: list[Precheck.Violation] = []
|
|
204
|
+
|
|
205
|
+
mod = fp.rpartition('.')[0].replace(os.sep, '.')
|
|
206
|
+
|
|
207
|
+
log.debug('%s: loading module %s', self.__class__.__name__, mod)
|
|
208
|
+
|
|
209
|
+
proc = await asyncio.create_subprocess_exec(
|
|
210
|
+
'.venvs/8/bin/python',
|
|
211
|
+
'-c',
|
|
212
|
+
f'import {mod}',
|
|
213
|
+
stderr=subprocess.PIPE,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
_, stderr = await proc.communicate()
|
|
217
|
+
if proc.returncode != 0:
|
|
218
|
+
vs.append(Precheck.Violation(self, f'lite module {fp} failed to import in python8: {stderr.decode()}')) # noqa
|
|
219
|
+
|
|
220
|
+
return vs
|
|
221
|
+
|
|
222
|
+
async def _run_module(self, fp: str) -> list[Precheck.Violation]:
|
|
223
|
+
vs: list[Precheck.Violation] = []
|
|
224
|
+
|
|
225
|
+
if fp.endswith('__init__.py'):
|
|
226
|
+
pfps = glob.glob(os.path.join(os.path.dirname(fp), '**/*.py'), recursive=True)
|
|
227
|
+
else:
|
|
228
|
+
pfps = [fp]
|
|
229
|
+
|
|
230
|
+
for pfp in pfps:
|
|
231
|
+
vs.extend(await self._run_one_module(pfp))
|
|
232
|
+
|
|
233
|
+
return vs
|
|
234
|
+
|
|
235
|
+
async def run(self) -> ta.AsyncGenerator[Precheck.Violation, None]:
|
|
236
|
+
for fp in findmagic.find_magic(
|
|
237
|
+
self._context.src_roots,
|
|
238
|
+
['# @omlish-lite'],
|
|
239
|
+
['py'],
|
|
240
|
+
):
|
|
241
|
+
with open(fp) as f: # noqa # FIXME
|
|
242
|
+
src = f.read()
|
|
243
|
+
|
|
244
|
+
is_script = '# @omlish-script' in src.splitlines()
|
|
245
|
+
|
|
246
|
+
if is_script:
|
|
247
|
+
for v in await self._run_script(fp):
|
|
248
|
+
yield v
|
|
249
|
+
|
|
250
|
+
else:
|
|
251
|
+
for v in await self._run_module(fp):
|
|
252
|
+
yield v
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
##
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _check_cmd(args) -> None:
|
|
259
|
+
if not os.path.isfile('pyproject.toml'):
|
|
260
|
+
raise RuntimeError('must run in project root')
|
|
261
|
+
|
|
262
|
+
ctx = PrecheckContext(
|
|
263
|
+
src_roots=args.roots,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
pcs: list[Precheck] = [
|
|
267
|
+
GitBlacklistPrecheck(ctx),
|
|
268
|
+
ScriptDepsPrecheck(ctx),
|
|
269
|
+
LitePython8Precheck(ctx),
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
async def run() -> list[Precheck.Violation]:
|
|
273
|
+
vs: list[Precheck.Violation] = []
|
|
274
|
+
|
|
275
|
+
for pc in pcs:
|
|
276
|
+
async for v in pc.run():
|
|
277
|
+
vs.append(v)
|
|
278
|
+
print(v)
|
|
279
|
+
|
|
280
|
+
return vs
|
|
281
|
+
|
|
282
|
+
vs = asyncio.run(run())
|
|
283
|
+
|
|
284
|
+
if vs:
|
|
285
|
+
print(f'{len(vs)} violations found')
|
|
286
|
+
sys.exit(1)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
##
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
293
|
+
parser = argparse.ArgumentParser()
|
|
294
|
+
|
|
295
|
+
subparsers = parser.add_subparsers()
|
|
296
|
+
|
|
297
|
+
parser_check = subparsers.add_parser('check')
|
|
298
|
+
parser_check.add_argument('roots', nargs='+')
|
|
299
|
+
parser_check.set_defaults(func=_check_cmd)
|
|
300
|
+
|
|
301
|
+
return parser
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _main(argv: ta.Sequence[str] | None = None) -> None:
|
|
305
|
+
logs.configure_standard_logging('INFO')
|
|
306
|
+
|
|
307
|
+
parser = _build_parser()
|
|
308
|
+
args = parser.parse_args(argv)
|
|
309
|
+
if not getattr(args, 'func', None):
|
|
310
|
+
parser.print_help()
|
|
311
|
+
else:
|
|
312
|
+
args.func(args)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
if __name__ == '__main__':
|
|
316
|
+
_main()
|
omdev/pyproject/cli.py
CHANGED
|
@@ -308,6 +308,7 @@ def _pkg_cmd(args) -> None:
|
|
|
308
308
|
|
|
309
309
|
build_output_dir = 'dist'
|
|
310
310
|
run_build = bool(args.build)
|
|
311
|
+
add_revision = bool(args.revision)
|
|
311
312
|
|
|
312
313
|
if run_build:
|
|
313
314
|
os.makedirs(build_output_dir, exist_ok=True)
|
|
@@ -322,6 +323,7 @@ def _pkg_cmd(args) -> None:
|
|
|
322
323
|
).gen,
|
|
323
324
|
run_build=run_build,
|
|
324
325
|
build_output_dir=build_output_dir,
|
|
326
|
+
add_revision=add_revision,
|
|
325
327
|
))
|
|
326
328
|
for dir_name in run.cfg().pkgs
|
|
327
329
|
]
|
|
@@ -350,6 +352,7 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
350
352
|
|
|
351
353
|
parser_resolve = subparsers.add_parser('pkg')
|
|
352
354
|
parser_resolve.add_argument('-b', '--build', action='store_true')
|
|
355
|
+
parser_resolve.add_argument('-r', '--revision', action='store_true')
|
|
353
356
|
parser_resolve.add_argument('cmd', nargs='?')
|
|
354
357
|
parser_resolve.add_argument('args', nargs=argparse.REMAINDER)
|
|
355
358
|
parser_resolve.set_defaults(func=_pkg_cmd)
|
omdev/pyproject/pkg.py
CHANGED
|
@@ -30,6 +30,7 @@ from omlish.lite.cached import cached_nullary
|
|
|
30
30
|
from omlish.lite.logs import log
|
|
31
31
|
|
|
32
32
|
from ..toml.writer import TomlWriter
|
|
33
|
+
from ..tools.revisions import GitRevisionAdder
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
class PyprojectPackageGenerator:
|
|
@@ -179,6 +180,8 @@ class PyprojectPackageGenerator:
|
|
|
179
180
|
def _run_build(
|
|
180
181
|
self,
|
|
181
182
|
build_output_dir: ta.Optional[str] = None,
|
|
183
|
+
*,
|
|
184
|
+
add_revision: bool = False,
|
|
182
185
|
) -> None:
|
|
183
186
|
subprocess.check_call(
|
|
184
187
|
[
|
|
@@ -189,8 +192,12 @@ class PyprojectPackageGenerator:
|
|
|
189
192
|
cwd=self._build_dir(),
|
|
190
193
|
)
|
|
191
194
|
|
|
195
|
+
dist_dir = os.path.join(self._build_dir(), 'dist')
|
|
196
|
+
|
|
197
|
+
if add_revision:
|
|
198
|
+
GitRevisionAdder().add_to(dist_dir)
|
|
199
|
+
|
|
192
200
|
if build_output_dir is not None:
|
|
193
|
-
dist_dir = os.path.join(self._build_dir(), 'dist')
|
|
194
201
|
for fn in os.listdir(dist_dir):
|
|
195
202
|
shutil.copyfile(os.path.join(dist_dir, fn), os.path.join(build_output_dir, fn))
|
|
196
203
|
|
|
@@ -201,6 +208,7 @@ class PyprojectPackageGenerator:
|
|
|
201
208
|
*,
|
|
202
209
|
run_build: bool = False,
|
|
203
210
|
build_output_dir: ta.Optional[str] = None,
|
|
211
|
+
add_revision: bool = False,
|
|
204
212
|
) -> str:
|
|
205
213
|
log.info('Generating pyproject package: %s -> %s', self._dir_name, self._build_root)
|
|
206
214
|
|
|
@@ -211,6 +219,9 @@ class PyprojectPackageGenerator:
|
|
|
211
219
|
self._symlink_standard_files()
|
|
212
220
|
|
|
213
221
|
if run_build:
|
|
214
|
-
self._run_build(
|
|
222
|
+
self._run_build(
|
|
223
|
+
build_output_dir,
|
|
224
|
+
add_revision=add_revision,
|
|
225
|
+
)
|
|
215
226
|
|
|
216
227
|
return self._build_dir()
|
omdev/scripts/execrss.py
CHANGED