omdev 0.0.0.dev52__tar.gz → 0.0.0.dev54__tar.gz
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.
- {omdev-0.0.0.dev52/omdev.egg-info → omdev-0.0.0.dev54}/PKG-INFO +2 -2
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/main.py +25 -5
- omdev-0.0.0.dev54/omdev/cli/types.py +25 -0
- omdev-0.0.0.dev54/omdev/manifests/build.py +356 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/cli.py +1 -1
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/pyproject.py +1 -1
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54/omdev.egg-info}/PKG-INFO +2 -2
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev.egg-info/requires.txt +1 -1
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/pyproject.toml +2 -2
- omdev-0.0.0.dev52/omdev/cli/types.py +0 -17
- omdev-0.0.0.dev52/omdev/manifests/build.py +0 -331
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/LICENSE +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/MANIFEST.in +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/README.rst +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/.manifests.json +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/__about__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/amalg/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/amalg/__main__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/amalg/amalg.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/bracepy.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/cache.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/contexts.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/currents.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/fns.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/resolvers.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/storage.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/compute/types.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/actions.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/cache.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/consts.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/defaults.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/manifests.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cache/data/specs.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_boilerplate.cc +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/LICENSE +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/build_ext.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/compilers/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/compilers/ccompiler.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/compilers/options.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/compilers/unixccompiler.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/dir_util.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/errors.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/extension.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/file_util.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/modified.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/spawn.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/sysconfig.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/util.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/_distutils/version.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/build.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/cmake.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/importhook.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/magic.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cexts/scan.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/classdot.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/__main__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/clicli.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/install.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cli/managers.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/cmake.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/findimports.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/findmagic.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/git.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/__main__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/cli.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/inspect.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/providers.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/pyenv.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/resolvers.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/standalone.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/system.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/interp/types.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/manifests/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/manifests/load.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/manifests/types.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/mypy/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/mypy/debug.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/packaging/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/packaging/names.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/packaging/requires.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/packaging/specifiers.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/packaging/versions.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/__main__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/base.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/git.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/lite.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/precheck.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/precheck/scripts.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/__main__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/cexts.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/configs.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/pkg.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/pyproject/reqs.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/revisions.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/bumpversion.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/execrss.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/exectime.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/importtrace.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/scripts/interp.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/secrets.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tokens.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/toml/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/toml/parser.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/toml/writer.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/__init__.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/dockertools.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/gittools.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/importscan.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/mkrelimp.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/piptools.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/proftools.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/rst.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/tools/sqlrepl.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev/wheelfile.py +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev.egg-info/SOURCES.txt +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev.egg-info/dependency_links.txt +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev.egg-info/entry_points.txt +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/omdev.egg-info/top_level.txt +0 -0
- {omdev-0.0.0.dev52 → omdev-0.0.0.dev54}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: omdev
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev54
|
|
4
4
|
Summary: omdev
|
|
5
5
|
Author: wrmsr
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
|
|
|
12
12
|
Classifier: Operating System :: POSIX
|
|
13
13
|
Requires-Python: ~=3.12
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: omlish==0.0.0.
|
|
15
|
+
Requires-Dist: omlish==0.0.0.dev54
|
|
16
16
|
Provides-Extra: all
|
|
17
17
|
Requires-Dist: pycparser~=2.22; extra == "all"
|
|
18
18
|
Requires-Dist: cffi~=1.17; extra == "all"
|
|
@@ -64,9 +64,10 @@ def _main() -> None:
|
|
|
64
64
|
|
|
65
65
|
dct: dict[str, CliCmd] = {}
|
|
66
66
|
for cc in ccs:
|
|
67
|
-
if cc.cmd_name
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
for cn in [cc.cmd_name] if isinstance(cc.cmd_name, str) else cc.cmd_name:
|
|
68
|
+
if cn in dct:
|
|
69
|
+
raise NameError(cc)
|
|
70
|
+
dct[cn] = cc
|
|
70
71
|
|
|
71
72
|
#
|
|
72
73
|
|
|
@@ -76,7 +77,26 @@ def _main() -> None:
|
|
|
76
77
|
|
|
77
78
|
args = parser.parse_args()
|
|
78
79
|
if not args.cmd:
|
|
79
|
-
|
|
80
|
+
mdct: dict = {}
|
|
81
|
+
for cc in ccs:
|
|
82
|
+
if isinstance(cc.cmd_name, str) and cc.cmd_name[0] == '_':
|
|
83
|
+
continue
|
|
84
|
+
if isinstance(cc, CliFunc):
|
|
85
|
+
mdct.setdefault('-', []).append(cc)
|
|
86
|
+
elif isinstance(cc, CliModule):
|
|
87
|
+
mdct.setdefault(cc.mod_name.partition('.')[0], []).append(cc)
|
|
88
|
+
else:
|
|
89
|
+
raise TypeError(cc)
|
|
90
|
+
|
|
91
|
+
print('Subcommands:\n')
|
|
92
|
+
for m, l in sorted(mdct.items(), key=lambda t: (t[0] == '-', t[0])):
|
|
93
|
+
print(f' {m}')
|
|
94
|
+
for cc in sorted(l, key=lambda c: c.primary_name):
|
|
95
|
+
if isinstance(cc.cmd_name, str):
|
|
96
|
+
print(f' {cc.cmd_name}')
|
|
97
|
+
else:
|
|
98
|
+
print(f' {cc.cmd_name[0]} ({", ".join(cc.cmd_name[1:])})')
|
|
99
|
+
print()
|
|
80
100
|
return
|
|
81
101
|
|
|
82
102
|
#
|
|
@@ -84,7 +104,7 @@ def _main() -> None:
|
|
|
84
104
|
cc = dct[args.cmd]
|
|
85
105
|
|
|
86
106
|
if isinstance(cc, CliModule):
|
|
87
|
-
sys.argv = [
|
|
107
|
+
sys.argv = [args.cmd, *(args.args or ())]
|
|
88
108
|
runpy._run_module_as_main(cc.mod_name) # type: ignore # noqa
|
|
89
109
|
|
|
90
110
|
elif isinstance(cc, CliFunc):
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# ruff: noqa: UP007
|
|
2
|
+
import dataclasses as dc
|
|
3
|
+
import typing as ta
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dc.dataclass(frozen=True)
|
|
7
|
+
class CliCmd:
|
|
8
|
+
cmd_name: ta.Union[str, ta.Sequence[str]]
|
|
9
|
+
|
|
10
|
+
@property
|
|
11
|
+
def primary_name(self) -> str:
|
|
12
|
+
if isinstance(self.cmd_name, str):
|
|
13
|
+
return self.cmd_name
|
|
14
|
+
else:
|
|
15
|
+
return self.cmd_name[0]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dc.dataclass(frozen=True)
|
|
19
|
+
class CliModule(CliCmd):
|
|
20
|
+
mod_name: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dc.dataclass(frozen=True)
|
|
24
|
+
class CliFunc(CliCmd):
|
|
25
|
+
fn: ta.Callable
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TODO:
|
|
3
|
+
- separate build from cli
|
|
4
|
+
- parallelize & sort
|
|
5
|
+
|
|
6
|
+
See (entry_points):
|
|
7
|
+
- https://github.com/pytest-dev/pluggy/blob/main/src/pluggy/_manager.py#L405
|
|
8
|
+
- https://docs.pytest.org/en/7.1.x/how-to/writing_plugins.html#setuptools-entry-points
|
|
9
|
+
- https://packaging.python.org/en/latest/specifications/entry-points/
|
|
10
|
+
- https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/
|
|
11
|
+
- [project.entry-points.omlish-manifests] \n omdev = omdev
|
|
12
|
+
"""
|
|
13
|
+
# ruff: noqa: UP006 UP007
|
|
14
|
+
import argparse
|
|
15
|
+
import asyncio
|
|
16
|
+
import collections
|
|
17
|
+
import dataclasses as dc
|
|
18
|
+
import inspect
|
|
19
|
+
import itertools
|
|
20
|
+
import json
|
|
21
|
+
import multiprocessing as mp
|
|
22
|
+
import os.path
|
|
23
|
+
import re
|
|
24
|
+
import shlex
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
import time
|
|
28
|
+
import typing as ta
|
|
29
|
+
|
|
30
|
+
from omlish.lite.cached import cached_nullary
|
|
31
|
+
from omlish.lite.json import json_dumps_pretty
|
|
32
|
+
from omlish.lite.logs import configure_standard_logging
|
|
33
|
+
from omlish.lite.logs import log
|
|
34
|
+
|
|
35
|
+
from .. import findmagic
|
|
36
|
+
from .load import ManifestLoader
|
|
37
|
+
from .types import Manifest
|
|
38
|
+
from .types import ManifestOrigin
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
T = ta.TypeVar('T')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
MANIFEST_MAGIC = '# @omlish-manifest'
|
|
48
|
+
|
|
49
|
+
_MANIFEST_GLOBAL_PAT = re.compile(r'^(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*=.*')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _dump_module_manifests(spec: str, *attrs: str) -> None:
|
|
53
|
+
import collections.abc
|
|
54
|
+
import dataclasses as dc # noqa
|
|
55
|
+
import importlib
|
|
56
|
+
import json
|
|
57
|
+
|
|
58
|
+
mod = importlib.import_module(spec)
|
|
59
|
+
|
|
60
|
+
out = {}
|
|
61
|
+
for attr in attrs:
|
|
62
|
+
manifest = getattr(mod, attr)
|
|
63
|
+
|
|
64
|
+
if dc.is_dataclass(manifest):
|
|
65
|
+
cls = type(manifest)
|
|
66
|
+
manifest_json = json.dumps(dc.asdict(manifest)) # type: ignore
|
|
67
|
+
manifest_dct = json.loads(manifest_json)
|
|
68
|
+
|
|
69
|
+
rt_manifest = cls(**manifest_dct) # type: ignore
|
|
70
|
+
if rt_manifest != manifest:
|
|
71
|
+
raise Exception(f'Manifest failed to roundtrip: {manifest} -> {manifest_dct} != {rt_manifest}')
|
|
72
|
+
|
|
73
|
+
key = f'${cls.__module__}.{cls.__qualname__}'
|
|
74
|
+
out[attr] = {key: manifest_dct}
|
|
75
|
+
|
|
76
|
+
elif isinstance(manifest, collections.abc.Mapping):
|
|
77
|
+
[(key, manifest_dct)] = manifest.items()
|
|
78
|
+
if not key.startswith('$'): # noqa
|
|
79
|
+
raise Exception(f'Bad key: {key}')
|
|
80
|
+
|
|
81
|
+
if not isinstance(manifest_dct, collections.abc.Mapping):
|
|
82
|
+
raise Exception(f'Bad value: {manifest_dct}')
|
|
83
|
+
|
|
84
|
+
manifest_json = json.dumps(manifest_dct)
|
|
85
|
+
rt_manifest_dct = json.loads(manifest_json)
|
|
86
|
+
if manifest_dct != rt_manifest_dct:
|
|
87
|
+
raise Exception(f'Manifest failed to roundtrip: {manifest_dct} != {rt_manifest_dct}')
|
|
88
|
+
|
|
89
|
+
out[attr] = {key: manifest_dct}
|
|
90
|
+
|
|
91
|
+
else:
|
|
92
|
+
raise TypeError(f'Manifest must be dataclass or mapping: {manifest!r}')
|
|
93
|
+
|
|
94
|
+
out_json = json.dumps(out, indent=None, separators=(',', ':'))
|
|
95
|
+
print(out_json)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@cached_nullary
|
|
99
|
+
def _payload_src() -> str:
|
|
100
|
+
return inspect.getsource(_dump_module_manifests)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class ManifestBuilder:
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
base: str,
|
|
107
|
+
concurrency: int = 8,
|
|
108
|
+
*,
|
|
109
|
+
write: bool = False,
|
|
110
|
+
) -> None:
|
|
111
|
+
super().__init__()
|
|
112
|
+
|
|
113
|
+
self._base = base
|
|
114
|
+
self._sem = asyncio.Semaphore(concurrency)
|
|
115
|
+
self._write = write
|
|
116
|
+
|
|
117
|
+
async def _spawn(self, fn: ta.Callable[..., ta.Awaitable[T]], *args: ta.Any, **kwargs: ta.Any) -> T:
|
|
118
|
+
await self._sem.acquire()
|
|
119
|
+
try:
|
|
120
|
+
try:
|
|
121
|
+
return await fn(*args, **kwargs)
|
|
122
|
+
except Exception: # noqa
|
|
123
|
+
log.exception('Exception in task: %s, %r, %r', fn, args, kwargs)
|
|
124
|
+
raise
|
|
125
|
+
finally:
|
|
126
|
+
self._sem.release()
|
|
127
|
+
|
|
128
|
+
async def build_module_manifests(
|
|
129
|
+
self,
|
|
130
|
+
file: str,
|
|
131
|
+
*,
|
|
132
|
+
shell_wrap: bool = True,
|
|
133
|
+
warn_threshold_s: ta.Optional[float] = 1.,
|
|
134
|
+
) -> ta.Sequence[Manifest]:
|
|
135
|
+
log.info('Extracting manifests from file %s', file)
|
|
136
|
+
|
|
137
|
+
if not file.endswith('.py'):
|
|
138
|
+
raise Exception(file)
|
|
139
|
+
|
|
140
|
+
mod_name = file.rpartition('.')[0].replace(os.sep, '.')
|
|
141
|
+
mod_base = mod_name.split('.')[0]
|
|
142
|
+
if mod_base != (first_dir := file.split(os.path.sep)[0]):
|
|
143
|
+
raise Exception(f'Unexpected module base: {mod_base=} != {first_dir=}')
|
|
144
|
+
|
|
145
|
+
with open(os.path.join(self._base, file)) as f: # noqa
|
|
146
|
+
src = f.read()
|
|
147
|
+
|
|
148
|
+
origins: ta.List[ManifestOrigin] = []
|
|
149
|
+
lines = src.splitlines(keepends=True)
|
|
150
|
+
for i, l in enumerate(lines):
|
|
151
|
+
if l.startswith(MANIFEST_MAGIC):
|
|
152
|
+
if (m := _MANIFEST_GLOBAL_PAT.match(nl := lines[i + 1])) is None:
|
|
153
|
+
raise Exception(nl)
|
|
154
|
+
|
|
155
|
+
origins.append(ManifestOrigin(
|
|
156
|
+
module='.'.join(['', *mod_name.split('.')[1:]]),
|
|
157
|
+
attr=m.groupdict()['name'],
|
|
158
|
+
|
|
159
|
+
file=file,
|
|
160
|
+
line=i + 1,
|
|
161
|
+
))
|
|
162
|
+
|
|
163
|
+
if not origins:
|
|
164
|
+
raise Exception('no manifests found')
|
|
165
|
+
|
|
166
|
+
if (dups := [k for k, v in collections.Counter(o.attr for o in origins).items() if v > 1]):
|
|
167
|
+
raise Exception(f'Duplicate attrs: {dups}')
|
|
168
|
+
|
|
169
|
+
attrs = [o.attr for o in origins]
|
|
170
|
+
|
|
171
|
+
subproc_src = '\n\n'.join([
|
|
172
|
+
_payload_src(),
|
|
173
|
+
f'_dump_module_manifests({mod_name!r}, {", ".join(repr(a) for a in attrs)})\n',
|
|
174
|
+
])
|
|
175
|
+
|
|
176
|
+
args = [
|
|
177
|
+
sys.executable,
|
|
178
|
+
'-c',
|
|
179
|
+
subproc_src,
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
if shell_wrap:
|
|
183
|
+
args = ['sh', '-c', ' '.join(map(shlex.quote, args))]
|
|
184
|
+
|
|
185
|
+
start_time = time.time()
|
|
186
|
+
|
|
187
|
+
proc = await asyncio.create_subprocess_exec(*args, stdout=subprocess.PIPE)
|
|
188
|
+
subproc_out, _ = await proc.communicate()
|
|
189
|
+
if proc.returncode:
|
|
190
|
+
raise Exception('Subprocess failed')
|
|
191
|
+
|
|
192
|
+
end_time = time.time()
|
|
193
|
+
|
|
194
|
+
if warn_threshold_s is not None and (elapsed_time := (end_time - start_time)) >= warn_threshold_s:
|
|
195
|
+
log.warning('Manifest extraction took a long time: %s, %.2f s', file, elapsed_time)
|
|
196
|
+
|
|
197
|
+
sp_lines = subproc_out.decode().strip().splitlines()
|
|
198
|
+
if len(sp_lines) != 1:
|
|
199
|
+
raise Exception('Unexpected subprocess output')
|
|
200
|
+
|
|
201
|
+
dct = json.loads(sp_lines[0])
|
|
202
|
+
if set(dct) != set(attrs):
|
|
203
|
+
raise Exception('Unexpected subprocess output keys')
|
|
204
|
+
|
|
205
|
+
out: ta.List[Manifest] = []
|
|
206
|
+
|
|
207
|
+
for o in origins:
|
|
208
|
+
value = dct[o.attr]
|
|
209
|
+
|
|
210
|
+
if not (
|
|
211
|
+
isinstance(value, ta.Mapping) and
|
|
212
|
+
len(value) == 1 and
|
|
213
|
+
all(isinstance(k, str) and k.startswith('$') and len(k) > 1 for k in value)
|
|
214
|
+
):
|
|
215
|
+
raise TypeError(f'Manifests must be mappings of strings starting with $: {value!r}')
|
|
216
|
+
|
|
217
|
+
[(key, value_dct)] = value.items()
|
|
218
|
+
kb, _, kr = key[1:].partition('.') # noqa
|
|
219
|
+
if kb == mod_base: # noqa
|
|
220
|
+
key = f'$.{kr}'
|
|
221
|
+
value = {key: value_dct}
|
|
222
|
+
|
|
223
|
+
out.append(Manifest(
|
|
224
|
+
**dc.asdict(o),
|
|
225
|
+
value=value,
|
|
226
|
+
))
|
|
227
|
+
|
|
228
|
+
return out
|
|
229
|
+
|
|
230
|
+
async def build_package_manifests(
|
|
231
|
+
self,
|
|
232
|
+
name: str,
|
|
233
|
+
) -> ta.List[Manifest]:
|
|
234
|
+
pkg_dir = os.path.join(self._base, name)
|
|
235
|
+
if not os.path.isdir(pkg_dir) or not os.path.isfile(os.path.join(pkg_dir, '__init__.py')):
|
|
236
|
+
raise Exception(pkg_dir)
|
|
237
|
+
|
|
238
|
+
files = sorted(findmagic.find_magic(
|
|
239
|
+
[pkg_dir],
|
|
240
|
+
[MANIFEST_MAGIC],
|
|
241
|
+
['py'],
|
|
242
|
+
))
|
|
243
|
+
manifests: ta.List[Manifest] = list(itertools.chain.from_iterable(await asyncio.gather(*[
|
|
244
|
+
self._spawn(
|
|
245
|
+
self.build_module_manifests,
|
|
246
|
+
os.path.relpath(file, self._base),
|
|
247
|
+
)
|
|
248
|
+
for file in files
|
|
249
|
+
])))
|
|
250
|
+
|
|
251
|
+
if self._write:
|
|
252
|
+
with open(os.path.join(pkg_dir, '.manifests.json'), 'w') as f: # noqa
|
|
253
|
+
f.write(json_dumps_pretty([dc.asdict(m) for m in manifests]))
|
|
254
|
+
f.write('\n')
|
|
255
|
+
|
|
256
|
+
return manifests
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
##
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def check_package_manifests(
|
|
263
|
+
name: str,
|
|
264
|
+
base: str,
|
|
265
|
+
) -> None:
|
|
266
|
+
pkg_dir = os.path.join(base, name)
|
|
267
|
+
if not os.path.isdir(pkg_dir) or not os.path.isfile(os.path.join(pkg_dir, '__init__.py')):
|
|
268
|
+
raise Exception(pkg_dir)
|
|
269
|
+
|
|
270
|
+
manifests_file = os.path.join(pkg_dir, '.manifests.json')
|
|
271
|
+
if not os.path.isfile(manifests_file):
|
|
272
|
+
raise Exception(f'No manifests file: {manifests_file}')
|
|
273
|
+
|
|
274
|
+
with open(manifests_file) as f:
|
|
275
|
+
manifests_json = json.load(f)
|
|
276
|
+
|
|
277
|
+
ldr = ManifestLoader()
|
|
278
|
+
for entry in manifests_json:
|
|
279
|
+
manifest = Manifest(**entry)
|
|
280
|
+
[(key, value_dct)] = manifest.value.items()
|
|
281
|
+
if key.startswith('$.'):
|
|
282
|
+
key = f'${name}{key[1:]}'
|
|
283
|
+
cls = ldr.load_cls(key)
|
|
284
|
+
value = cls(**value_dct) # noqa
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
##
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
if __name__ == '__main__':
|
|
291
|
+
def _get_base(args) -> str:
|
|
292
|
+
if args.base is not None:
|
|
293
|
+
base = args.base
|
|
294
|
+
else:
|
|
295
|
+
base = os.getcwd()
|
|
296
|
+
base = os.path.abspath(base)
|
|
297
|
+
if not os.path.isdir(base):
|
|
298
|
+
raise RuntimeError(base)
|
|
299
|
+
return base
|
|
300
|
+
|
|
301
|
+
def _gen_cmd(args) -> None:
|
|
302
|
+
base = _get_base(args)
|
|
303
|
+
|
|
304
|
+
jobs = args.jobs or int(max(mp.cpu_count() // 1.5, 1))
|
|
305
|
+
builder = ManifestBuilder(
|
|
306
|
+
base,
|
|
307
|
+
jobs,
|
|
308
|
+
write=args.write or False,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
async def do():
|
|
312
|
+
return await asyncio.gather(*[
|
|
313
|
+
builder.build_package_manifests(pkg)
|
|
314
|
+
for pkg in args.package
|
|
315
|
+
])
|
|
316
|
+
|
|
317
|
+
mss = asyncio.run(do())
|
|
318
|
+
if not args.quiet:
|
|
319
|
+
for ms in mss:
|
|
320
|
+
print(json_dumps_pretty([dc.asdict(m) for m in ms]))
|
|
321
|
+
|
|
322
|
+
def _check_cmd(args) -> None:
|
|
323
|
+
base = _get_base(args)
|
|
324
|
+
|
|
325
|
+
for pkg in args.package:
|
|
326
|
+
check_package_manifests(
|
|
327
|
+
pkg,
|
|
328
|
+
base,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def _main(argv=None) -> None:
|
|
332
|
+
configure_standard_logging('INFO')
|
|
333
|
+
|
|
334
|
+
parser = argparse.ArgumentParser()
|
|
335
|
+
subparsers = parser.add_subparsers()
|
|
336
|
+
|
|
337
|
+
parser_gen = subparsers.add_parser('gen')
|
|
338
|
+
parser_gen.add_argument('-b', '--base')
|
|
339
|
+
parser_gen.add_argument('-w', '--write', action='store_true')
|
|
340
|
+
parser_gen.add_argument('-q', '--quiet', action='store_true')
|
|
341
|
+
parser_gen.add_argument('-j', '--jobs', type=int)
|
|
342
|
+
parser_gen.add_argument('package', nargs='*')
|
|
343
|
+
parser_gen.set_defaults(func=_gen_cmd)
|
|
344
|
+
|
|
345
|
+
parser_check = subparsers.add_parser('check')
|
|
346
|
+
parser_check.add_argument('-b', '--base')
|
|
347
|
+
parser_check.add_argument('package', nargs='*')
|
|
348
|
+
parser_check.set_defaults(func=_check_cmd)
|
|
349
|
+
|
|
350
|
+
args = parser.parse_args(argv)
|
|
351
|
+
if not getattr(args, 'func', None):
|
|
352
|
+
parser.print_help()
|
|
353
|
+
else:
|
|
354
|
+
args.func(args)
|
|
355
|
+
|
|
356
|
+
_main()
|
|
@@ -348,7 +348,7 @@ def _pkg_cmd(args) -> None:
|
|
|
348
348
|
]
|
|
349
349
|
pgs = list(itertools.chain.from_iterable([pg, *pg.children()] for pg in pgs))
|
|
350
350
|
|
|
351
|
-
num_threads = args.jobs or max(mp.cpu_count() //
|
|
351
|
+
num_threads = args.jobs or int(max(mp.cpu_count() // 1.5, 1))
|
|
352
352
|
futs: ta.List[cf.Future]
|
|
353
353
|
with cf.ThreadPoolExecutor(num_threads) as ex:
|
|
354
354
|
futs = [ex.submit(pg.gen) for pg in pgs]
|
|
@@ -5287,7 +5287,7 @@ def _pkg_cmd(args) -> None:
|
|
|
5287
5287
|
]
|
|
5288
5288
|
pgs = list(itertools.chain.from_iterable([pg, *pg.children()] for pg in pgs))
|
|
5289
5289
|
|
|
5290
|
-
num_threads = args.jobs or max(mp.cpu_count() //
|
|
5290
|
+
num_threads = args.jobs or int(max(mp.cpu_count() // 1.5, 1))
|
|
5291
5291
|
futs: ta.List[cf.Future]
|
|
5292
5292
|
with cf.ThreadPoolExecutor(num_threads) as ex:
|
|
5293
5293
|
futs = [ex.submit(pg.gen) for pg in pgs]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: omdev
|
|
3
|
-
Version: 0.0.0.
|
|
3
|
+
Version: 0.0.0.dev54
|
|
4
4
|
Summary: omdev
|
|
5
5
|
Author: wrmsr
|
|
6
6
|
License: BSD-3-Clause
|
|
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
|
|
|
12
12
|
Classifier: Operating System :: POSIX
|
|
13
13
|
Requires-Python: ~=3.12
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: omlish==0.0.0.
|
|
15
|
+
Requires-Dist: omlish==0.0.0.dev54
|
|
16
16
|
Provides-Extra: all
|
|
17
17
|
Requires-Dist: pycparser~=2.22; extra == "all"
|
|
18
18
|
Requires-Dist: cffi~=1.17; extra == "all"
|
|
@@ -12,7 +12,7 @@ authors = [
|
|
|
12
12
|
urls = {source = 'https://github.com/wrmsr/omlish'}
|
|
13
13
|
license = {text = 'BSD-3-Clause'}
|
|
14
14
|
requires-python = '~=3.12'
|
|
15
|
-
version = '0.0.0.
|
|
15
|
+
version = '0.0.0.dev54'
|
|
16
16
|
classifiers = [
|
|
17
17
|
'License :: OSI Approved :: BSD License',
|
|
18
18
|
'Development Status :: 2 - Pre-Alpha',
|
|
@@ -22,7 +22,7 @@ classifiers = [
|
|
|
22
22
|
]
|
|
23
23
|
description = 'omdev'
|
|
24
24
|
dependencies = [
|
|
25
|
-
'omlish == 0.0.0.
|
|
25
|
+
'omlish == 0.0.0.dev54',
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[project.optional-dependencies]
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import dataclasses as dc
|
|
2
|
-
import typing as ta
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
@dc.dataclass(frozen=True)
|
|
6
|
-
class CliCmd:
|
|
7
|
-
cmd_name: str
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@dc.dataclass(frozen=True)
|
|
11
|
-
class CliModule(CliCmd):
|
|
12
|
-
mod_name: str
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dc.dataclass(frozen=True)
|
|
16
|
-
class CliFunc(CliCmd):
|
|
17
|
-
fn: ta.Callable
|