omdev 0.0.0.dev212__py3-none-any.whl → 0.0.0.dev213__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- omdev/cc/cdeps.py +34 -1
- omdev/cc/cdeps.toml +19 -2
- omdev/cc/cli.py +13 -1
- omdev/ci/ci.py +71 -48
- omdev/ci/cli.py +12 -6
- omdev/ci/compose.py +26 -55
- omdev/ci/docker.py +35 -16
- omdev/ci/github/cache.py +153 -184
- omdev/ci/github/cacheapi.py +1 -1
- omdev/ci/github/cli.py +2 -2
- omdev/ci/github/curl.py +209 -0
- omdev/git/shallow.py +1 -1
- omdev/scripts/ci.py +936 -448
- omdev/scripts/interp.py +23 -0
- omdev/scripts/pyproject.py +23 -0
- omdev/tokens/tokenizert.py +1 -3
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev213.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev213.dist-info}/RECORD +22 -21
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev213.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev213.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev213.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev213.dist-info}/top_level.txt +0 -0
omdev/cc/cdeps.py
CHANGED
@@ -5,6 +5,7 @@ import typing as ta
|
|
5
5
|
from omlish import cached
|
6
6
|
from omlish import lang
|
7
7
|
from omlish import marshal as msh
|
8
|
+
from omlish.configs import all as configs
|
8
9
|
|
9
10
|
|
10
11
|
@dc.dataclass(frozen=True)
|
@@ -20,19 +21,51 @@ class Cdep:
|
|
20
21
|
|
21
22
|
#
|
22
23
|
|
24
|
+
sources: ta.Sequence[str] | None = None
|
23
25
|
include: ta.Sequence[str] | None = None
|
24
26
|
|
25
27
|
#
|
26
28
|
|
27
29
|
@dc.dataclass(frozen=True)
|
28
30
|
class Cmake:
|
29
|
-
|
31
|
+
@dc.dataclass(frozen=True)
|
32
|
+
class FetchContent:
|
33
|
+
url: str | None = None
|
34
|
+
|
35
|
+
@dc.dataclass(frozen=True)
|
36
|
+
class Git:
|
37
|
+
repository: str | None = None
|
38
|
+
tag: str | None = None
|
39
|
+
|
40
|
+
git: Git | None = None
|
41
|
+
|
42
|
+
fetch_content: FetchContent | None = None
|
30
43
|
|
31
44
|
cmake: Cmake | None = None
|
32
45
|
|
33
46
|
|
47
|
+
def process_marshaled_cdep(obj: ta.Any) -> ta.Any:
|
48
|
+
obj = configs.processing.matched_rewrite(
|
49
|
+
lambda s: s if isinstance(s, str) else ''.join(s),
|
50
|
+
obj,
|
51
|
+
('sources', None),
|
52
|
+
('include', None),
|
53
|
+
)
|
54
|
+
|
55
|
+
return obj
|
56
|
+
|
57
|
+
|
34
58
|
@cached.function
|
35
59
|
def load_cdeps() -> ta.Mapping[str, Cdep]:
|
36
60
|
src = lang.get_relative_resources(globals=globals())['cdeps.toml'].read_text()
|
37
61
|
dct = tomllib.loads(src)
|
62
|
+
|
63
|
+
dct = {
|
64
|
+
**dct,
|
65
|
+
'deps': {
|
66
|
+
k: process_marshaled_cdep(v)
|
67
|
+
for k, v in dct.get('deps', {}).items()
|
68
|
+
},
|
69
|
+
}
|
70
|
+
|
38
71
|
return msh.unmarshal(dct.get('deps', {}), ta.Mapping[str, Cdep]) # type: ignore
|
omdev/cc/cdeps.toml
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
[deps.httplib]
|
2
|
+
include = ['.']
|
3
|
+
|
4
|
+
[deps.httplib.git]
|
5
|
+
url = 'https://github.com/yhirose/cpp-httplib'
|
6
|
+
rev = 'a7bc00e3307fecdb4d67545e93be7b88cfb1e186'
|
7
|
+
subtrees = ['httplib.h']
|
8
|
+
|
9
|
+
[deps.httplib.cmake.fetch_content.git]
|
10
|
+
repository = 'https://github.com/yhirose/cpp-httplib.git'
|
11
|
+
tag = 'a7bc00e3307fecdb4d67545e93be7b88cfb1e186'
|
12
|
+
|
13
|
+
|
14
|
+
#
|
15
|
+
|
1
16
|
[deps.json]
|
2
17
|
include = ['single_include']
|
3
18
|
|
@@ -6,8 +21,9 @@ url = 'https://github.com/nlohmann/json'
|
|
6
21
|
rev = '9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03'
|
7
22
|
subtrees = ['single_include']
|
8
23
|
|
9
|
-
[deps.json.cmake]
|
10
|
-
|
24
|
+
[deps.json.cmake.fetch_content]
|
25
|
+
url = 'https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz'
|
26
|
+
|
11
27
|
|
12
28
|
#
|
13
29
|
|
@@ -19,6 +35,7 @@ url = 'https://github.com/pybind/pybind11'
|
|
19
35
|
rev = '945e251a6ce7273058b36214d94415e9c9530b8e'
|
20
36
|
subtrees = ['include']
|
21
37
|
|
38
|
+
|
22
39
|
#
|
23
40
|
|
24
41
|
[deps.ut]
|
omdev/cc/cli.py
CHANGED
@@ -18,6 +18,8 @@ Freestanding options:
|
|
18
18
|
TODO:
|
19
19
|
- cext interop
|
20
20
|
- gen cmake
|
21
|
+
- fix CFLAGS/CCFLAGS/CPPFLAGS/CXXFLAGS
|
22
|
+
- jit-gen cmake mode? multi-src builds
|
21
23
|
"""
|
22
24
|
import os
|
23
25
|
import shlex
|
@@ -35,6 +37,7 @@ from .. import magic
|
|
35
37
|
from ..cache import data as dcache
|
36
38
|
from .cdeps import Cdep
|
37
39
|
from .cdeps import load_cdeps
|
40
|
+
from .cdeps import process_marshaled_cdep
|
38
41
|
|
39
42
|
|
40
43
|
class Cli(ap.Cli):
|
@@ -64,16 +67,22 @@ class Cli(ap.Cli):
|
|
64
67
|
if src_magic.key == '@omlish-cdeps':
|
65
68
|
for dep in check.isinstance(src_magic.prepared, ta.Sequence):
|
66
69
|
if isinstance(dep, ta.Mapping):
|
67
|
-
dep =
|
70
|
+
dep = process_marshaled_cdep(dep)
|
71
|
+
dep = msh.unmarshal(dep, Cdep)
|
68
72
|
else:
|
69
73
|
dep = load_cdeps()[check.isinstance(dep, str)]
|
70
74
|
|
75
|
+
if dep.sources:
|
76
|
+
# TODO
|
77
|
+
raise NotImplementedError
|
78
|
+
|
71
79
|
dep_spec = dcache.GitSpec(
|
72
80
|
url=dep.git.url,
|
73
81
|
rev=dep.git.rev,
|
74
82
|
subtrees=dep.git.subtrees,
|
75
83
|
)
|
76
84
|
dep_dir = dcache.default().get(dep_spec)
|
85
|
+
|
77
86
|
for dep_inc in dep.include or []:
|
78
87
|
inc_dir = os.path.join(dep_dir, dep_inc)
|
79
88
|
check.state(os.path.isdir(inc_dir))
|
@@ -96,6 +105,9 @@ class Cli(ap.Cli):
|
|
96
105
|
if cflags := os.environ.get('CFLAGS'):
|
97
106
|
sh_parts.append(cflags) # Explicitly shell-unquoted
|
98
107
|
|
108
|
+
if ldflags := os.environ.get('LDFLAGS'):
|
109
|
+
sh_parts.append(ldflags) # Explicitly shell-unquoted
|
110
|
+
|
99
111
|
sh_parts.extend([
|
100
112
|
'-std=c++20',
|
101
113
|
shlex.quote(os.path.abspath(src_file)),
|
omdev/ci/ci.py
CHANGED
@@ -7,9 +7,10 @@ import tarfile
|
|
7
7
|
import tempfile
|
8
8
|
import typing as ta
|
9
9
|
|
10
|
+
from omlish.lite.cached import async_cached_nullary
|
10
11
|
from omlish.lite.cached import cached_nullary
|
11
12
|
from omlish.lite.check import check
|
12
|
-
from omlish.lite.contextmanagers import
|
13
|
+
from omlish.lite.contextmanagers import AsyncExitStacked
|
13
14
|
from omlish.lite.contextmanagers import defer
|
14
15
|
|
15
16
|
from .cache import FileCache
|
@@ -22,13 +23,14 @@ from .docker import is_docker_image_present
|
|
22
23
|
from .docker import load_docker_tar_cmd
|
23
24
|
from .docker import pull_docker_image
|
24
25
|
from .docker import save_docker_tar_cmd
|
26
|
+
from .docker import tag_docker_image
|
25
27
|
from .requirements import build_requirements_hash
|
26
28
|
from .requirements import download_requirements
|
27
29
|
from .shell import ShellCmd
|
28
30
|
from .utils import log_timing_context
|
29
31
|
|
30
32
|
|
31
|
-
class Ci(
|
33
|
+
class Ci(AsyncExitStacked):
|
32
34
|
FILE_NAME_HASH_LEN = 16
|
33
35
|
|
34
36
|
@dc.dataclass(frozen=True)
|
@@ -45,6 +47,9 @@ class Ci(ExitStacked):
|
|
45
47
|
requirements_txts: ta.Optional[ta.Sequence[str]] = None
|
46
48
|
|
47
49
|
always_pull: bool = False
|
50
|
+
always_build: bool = False
|
51
|
+
|
52
|
+
no_dependencies: bool = False
|
48
53
|
|
49
54
|
def __post_init__(self) -> None:
|
50
55
|
check.not_isinstance(self.requirements_txts, str)
|
@@ -64,7 +69,7 @@ class Ci(ExitStacked):
|
|
64
69
|
|
65
70
|
#
|
66
71
|
|
67
|
-
def _load_cache_docker_image(self, key: str) -> ta.Optional[str]:
|
72
|
+
async def _load_cache_docker_image(self, key: str) -> ta.Optional[str]:
|
68
73
|
if self._shell_cache is None:
|
69
74
|
return None
|
70
75
|
|
@@ -74,9 +79,9 @@ class Ci(ExitStacked):
|
|
74
79
|
|
75
80
|
get_cache_cmd = dc.replace(get_cache_cmd, s=f'{get_cache_cmd.s} | zstd -cd --long') # noqa
|
76
81
|
|
77
|
-
return load_docker_tar_cmd(get_cache_cmd)
|
82
|
+
return await load_docker_tar_cmd(get_cache_cmd)
|
78
83
|
|
79
|
-
def _save_cache_docker_image(self, key: str, image: str) -> None:
|
84
|
+
async def _save_cache_docker_image(self, key: str, image: str) -> None:
|
80
85
|
if self._shell_cache is None:
|
81
86
|
return
|
82
87
|
|
@@ -85,12 +90,12 @@ class Ci(ExitStacked):
|
|
85
90
|
|
86
91
|
put_cache_cmd = dc.replace(put_cache_cmd, s=f'zstd | {put_cache_cmd.s}')
|
87
92
|
|
88
|
-
save_docker_tar_cmd(image, put_cache_cmd)
|
93
|
+
await save_docker_tar_cmd(image, put_cache_cmd)
|
89
94
|
|
90
95
|
#
|
91
96
|
|
92
|
-
def _load_docker_image(self, image: str) -> None:
|
93
|
-
if not self._cfg.always_pull and is_docker_image_present(image):
|
97
|
+
async def _load_docker_image(self, image: str) -> None:
|
98
|
+
if not self._cfg.always_pull and (await is_docker_image_present(image)):
|
94
99
|
return
|
95
100
|
|
96
101
|
dep_suffix = image
|
@@ -98,63 +103,79 @@ class Ci(ExitStacked):
|
|
98
103
|
dep_suffix = dep_suffix.replace(c, '-')
|
99
104
|
|
100
105
|
cache_key = f'docker-{dep_suffix}'
|
101
|
-
if self._load_cache_docker_image(cache_key) is not None:
|
106
|
+
if (await self._load_cache_docker_image(cache_key)) is not None:
|
102
107
|
return
|
103
108
|
|
104
|
-
pull_docker_image(image)
|
109
|
+
await pull_docker_image(image)
|
105
110
|
|
106
|
-
self._save_cache_docker_image(cache_key, image)
|
111
|
+
await self._save_cache_docker_image(cache_key, image)
|
107
112
|
|
108
|
-
def load_docker_image(self, image: str) -> None:
|
113
|
+
async def load_docker_image(self, image: str) -> None:
|
109
114
|
with log_timing_context(f'Load docker image: {image}'):
|
110
|
-
self._load_docker_image(image)
|
115
|
+
await self._load_docker_image(image)
|
111
116
|
|
112
|
-
@
|
113
|
-
def load_compose_service_dependencies(self) -> None:
|
117
|
+
@async_cached_nullary
|
118
|
+
async def load_compose_service_dependencies(self) -> None:
|
114
119
|
deps = get_compose_service_dependencies(
|
115
120
|
self._cfg.compose_file,
|
116
121
|
self._cfg.service,
|
117
122
|
)
|
118
123
|
|
119
124
|
for dep_image in deps.values():
|
120
|
-
self.load_docker_image(dep_image)
|
125
|
+
await self.load_docker_image(dep_image)
|
121
126
|
|
122
127
|
#
|
123
128
|
|
124
|
-
|
125
|
-
|
129
|
+
@cached_nullary
|
130
|
+
def docker_file_hash(self) -> str:
|
131
|
+
return build_docker_file_hash(self._cfg.docker_file)[:self.FILE_NAME_HASH_LEN]
|
132
|
+
|
133
|
+
async def _resolve_ci_image(self) -> str:
|
134
|
+
cache_key = f'ci-{self.docker_file_hash()}'
|
135
|
+
image_tag = f'{self._cfg.service}:{cache_key}'
|
136
|
+
|
137
|
+
if not self._cfg.always_build and (await is_docker_image_present(image_tag)):
|
138
|
+
return image_tag
|
126
139
|
|
127
|
-
cache_key
|
128
|
-
|
129
|
-
|
140
|
+
if (cache_image_id := await self._load_cache_docker_image(cache_key)) is not None:
|
141
|
+
await tag_docker_image(
|
142
|
+
cache_image_id,
|
143
|
+
image_tag,
|
144
|
+
)
|
145
|
+
return image_tag
|
130
146
|
|
131
|
-
image_id = build_docker_image(
|
147
|
+
image_id = await build_docker_image(
|
132
148
|
self._cfg.docker_file,
|
149
|
+
tag=image_tag,
|
133
150
|
cwd=self._cfg.project_dir,
|
134
151
|
)
|
135
152
|
|
136
|
-
self._save_cache_docker_image(cache_key, image_id)
|
153
|
+
await self._save_cache_docker_image(cache_key, image_id)
|
137
154
|
|
138
|
-
return
|
155
|
+
return image_tag
|
139
156
|
|
140
|
-
@
|
141
|
-
def resolve_ci_image(self) -> str:
|
157
|
+
@async_cached_nullary
|
158
|
+
async def resolve_ci_image(self) -> str:
|
142
159
|
with log_timing_context('Resolve ci image') as ltc:
|
143
|
-
image_id = self._resolve_ci_image()
|
160
|
+
image_id = await self._resolve_ci_image()
|
144
161
|
ltc.set_description(f'Resolve ci image: {image_id}')
|
145
162
|
return image_id
|
146
163
|
|
147
164
|
#
|
148
165
|
|
149
|
-
|
150
|
-
|
166
|
+
@cached_nullary
|
167
|
+
def requirements_txts(self) -> ta.Sequence[str]:
|
168
|
+
return [
|
151
169
|
os.path.join(self._cfg.project_dir, rf)
|
152
170
|
for rf in check.not_none(self._cfg.requirements_txts)
|
153
171
|
]
|
154
172
|
|
155
|
-
|
173
|
+
@cached_nullary
|
174
|
+
def requirements_hash(self) -> str:
|
175
|
+
return build_requirements_hash(self.requirements_txts())[:self.FILE_NAME_HASH_LEN]
|
156
176
|
|
157
|
-
|
177
|
+
async def _resolve_requirements_dir(self) -> str:
|
178
|
+
tar_file_key = f'requirements-{self.docker_file_hash()}-{self.requirements_hash()}'
|
158
179
|
tar_file_name = f'{tar_file_key}.tar'
|
159
180
|
|
160
181
|
temp_dir = tempfile.mkdtemp()
|
@@ -170,9 +191,9 @@ class Ci(ExitStacked):
|
|
170
191
|
os.makedirs(temp_requirements_dir)
|
171
192
|
|
172
193
|
download_requirements(
|
173
|
-
self.resolve_ci_image(),
|
194
|
+
await self.resolve_ci_image(),
|
174
195
|
temp_requirements_dir,
|
175
|
-
requirements_txts,
|
196
|
+
self.requirements_txts(),
|
176
197
|
)
|
177
198
|
|
178
199
|
if self._file_cache is not None:
|
@@ -189,16 +210,16 @@ class Ci(ExitStacked):
|
|
189
210
|
|
190
211
|
return temp_requirements_dir
|
191
212
|
|
192
|
-
@
|
193
|
-
def resolve_requirements_dir(self) -> str:
|
213
|
+
@async_cached_nullary
|
214
|
+
async def resolve_requirements_dir(self) -> str:
|
194
215
|
with log_timing_context('Resolve requirements dir') as ltc:
|
195
|
-
requirements_dir = self._resolve_requirements_dir()
|
216
|
+
requirements_dir = await self._resolve_requirements_dir()
|
196
217
|
ltc.set_description(f'Resolve requirements dir: {requirements_dir}')
|
197
218
|
return requirements_dir
|
198
219
|
|
199
220
|
#
|
200
221
|
|
201
|
-
def _run_compose_(self) -> None:
|
222
|
+
async def _run_compose_(self) -> None:
|
202
223
|
setup_cmds = [
|
203
224
|
'pip install --root-user-action ignore --find-links /requirements --no-index uv',
|
204
225
|
(
|
@@ -216,34 +237,36 @@ class Ci(ExitStacked):
|
|
216
237
|
|
217
238
|
#
|
218
239
|
|
219
|
-
with DockerComposeRun(DockerComposeRun.Config(
|
240
|
+
async with DockerComposeRun(DockerComposeRun.Config(
|
220
241
|
compose_file=self._cfg.compose_file,
|
221
242
|
service=self._cfg.service,
|
222
243
|
|
223
|
-
image=self.resolve_ci_image(),
|
244
|
+
image=await self.resolve_ci_image(),
|
224
245
|
|
225
246
|
cmd=ci_cmd,
|
226
247
|
|
227
248
|
run_options=[
|
228
249
|
'-v', f'{os.path.abspath(self._cfg.project_dir)}:/project',
|
229
|
-
'-v', f'{os.path.abspath(self.resolve_requirements_dir())}:/requirements',
|
250
|
+
'-v', f'{os.path.abspath(await self.resolve_requirements_dir())}:/requirements',
|
230
251
|
],
|
231
252
|
|
232
253
|
cwd=self._cfg.project_dir,
|
254
|
+
|
255
|
+
no_dependencies=self._cfg.no_dependencies,
|
233
256
|
)) as ci_compose_run:
|
234
|
-
ci_compose_run.run()
|
257
|
+
await ci_compose_run.run()
|
235
258
|
|
236
|
-
def _run_compose(self) -> None:
|
259
|
+
async def _run_compose(self) -> None:
|
237
260
|
with log_timing_context('Run compose'):
|
238
|
-
self._run_compose_()
|
261
|
+
await self._run_compose_()
|
239
262
|
|
240
263
|
#
|
241
264
|
|
242
|
-
def run(self) -> None:
|
243
|
-
self.load_compose_service_dependencies()
|
265
|
+
async def run(self) -> None:
|
266
|
+
await self.load_compose_service_dependencies()
|
244
267
|
|
245
|
-
self.resolve_ci_image()
|
268
|
+
await self.resolve_ci_image()
|
246
269
|
|
247
|
-
self.resolve_requirements_dir()
|
270
|
+
await self.resolve_requirements_dir()
|
248
271
|
|
249
|
-
self._run_compose()
|
272
|
+
await self._run_compose()
|
omdev/ci/cli.py
CHANGED
@@ -76,18 +76,21 @@ class CiCli(ArgparseCli):
|
|
76
76
|
argparse_arg('--docker-file'),
|
77
77
|
argparse_arg('--compose-file'),
|
78
78
|
argparse_arg('-r', '--requirements-txt', action='append'),
|
79
|
+
|
79
80
|
argparse_arg('--github-cache', action='store_true'),
|
80
81
|
argparse_arg('--cache-dir'),
|
82
|
+
|
81
83
|
argparse_arg('--always-pull', action='store_true'),
|
84
|
+
argparse_arg('--always-build', action='store_true'),
|
85
|
+
|
86
|
+
argparse_arg('--no-dependencies', action='store_true'),
|
82
87
|
)
|
83
88
|
async def run(self) -> None:
|
84
89
|
project_dir = self.args.project_dir
|
85
90
|
docker_file = self.args.docker_file
|
86
91
|
compose_file = self.args.compose_file
|
87
|
-
service = self.args.service
|
88
92
|
requirements_txts = self.args.requirements_txt
|
89
93
|
cache_dir = self.args.cache_dir
|
90
|
-
always_pull = self.args.always_pull
|
91
94
|
|
92
95
|
#
|
93
96
|
|
@@ -157,14 +160,14 @@ class CiCli(ArgparseCli):
|
|
157
160
|
|
158
161
|
#
|
159
162
|
|
160
|
-
with Ci(
|
163
|
+
async with Ci(
|
161
164
|
Ci.Config(
|
162
165
|
project_dir=project_dir,
|
163
166
|
|
164
167
|
docker_file=docker_file,
|
165
168
|
|
166
169
|
compose_file=compose_file,
|
167
|
-
service=service,
|
170
|
+
service=self.args.service,
|
168
171
|
|
169
172
|
requirements_txts=requirements_txts,
|
170
173
|
|
@@ -173,12 +176,15 @@ class CiCli(ArgparseCli):
|
|
173
176
|
'python3 -m pytest -svv test.py',
|
174
177
|
])),
|
175
178
|
|
176
|
-
always_pull=always_pull,
|
179
|
+
always_pull=self.args.always_pull,
|
180
|
+
always_build=self.args.always_build,
|
181
|
+
|
182
|
+
no_dependencies=self.args.no_dependencies,
|
177
183
|
),
|
178
184
|
file_cache=file_cache,
|
179
185
|
shell_cache=shell_cache,
|
180
186
|
) as ci:
|
181
|
-
ci.run()
|
187
|
+
await ci.run()
|
182
188
|
|
183
189
|
|
184
190
|
async def _async_main() -> ta.Optional[int]:
|
omdev/ci/compose.py
CHANGED
@@ -11,12 +11,13 @@ import os.path
|
|
11
11
|
import shlex
|
12
12
|
import typing as ta
|
13
13
|
|
14
|
+
from omlish.asyncs.asyncio.subprocesses import asyncio_subprocesses
|
14
15
|
from omlish.lite.cached import cached_nullary
|
15
16
|
from omlish.lite.check import check
|
16
|
-
from omlish.lite.contextmanagers import
|
17
|
+
from omlish.lite.contextmanagers import AsyncExitStacked
|
18
|
+
from omlish.lite.contextmanagers import adefer
|
17
19
|
from omlish.lite.contextmanagers import defer
|
18
20
|
from omlish.lite.json import json_dumps_pretty
|
19
|
-
from omlish.subprocesses import subprocesses
|
20
21
|
|
21
22
|
from .shell import ShellCmd
|
22
23
|
from .utils import make_temp_file
|
@@ -46,7 +47,7 @@ def get_compose_service_dependencies(
|
|
46
47
|
##
|
47
48
|
|
48
49
|
|
49
|
-
class DockerComposeRun(
|
50
|
+
class DockerComposeRun(AsyncExitStacked):
|
50
51
|
@dc.dataclass(frozen=True)
|
51
52
|
class Config:
|
52
53
|
compose_file: str
|
@@ -64,6 +65,7 @@ class DockerComposeRun(ExitStacked):
|
|
64
65
|
|
65
66
|
#
|
66
67
|
|
68
|
+
no_dependencies: bool = False
|
67
69
|
no_dependency_cleanup: bool = False
|
68
70
|
|
69
71
|
#
|
@@ -82,40 +84,6 @@ class DockerComposeRun(ExitStacked):
|
|
82
84
|
|
83
85
|
#
|
84
86
|
|
85
|
-
@property
|
86
|
-
def image_tag(self) -> str:
|
87
|
-
pfx = 'sha256:'
|
88
|
-
if (image := self._cfg.image).startswith(pfx):
|
89
|
-
image = image[len(pfx):]
|
90
|
-
|
91
|
-
return f'{self._cfg.service}:{image}'
|
92
|
-
|
93
|
-
@cached_nullary
|
94
|
-
def tag_image(self) -> str:
|
95
|
-
image_tag = self.image_tag
|
96
|
-
|
97
|
-
subprocesses.check_call(
|
98
|
-
'docker',
|
99
|
-
'tag',
|
100
|
-
self._cfg.image,
|
101
|
-
image_tag,
|
102
|
-
**self._subprocess_kwargs,
|
103
|
-
)
|
104
|
-
|
105
|
-
def delete_tag() -> None:
|
106
|
-
subprocesses.check_call(
|
107
|
-
'docker',
|
108
|
-
'rmi',
|
109
|
-
image_tag,
|
110
|
-
**self._subprocess_kwargs,
|
111
|
-
)
|
112
|
-
|
113
|
-
self._enter_context(defer(delete_tag)) # noqa
|
114
|
-
|
115
|
-
return image_tag
|
116
|
-
|
117
|
-
#
|
118
|
-
|
119
87
|
def _rewrite_compose_dct(self, in_dct: ta.Dict[str, ta.Any]) -> ta.Dict[str, ta.Any]:
|
120
88
|
out = dict(in_dct)
|
121
89
|
|
@@ -129,7 +97,7 @@ class DockerComposeRun(ExitStacked):
|
|
129
97
|
in_service: dict = in_services[self._cfg.service]
|
130
98
|
out_services[self._cfg.service] = out_service = dict(in_service)
|
131
99
|
|
132
|
-
out_service['image'] = self.
|
100
|
+
out_service['image'] = self._cfg.image
|
133
101
|
|
134
102
|
for k in ['build', 'platform']:
|
135
103
|
if k in out_service:
|
@@ -142,16 +110,21 @@ class DockerComposeRun(ExitStacked):
|
|
142
110
|
|
143
111
|
#
|
144
112
|
|
145
|
-
|
113
|
+
if not self._cfg.no_dependencies:
|
114
|
+
depends_on = in_service.get('depends_on', [])
|
146
115
|
|
147
|
-
|
148
|
-
|
149
|
-
|
116
|
+
for dep_service, in_dep_service_dct in list(in_services.items()):
|
117
|
+
if dep_service not in depends_on:
|
118
|
+
continue
|
150
119
|
|
151
|
-
|
152
|
-
|
120
|
+
out_dep_service: dict = dict(in_dep_service_dct)
|
121
|
+
out_services[dep_service] = out_dep_service
|
153
122
|
|
154
|
-
|
123
|
+
out_dep_service['ports'] = []
|
124
|
+
|
125
|
+
else:
|
126
|
+
out_service['depends_on'] = []
|
127
|
+
out_service['links'] = []
|
155
128
|
|
156
129
|
#
|
157
130
|
|
@@ -177,22 +150,20 @@ class DockerComposeRun(ExitStacked):
|
|
177
150
|
|
178
151
|
#
|
179
152
|
|
180
|
-
def _cleanup_dependencies(self) -> None:
|
181
|
-
|
153
|
+
async def _cleanup_dependencies(self) -> None:
|
154
|
+
await asyncio_subprocesses.check_call(
|
182
155
|
'docker',
|
183
156
|
'compose',
|
184
157
|
'-f', self.rewrite_compose_file(),
|
185
158
|
'down',
|
186
159
|
)
|
187
160
|
|
188
|
-
def run(self) -> None:
|
189
|
-
self.tag_image()
|
190
|
-
|
161
|
+
async def run(self) -> None:
|
191
162
|
compose_file = self.rewrite_compose_file()
|
192
163
|
|
193
|
-
with contextlib.
|
194
|
-
if not self._cfg.no_dependency_cleanup:
|
195
|
-
es.
|
164
|
+
async with contextlib.AsyncExitStack() as es:
|
165
|
+
if not (self._cfg.no_dependencies or self._cfg.no_dependency_cleanup):
|
166
|
+
await es.enter_async_context(adefer(self._cleanup_dependencies)) # noqa
|
196
167
|
|
197
168
|
sh_cmd = ' '.join([
|
198
169
|
'docker',
|
@@ -211,7 +182,7 @@ class DockerComposeRun(ExitStacked):
|
|
211
182
|
|
212
183
|
run_cmd = dc.replace(self._cfg.cmd, s=sh_cmd)
|
213
184
|
|
214
|
-
run_cmd.run(
|
215
|
-
|
185
|
+
await run_cmd.run(
|
186
|
+
asyncio_subprocesses.check_call,
|
216
187
|
**self._subprocess_kwargs,
|
217
188
|
)
|