omdev 0.0.0.dev213__py3-none-any.whl → 0.0.0.dev215__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.
- omdev/.manifests.json +1 -1
- omdev/ci/__init__.py +1 -0
- omdev/ci/cache.py +100 -121
- omdev/ci/ci.py +120 -118
- omdev/ci/cli.py +50 -24
- omdev/ci/compose.py +1 -8
- omdev/ci/consts.py +1 -0
- omdev/ci/docker.py +4 -6
- omdev/ci/github/{cacheapi.py → api.py} +0 -1
- omdev/ci/github/bootstrap.py +8 -1
- omdev/ci/github/cache.py +36 -289
- omdev/ci/github/cli.py +9 -5
- omdev/ci/github/client.py +492 -0
- omdev/ci/github/env.py +21 -0
- omdev/ci/requirements.py +0 -1
- omdev/ci/shell.py +0 -1
- omdev/ci/utils.py +2 -14
- omdev/scripts/ci.py +1149 -922
- omdev/scripts/pyproject.py +79 -12
- omdev/tools/docker.py +6 -0
- {omdev-0.0.0.dev213.dist-info → omdev-0.0.0.dev215.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev213.dist-info → omdev-0.0.0.dev215.dist-info}/RECORD +26 -24
- omdev/ci/github/curl.py +0 -209
- {omdev-0.0.0.dev213.dist-info → omdev-0.0.0.dev215.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev213.dist-info → omdev-0.0.0.dev215.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev213.dist-info → omdev-0.0.0.dev215.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev213.dist-info → omdev-0.0.0.dev215.dist-info}/top_level.txt +0 -0
omdev/.manifests.json
CHANGED
omdev/ci/__init__.py
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
# @omlish-lite
|
omdev/ci/cache.py
CHANGED
@@ -1,12 +1,14 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
-
# @omlish-lite
|
3
2
|
import abc
|
4
3
|
import os.path
|
5
|
-
import shlex
|
6
4
|
import shutil
|
7
5
|
import typing as ta
|
8
6
|
|
9
|
-
from .
|
7
|
+
from omlish.lite.cached import cached_nullary
|
8
|
+
from omlish.lite.check import check
|
9
|
+
from omlish.lite.logs import log
|
10
|
+
|
11
|
+
from .consts import CI_CACHE_VERSION
|
10
12
|
|
11
13
|
|
12
14
|
##
|
@@ -14,12 +16,35 @@ from .shell import ShellCmd
|
|
14
16
|
|
15
17
|
@abc.abstractmethod
|
16
18
|
class FileCache(abc.ABC):
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
*,
|
22
|
+
version: int = CI_CACHE_VERSION,
|
23
|
+
) -> None:
|
24
|
+
super().__init__()
|
25
|
+
|
26
|
+
check.isinstance(version, int)
|
27
|
+
check.arg(version >= 0)
|
28
|
+
self._version = version
|
29
|
+
|
30
|
+
@property
|
31
|
+
def version(self) -> int:
|
32
|
+
return self._version
|
33
|
+
|
34
|
+
#
|
35
|
+
|
17
36
|
@abc.abstractmethod
|
18
|
-
def get_file(self, key: str) -> ta.Optional[str]:
|
37
|
+
def get_file(self, key: str) -> ta.Awaitable[ta.Optional[str]]:
|
19
38
|
raise NotImplementedError
|
20
39
|
|
21
40
|
@abc.abstractmethod
|
22
|
-
def put_file(
|
41
|
+
def put_file(
|
42
|
+
self,
|
43
|
+
key: str,
|
44
|
+
file_path: str,
|
45
|
+
*,
|
46
|
+
steal: bool = False,
|
47
|
+
) -> ta.Awaitable[str]:
|
23
48
|
raise NotImplementedError
|
24
49
|
|
25
50
|
|
@@ -27,142 +52,96 @@ class FileCache(abc.ABC):
|
|
27
52
|
|
28
53
|
|
29
54
|
class DirectoryFileCache(FileCache):
|
30
|
-
def __init__(
|
31
|
-
super().__init__()
|
32
|
-
|
33
|
-
self._dir = dir
|
34
|
-
|
35
|
-
#
|
36
|
-
|
37
|
-
def get_cache_file_path(
|
55
|
+
def __init__(
|
38
56
|
self,
|
39
|
-
|
57
|
+
dir: str, # noqa
|
40
58
|
*,
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
59
|
+
no_create: bool = False,
|
60
|
+
no_purge: bool = False,
|
61
|
+
**kwargs: ta.Any,
|
62
|
+
) -> None: # noqa
|
63
|
+
super().__init__(**kwargs)
|
46
64
|
|
47
|
-
|
48
|
-
|
65
|
+
self._dir = dir
|
66
|
+
self._no_create = no_create
|
67
|
+
self._no_purge = no_purge
|
49
68
|
|
50
69
|
#
|
51
70
|
|
52
|
-
|
53
|
-
cache_file_path = self.get_cache_file_path(key)
|
54
|
-
if not os.path.exists(cache_file_path):
|
55
|
-
return None
|
56
|
-
return cache_file_path
|
57
|
-
|
58
|
-
def put_file(self, key: str, file_path: str) -> None:
|
59
|
-
cache_file_path = self.get_cache_file_path(key, make_dirs=True)
|
60
|
-
shutil.copyfile(file_path, cache_file_path)
|
61
|
-
|
62
|
-
|
63
|
-
##
|
64
|
-
|
65
|
-
|
66
|
-
class ShellCache(abc.ABC):
|
67
|
-
@abc.abstractmethod
|
68
|
-
def get_file_cmd(self, key: str) -> ta.Optional[ShellCmd]:
|
69
|
-
raise NotImplementedError
|
70
|
-
|
71
|
-
class PutFileCmdContext(abc.ABC):
|
72
|
-
def __init__(self) -> None:
|
73
|
-
super().__init__()
|
74
|
-
|
75
|
-
self._state: ta.Literal['open', 'committed', 'aborted'] = 'open'
|
76
|
-
|
77
|
-
@property
|
78
|
-
def state(self) -> ta.Literal['open', 'committed', 'aborted']:
|
79
|
-
return self._state
|
71
|
+
VERSION_FILE_NAME = '.ci-cache-version'
|
80
72
|
|
81
|
-
|
73
|
+
@cached_nullary
|
74
|
+
def setup_dir(self) -> None:
|
75
|
+
version_file = os.path.join(self._dir, self.VERSION_FILE_NAME)
|
82
76
|
|
83
|
-
|
84
|
-
|
85
|
-
def cmd(self) -> ShellCmd:
|
86
|
-
raise NotImplementedError
|
77
|
+
if self._no_create:
|
78
|
+
check.state(os.path.isdir(self._dir))
|
87
79
|
|
88
|
-
|
80
|
+
elif not os.path.isdir(self._dir):
|
81
|
+
os.makedirs(self._dir)
|
82
|
+
with open(version_file, 'w') as f:
|
83
|
+
f.write(str(self._version))
|
84
|
+
return
|
89
85
|
|
90
|
-
|
91
|
-
|
86
|
+
with open(version_file) as f:
|
87
|
+
dir_version = int(f.read().strip())
|
92
88
|
|
93
|
-
|
94
|
-
|
95
|
-
self.commit()
|
96
|
-
else:
|
97
|
-
self.abort()
|
89
|
+
if dir_version == self._version:
|
90
|
+
return
|
98
91
|
|
99
|
-
|
92
|
+
if self._no_purge:
|
93
|
+
raise RuntimeError(f'{dir_version=} != {self._version=}')
|
100
94
|
|
101
|
-
|
102
|
-
|
103
|
-
raise
|
95
|
+
dirs = [n for n in sorted(os.listdir(self._dir)) if os.path.isdir(os.path.join(self._dir, n))]
|
96
|
+
if dirs:
|
97
|
+
raise RuntimeError(
|
98
|
+
f'Refusing to remove stale cache dir {self._dir!r} '
|
99
|
+
f'due to present directories: {", ".join(dirs)}',
|
100
|
+
)
|
104
101
|
|
105
|
-
|
106
|
-
if
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
raise RuntimeError(self._state)
|
102
|
+
for n in sorted(os.listdir(self._dir)):
|
103
|
+
if n.startswith('.'):
|
104
|
+
continue
|
105
|
+
fp = os.path.join(self._dir, n)
|
106
|
+
check.state(os.path.isfile(fp))
|
107
|
+
log.debug('Purging stale cache file: %s', fp)
|
108
|
+
os.unlink(fp)
|
113
109
|
|
114
|
-
|
115
|
-
|
116
|
-
@abc.abstractmethod
|
117
|
-
def _abort(self) -> None:
|
118
|
-
raise NotImplementedError
|
119
|
-
|
120
|
-
def abort(self) -> None:
|
121
|
-
if self._state == 'aborted':
|
122
|
-
return
|
123
|
-
elif self._state == 'open':
|
124
|
-
self._abort()
|
125
|
-
self._state = 'committed'
|
126
|
-
else:
|
127
|
-
raise RuntimeError(self._state)
|
128
|
-
|
129
|
-
@abc.abstractmethod
|
130
|
-
def put_file_cmd(self, key: str) -> PutFileCmdContext:
|
131
|
-
raise NotImplementedError
|
110
|
+
os.unlink(version_file)
|
132
111
|
|
112
|
+
with open(version_file, 'w') as f:
|
113
|
+
f.write(str(self._version))
|
133
114
|
|
134
|
-
#
|
115
|
+
#
|
135
116
|
|
117
|
+
def get_cache_file_path(
|
118
|
+
self,
|
119
|
+
key: str,
|
120
|
+
) -> str:
|
121
|
+
self.setup_dir()
|
122
|
+
return os.path.join(self._dir, key)
|
136
123
|
|
137
|
-
|
138
|
-
|
139
|
-
super().__init__()
|
124
|
+
def format_incomplete_file(self, f: str) -> str:
|
125
|
+
return os.path.join(os.path.dirname(f), f'_{os.path.basename(f)}.incomplete')
|
140
126
|
|
141
|
-
|
127
|
+
#
|
142
128
|
|
143
|
-
def
|
144
|
-
|
145
|
-
if
|
129
|
+
async def get_file(self, key: str) -> ta.Optional[str]:
|
130
|
+
cache_file_path = self.get_cache_file_path(key)
|
131
|
+
if not os.path.exists(cache_file_path):
|
146
132
|
return None
|
147
|
-
return
|
148
|
-
|
149
|
-
class _PutFileCmdContext(ShellCache.PutFileCmdContext): # noqa
|
150
|
-
def __init__(self, tf: str, f: str) -> None:
|
151
|
-
super().__init__()
|
152
|
-
|
153
|
-
self._tf = tf
|
154
|
-
self._f = f
|
155
|
-
|
156
|
-
@property
|
157
|
-
def cmd(self) -> ShellCmd:
|
158
|
-
return ShellCmd(f'cat > {shlex.quote(self._tf)}')
|
159
|
-
|
160
|
-
def _commit(self) -> None:
|
161
|
-
os.replace(self._tf, self._f)
|
162
|
-
|
163
|
-
def _abort(self) -> None:
|
164
|
-
os.unlink(self._tf)
|
133
|
+
return cache_file_path
|
165
134
|
|
166
|
-
def
|
167
|
-
|
168
|
-
|
135
|
+
async def put_file(
|
136
|
+
self,
|
137
|
+
key: str,
|
138
|
+
file_path: str,
|
139
|
+
*,
|
140
|
+
steal: bool = False,
|
141
|
+
) -> str:
|
142
|
+
cache_file_path = self.get_cache_file_path(key)
|
143
|
+
if steal:
|
144
|
+
shutil.move(file_path, cache_file_path)
|
145
|
+
else:
|
146
|
+
shutil.copyfile(file_path, cache_file_path)
|
147
|
+
return cache_file_path
|
omdev/ci/ci.py
CHANGED
@@ -1,20 +1,15 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
-
# @omlish-lite
|
3
2
|
import dataclasses as dc
|
4
3
|
import os.path
|
5
|
-
import shutil
|
6
|
-
import tarfile
|
7
|
-
import tempfile
|
8
4
|
import typing as ta
|
9
5
|
|
10
6
|
from omlish.lite.cached import async_cached_nullary
|
11
7
|
from omlish.lite.cached import cached_nullary
|
12
8
|
from omlish.lite.check import check
|
13
9
|
from omlish.lite.contextmanagers import AsyncExitStacked
|
14
|
-
from omlish.
|
10
|
+
from omlish.os.temp import temp_file_context
|
15
11
|
|
16
12
|
from .cache import FileCache
|
17
|
-
from .cache import ShellCache
|
18
13
|
from .compose import DockerComposeRun
|
19
14
|
from .compose import get_compose_service_dependencies
|
20
15
|
from .docker import build_docker_file_hash
|
@@ -25,13 +20,12 @@ from .docker import pull_docker_image
|
|
25
20
|
from .docker import save_docker_tar_cmd
|
26
21
|
from .docker import tag_docker_image
|
27
22
|
from .requirements import build_requirements_hash
|
28
|
-
from .requirements import download_requirements
|
29
23
|
from .shell import ShellCmd
|
30
24
|
from .utils import log_timing_context
|
31
25
|
|
32
26
|
|
33
27
|
class Ci(AsyncExitStacked):
|
34
|
-
|
28
|
+
KEY_HASH_LEN = 16
|
35
29
|
|
36
30
|
@dc.dataclass(frozen=True)
|
37
31
|
class Config:
|
@@ -44,6 +38,8 @@ class Ci(AsyncExitStacked):
|
|
44
38
|
|
45
39
|
cmd: ShellCmd
|
46
40
|
|
41
|
+
#
|
42
|
+
|
47
43
|
requirements_txts: ta.Optional[ta.Sequence[str]] = None
|
48
44
|
|
49
45
|
always_pull: bool = False
|
@@ -51,6 +47,10 @@ class Ci(AsyncExitStacked):
|
|
51
47
|
|
52
48
|
no_dependencies: bool = False
|
53
49
|
|
50
|
+
run_options: ta.Optional[ta.Sequence[str]] = None
|
51
|
+
|
52
|
+
#
|
53
|
+
|
54
54
|
def __post_init__(self) -> None:
|
55
55
|
check.not_isinstance(self.requirements_txts, str)
|
56
56
|
|
@@ -58,42 +58,15 @@ class Ci(AsyncExitStacked):
|
|
58
58
|
self,
|
59
59
|
cfg: Config,
|
60
60
|
*,
|
61
|
-
shell_cache: ta.Optional[ShellCache] = None,
|
62
61
|
file_cache: ta.Optional[FileCache] = None,
|
63
62
|
) -> None:
|
64
63
|
super().__init__()
|
65
64
|
|
66
65
|
self._cfg = cfg
|
67
|
-
self._shell_cache = shell_cache
|
68
66
|
self._file_cache = file_cache
|
69
67
|
|
70
68
|
#
|
71
69
|
|
72
|
-
async def _load_cache_docker_image(self, key: str) -> ta.Optional[str]:
|
73
|
-
if self._shell_cache is None:
|
74
|
-
return None
|
75
|
-
|
76
|
-
get_cache_cmd = self._shell_cache.get_file_cmd(key)
|
77
|
-
if get_cache_cmd is None:
|
78
|
-
return None
|
79
|
-
|
80
|
-
get_cache_cmd = dc.replace(get_cache_cmd, s=f'{get_cache_cmd.s} | zstd -cd --long') # noqa
|
81
|
-
|
82
|
-
return await load_docker_tar_cmd(get_cache_cmd)
|
83
|
-
|
84
|
-
async def _save_cache_docker_image(self, key: str, image: str) -> None:
|
85
|
-
if self._shell_cache is None:
|
86
|
-
return
|
87
|
-
|
88
|
-
with self._shell_cache.put_file_cmd(key) as put_cache:
|
89
|
-
put_cache_cmd = put_cache.cmd
|
90
|
-
|
91
|
-
put_cache_cmd = dc.replace(put_cache_cmd, s=f'zstd | {put_cache_cmd.s}')
|
92
|
-
|
93
|
-
await save_docker_tar_cmd(image, put_cache_cmd)
|
94
|
-
|
95
|
-
#
|
96
|
-
|
97
70
|
async def _load_docker_image(self, image: str) -> None:
|
98
71
|
if not self._cfg.always_pull and (await is_docker_image_present(image)):
|
99
72
|
return
|
@@ -114,24 +87,38 @@ class Ci(AsyncExitStacked):
|
|
114
87
|
with log_timing_context(f'Load docker image: {image}'):
|
115
88
|
await self._load_docker_image(image)
|
116
89
|
|
117
|
-
|
118
|
-
async def load_compose_service_dependencies(self) -> None:
|
119
|
-
deps = get_compose_service_dependencies(
|
120
|
-
self._cfg.compose_file,
|
121
|
-
self._cfg.service,
|
122
|
-
)
|
90
|
+
#
|
123
91
|
|
124
|
-
|
125
|
-
|
92
|
+
async def _load_cache_docker_image(self, key: str) -> ta.Optional[str]:
|
93
|
+
if self._file_cache is None:
|
94
|
+
return None
|
126
95
|
|
127
|
-
|
96
|
+
cache_file = await self._file_cache.get_file(key)
|
97
|
+
if cache_file is None:
|
98
|
+
return None
|
128
99
|
|
129
|
-
|
130
|
-
def docker_file_hash(self) -> str:
|
131
|
-
return build_docker_file_hash(self._cfg.docker_file)[:self.FILE_NAME_HASH_LEN]
|
100
|
+
get_cache_cmd = ShellCmd(f'cat {cache_file} | zstd -cd --long')
|
132
101
|
|
133
|
-
|
134
|
-
|
102
|
+
return await load_docker_tar_cmd(get_cache_cmd)
|
103
|
+
|
104
|
+
async def _save_cache_docker_image(self, key: str, image: str) -> None:
|
105
|
+
if self._file_cache is None:
|
106
|
+
return
|
107
|
+
|
108
|
+
with temp_file_context() as tmp_file:
|
109
|
+
write_tmp_cmd = ShellCmd(f'zstd > {tmp_file}')
|
110
|
+
|
111
|
+
await save_docker_tar_cmd(image, write_tmp_cmd)
|
112
|
+
|
113
|
+
await self._file_cache.put_file(key, tmp_file, steal=True)
|
114
|
+
|
115
|
+
#
|
116
|
+
|
117
|
+
async def _resolve_docker_image(
|
118
|
+
self,
|
119
|
+
cache_key: str,
|
120
|
+
build_and_tag: ta.Callable[[str], ta.Awaitable[str]],
|
121
|
+
) -> str:
|
135
122
|
image_tag = f'{self._cfg.service}:{cache_key}'
|
136
123
|
|
137
124
|
if not self._cfg.always_build and (await is_docker_image_present(image_tag)):
|
@@ -144,21 +131,35 @@ class Ci(AsyncExitStacked):
|
|
144
131
|
)
|
145
132
|
return image_tag
|
146
133
|
|
147
|
-
image_id = await
|
148
|
-
self._cfg.docker_file,
|
149
|
-
tag=image_tag,
|
150
|
-
cwd=self._cfg.project_dir,
|
151
|
-
)
|
134
|
+
image_id = await build_and_tag(image_tag)
|
152
135
|
|
153
136
|
await self._save_cache_docker_image(cache_key, image_id)
|
154
137
|
|
155
138
|
return image_tag
|
156
139
|
|
140
|
+
#
|
141
|
+
|
142
|
+
@cached_nullary
|
143
|
+
def docker_file_hash(self) -> str:
|
144
|
+
return build_docker_file_hash(self._cfg.docker_file)[:self.KEY_HASH_LEN]
|
145
|
+
|
146
|
+
async def _resolve_ci_base_image(self) -> str:
|
147
|
+
async def build_and_tag(image_tag: str) -> str:
|
148
|
+
return await build_docker_image(
|
149
|
+
self._cfg.docker_file,
|
150
|
+
tag=image_tag,
|
151
|
+
cwd=self._cfg.project_dir,
|
152
|
+
)
|
153
|
+
|
154
|
+
cache_key = f'ci-base-{self.docker_file_hash()}'
|
155
|
+
|
156
|
+
return await self._resolve_docker_image(cache_key, build_and_tag)
|
157
|
+
|
157
158
|
@async_cached_nullary
|
158
|
-
async def
|
159
|
-
with log_timing_context('Resolve ci image') as ltc:
|
160
|
-
image_id = await self.
|
161
|
-
ltc.set_description(f'Resolve ci image: {image_id}')
|
159
|
+
async def resolve_ci_base_image(self) -> str:
|
160
|
+
with log_timing_context('Resolve ci base image') as ltc:
|
161
|
+
image_id = await self._resolve_ci_base_image()
|
162
|
+
ltc.set_description(f'Resolve ci base image: {image_id}')
|
162
163
|
return image_id
|
163
164
|
|
164
165
|
#
|
@@ -172,82 +173,85 @@ class Ci(AsyncExitStacked):
|
|
172
173
|
|
173
174
|
@cached_nullary
|
174
175
|
def requirements_hash(self) -> str:
|
175
|
-
return build_requirements_hash(self.requirements_txts())[:self.
|
176
|
-
|
177
|
-
async def _resolve_requirements_dir(self) -> str:
|
178
|
-
tar_file_key = f'requirements-{self.docker_file_hash()}-{self.requirements_hash()}'
|
179
|
-
tar_file_name = f'{tar_file_key}.tar'
|
180
|
-
|
181
|
-
temp_dir = tempfile.mkdtemp()
|
182
|
-
self._enter_context(defer(lambda: shutil.rmtree(temp_dir))) # noqa
|
176
|
+
return build_requirements_hash(self.requirements_txts())[:self.KEY_HASH_LEN]
|
183
177
|
|
184
|
-
|
185
|
-
|
186
|
-
|
178
|
+
async def _resolve_ci_image(self) -> str:
|
179
|
+
async def build_and_tag(image_tag: str) -> str:
|
180
|
+
base_image = await self.resolve_ci_base_image()
|
181
|
+
|
182
|
+
setup_cmds = [
|
183
|
+
' '.join([
|
184
|
+
'pip install',
|
185
|
+
'--no-cache-dir',
|
186
|
+
'--root-user-action ignore',
|
187
|
+
'uv',
|
188
|
+
]),
|
189
|
+
' '.join([
|
190
|
+
'uv pip install',
|
191
|
+
'--no-cache',
|
192
|
+
'--index-strategy unsafe-best-match',
|
193
|
+
'--system',
|
194
|
+
*[f'-r /project/{rf}' for rf in self._cfg.requirements_txts or []],
|
195
|
+
]),
|
196
|
+
]
|
197
|
+
setup_cmd = ' && '.join(setup_cmds)
|
198
|
+
|
199
|
+
docker_file_lines = [
|
200
|
+
f'FROM {base_image}',
|
201
|
+
'RUN mkdir /project',
|
202
|
+
*[f'COPY {rf} /project/{rf}' for rf in self._cfg.requirements_txts or []],
|
203
|
+
f'RUN {setup_cmd}',
|
204
|
+
'RUN rm /project/*',
|
205
|
+
'WORKDIR /project',
|
206
|
+
]
|
207
|
+
|
208
|
+
with temp_file_context() as docker_file:
|
209
|
+
with open(docker_file, 'w') as f: # noqa
|
210
|
+
f.write('\n'.join(docker_file_lines))
|
211
|
+
|
212
|
+
return await build_docker_image(
|
213
|
+
docker_file,
|
214
|
+
tag=image_tag,
|
215
|
+
cwd=self._cfg.project_dir,
|
216
|
+
)
|
217
|
+
|
218
|
+
cache_key = f'ci-{self.docker_file_hash()}-{self.requirements_hash()}'
|
219
|
+
|
220
|
+
return await self._resolve_docker_image(cache_key, build_and_tag)
|
187
221
|
|
188
|
-
|
222
|
+
@async_cached_nullary
|
223
|
+
async def resolve_ci_image(self) -> str:
|
224
|
+
with log_timing_context('Resolve ci image') as ltc:
|
225
|
+
image_id = await self._resolve_ci_image()
|
226
|
+
ltc.set_description(f'Resolve ci image: {image_id}')
|
227
|
+
return image_id
|
189
228
|
|
190
|
-
|
191
|
-
os.makedirs(temp_requirements_dir)
|
229
|
+
#
|
192
230
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
self.
|
231
|
+
@async_cached_nullary
|
232
|
+
async def load_dependencies(self) -> None:
|
233
|
+
deps = get_compose_service_dependencies(
|
234
|
+
self._cfg.compose_file,
|
235
|
+
self._cfg.service,
|
197
236
|
)
|
198
237
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
with tarfile.open(temp_tar_file, 'w') as tar:
|
203
|
-
for requirement_file in os.listdir(temp_requirements_dir):
|
204
|
-
tar.add(
|
205
|
-
os.path.join(temp_requirements_dir, requirement_file),
|
206
|
-
arcname=requirement_file,
|
207
|
-
)
|
208
|
-
|
209
|
-
self._file_cache.put_file(os.path.basename(tar_file_key), temp_tar_file)
|
210
|
-
|
211
|
-
return temp_requirements_dir
|
212
|
-
|
213
|
-
@async_cached_nullary
|
214
|
-
async def resolve_requirements_dir(self) -> str:
|
215
|
-
with log_timing_context('Resolve requirements dir') as ltc:
|
216
|
-
requirements_dir = await self._resolve_requirements_dir()
|
217
|
-
ltc.set_description(f'Resolve requirements dir: {requirements_dir}')
|
218
|
-
return requirements_dir
|
238
|
+
for dep_image in deps.values():
|
239
|
+
await self.load_docker_image(dep_image)
|
219
240
|
|
220
241
|
#
|
221
242
|
|
222
243
|
async def _run_compose_(self) -> None:
|
223
|
-
setup_cmds = [
|
224
|
-
'pip install --root-user-action ignore --find-links /requirements --no-index uv',
|
225
|
-
(
|
226
|
-
'uv pip install --system --find-links /requirements ' +
|
227
|
-
' '.join(f'-r /project/{rf}' for rf in self._cfg.requirements_txts or [])
|
228
|
-
),
|
229
|
-
]
|
230
|
-
|
231
|
-
#
|
232
|
-
|
233
|
-
ci_cmd = dc.replace(self._cfg.cmd, s=' && '.join([
|
234
|
-
*setup_cmds,
|
235
|
-
f'({self._cfg.cmd.s})',
|
236
|
-
]))
|
237
|
-
|
238
|
-
#
|
239
|
-
|
240
244
|
async with DockerComposeRun(DockerComposeRun.Config(
|
241
245
|
compose_file=self._cfg.compose_file,
|
242
246
|
service=self._cfg.service,
|
243
247
|
|
244
248
|
image=await self.resolve_ci_image(),
|
245
249
|
|
246
|
-
cmd=
|
250
|
+
cmd=self._cfg.cmd,
|
247
251
|
|
248
252
|
run_options=[
|
249
253
|
'-v', f'{os.path.abspath(self._cfg.project_dir)}:/project',
|
250
|
-
|
254
|
+
*(self._cfg.run_options or []),
|
251
255
|
],
|
252
256
|
|
253
257
|
cwd=self._cfg.project_dir,
|
@@ -263,10 +267,8 @@ class Ci(AsyncExitStacked):
|
|
263
267
|
#
|
264
268
|
|
265
269
|
async def run(self) -> None:
|
266
|
-
await self.load_compose_service_dependencies()
|
267
|
-
|
268
270
|
await self.resolve_ci_image()
|
269
271
|
|
270
|
-
await self.
|
272
|
+
await self.load_dependencies()
|
271
273
|
|
272
274
|
await self._run_compose()
|