omdev 0.0.0.dev212__py3-none-any.whl → 0.0.0.dev214__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/cc/cdeps.py +34 -1
- omdev/cc/cdeps.toml +19 -2
- omdev/cc/cli.py +13 -1
- omdev/ci/__init__.py +1 -0
- omdev/ci/cache.py +100 -121
- omdev/ci/ci.py +161 -136
- omdev/ci/cli.py +62 -30
- omdev/ci/compose.py +26 -62
- omdev/ci/consts.py +1 -0
- omdev/ci/docker.py +39 -22
- omdev/ci/github/{cacheapi.py → api.py} +1 -2
- omdev/ci/github/bootstrap.py +8 -1
- omdev/ci/github/cache.py +36 -320
- 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/git/shallow.py +1 -1
- omdev/scripts/ci.py +1602 -887
- omdev/scripts/interp.py +23 -0
- omdev/scripts/pyproject.py +23 -0
- omdev/tokens/tokenizert.py +1 -3
- omdev/tools/docker.py +6 -0
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev214.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev214.dist-info}/RECORD +32 -29
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev214.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev214.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev214.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev212.dist-info → omdev-0.0.0.dev214.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,492 @@
|
|
1
|
+
# ruff: noqa: TC003 UP006 UP007
|
2
|
+
import abc
|
3
|
+
import asyncio
|
4
|
+
import dataclasses as dc
|
5
|
+
import http.client
|
6
|
+
import json
|
7
|
+
import os
|
8
|
+
import typing as ta
|
9
|
+
import urllib.parse
|
10
|
+
import urllib.request
|
11
|
+
|
12
|
+
from omlish.asyncs.asyncio.asyncio import asyncio_wait_concurrent
|
13
|
+
from omlish.lite.check import check
|
14
|
+
from omlish.lite.json import json_dumps_compact
|
15
|
+
from omlish.lite.logs import log
|
16
|
+
|
17
|
+
from ..consts import CI_CACHE_VERSION
|
18
|
+
from ..utils import log_timing_context
|
19
|
+
from .api import GithubCacheServiceV1
|
20
|
+
from .env import register_github_env_var
|
21
|
+
|
22
|
+
|
23
|
+
##
|
24
|
+
|
25
|
+
|
26
|
+
class GithubCacheClient(abc.ABC):
|
27
|
+
class Entry(abc.ABC): # noqa
|
28
|
+
pass
|
29
|
+
|
30
|
+
@abc.abstractmethod
|
31
|
+
def get_entry(self, key: str) -> ta.Awaitable[ta.Optional[Entry]]:
|
32
|
+
raise NotImplementedError
|
33
|
+
|
34
|
+
@abc.abstractmethod
|
35
|
+
def download_file(self, entry: Entry, out_file: str) -> ta.Awaitable[None]:
|
36
|
+
raise NotImplementedError
|
37
|
+
|
38
|
+
@abc.abstractmethod
|
39
|
+
def upload_file(self, key: str, in_file: str) -> ta.Awaitable[None]:
|
40
|
+
raise NotImplementedError
|
41
|
+
|
42
|
+
|
43
|
+
##
|
44
|
+
|
45
|
+
|
46
|
+
class GithubCacheServiceV1BaseClient(GithubCacheClient, abc.ABC):
|
47
|
+
BASE_URL_ENV_VAR = register_github_env_var('ACTIONS_CACHE_URL')
|
48
|
+
AUTH_TOKEN_ENV_VAR = register_github_env_var('ACTIONS_RUNTIME_TOKEN') # noqa
|
49
|
+
|
50
|
+
KEY_SUFFIX_ENV_VAR = register_github_env_var('GITHUB_RUN_ID')
|
51
|
+
|
52
|
+
#
|
53
|
+
|
54
|
+
def __init__(
|
55
|
+
self,
|
56
|
+
*,
|
57
|
+
base_url: ta.Optional[str] = None,
|
58
|
+
auth_token: ta.Optional[str] = None,
|
59
|
+
|
60
|
+
key_prefix: ta.Optional[str] = None,
|
61
|
+
key_suffix: ta.Optional[str] = None,
|
62
|
+
|
63
|
+
cache_version: int = CI_CACHE_VERSION,
|
64
|
+
|
65
|
+
loop: ta.Optional[asyncio.AbstractEventLoop] = None,
|
66
|
+
) -> None:
|
67
|
+
super().__init__()
|
68
|
+
|
69
|
+
#
|
70
|
+
|
71
|
+
if base_url is None:
|
72
|
+
base_url = check.non_empty_str(self.BASE_URL_ENV_VAR())
|
73
|
+
self._service_url = GithubCacheServiceV1.get_service_url(base_url)
|
74
|
+
|
75
|
+
if auth_token is None:
|
76
|
+
auth_token = self.AUTH_TOKEN_ENV_VAR()
|
77
|
+
self._auth_token = auth_token
|
78
|
+
|
79
|
+
#
|
80
|
+
|
81
|
+
self._key_prefix = key_prefix
|
82
|
+
|
83
|
+
if key_suffix is None:
|
84
|
+
key_suffix = self.KEY_SUFFIX_ENV_VAR()
|
85
|
+
self._key_suffix = check.non_empty_str(key_suffix)
|
86
|
+
|
87
|
+
#
|
88
|
+
|
89
|
+
self._cache_version = check.isinstance(cache_version, int)
|
90
|
+
|
91
|
+
#
|
92
|
+
|
93
|
+
self._given_loop = loop
|
94
|
+
|
95
|
+
#
|
96
|
+
|
97
|
+
def _get_loop(self) -> asyncio.AbstractEventLoop:
|
98
|
+
if (loop := self._given_loop) is not None:
|
99
|
+
return loop
|
100
|
+
return asyncio.get_event_loop()
|
101
|
+
|
102
|
+
#
|
103
|
+
|
104
|
+
def build_request_headers(
|
105
|
+
self,
|
106
|
+
headers: ta.Optional[ta.Mapping[str, str]] = None,
|
107
|
+
*,
|
108
|
+
content_type: ta.Optional[str] = None,
|
109
|
+
json_content: bool = False,
|
110
|
+
) -> ta.Dict[str, str]:
|
111
|
+
dct = {
|
112
|
+
'Accept': ';'.join([
|
113
|
+
'application/json',
|
114
|
+
f'api-version={GithubCacheServiceV1.API_VERSION}',
|
115
|
+
]),
|
116
|
+
}
|
117
|
+
|
118
|
+
if (auth_token := self._auth_token):
|
119
|
+
dct['Authorization'] = f'Bearer {auth_token}'
|
120
|
+
|
121
|
+
if content_type is None and json_content:
|
122
|
+
content_type = 'application/json'
|
123
|
+
if content_type is not None:
|
124
|
+
dct['Content-Type'] = content_type
|
125
|
+
|
126
|
+
if headers:
|
127
|
+
dct.update(headers)
|
128
|
+
|
129
|
+
return dct
|
130
|
+
|
131
|
+
#
|
132
|
+
|
133
|
+
def load_json_bytes(self, b: ta.Optional[bytes]) -> ta.Optional[ta.Any]:
|
134
|
+
if not b:
|
135
|
+
return None
|
136
|
+
return json.loads(b.decode('utf-8-sig'))
|
137
|
+
|
138
|
+
#
|
139
|
+
|
140
|
+
async def send_url_request(
|
141
|
+
self,
|
142
|
+
req: urllib.request.Request,
|
143
|
+
) -> ta.Tuple[http.client.HTTPResponse, ta.Optional[bytes]]:
|
144
|
+
def run_sync():
|
145
|
+
with urllib.request.urlopen(req) as resp: # noqa
|
146
|
+
body = resp.read()
|
147
|
+
return (resp, body)
|
148
|
+
|
149
|
+
return await self._get_loop().run_in_executor(None, run_sync) # noqa
|
150
|
+
|
151
|
+
#
|
152
|
+
|
153
|
+
@dc.dataclass()
|
154
|
+
class ServiceRequestError(RuntimeError):
|
155
|
+
status_code: int
|
156
|
+
body: ta.Optional[bytes]
|
157
|
+
|
158
|
+
def __str__(self) -> str:
|
159
|
+
return repr(self)
|
160
|
+
|
161
|
+
async def send_service_request(
|
162
|
+
self,
|
163
|
+
path: str,
|
164
|
+
*,
|
165
|
+
method: ta.Optional[str] = None,
|
166
|
+
headers: ta.Optional[ta.Mapping[str, str]] = None,
|
167
|
+
content_type: ta.Optional[str] = None,
|
168
|
+
content: ta.Optional[bytes] = None,
|
169
|
+
json_content: ta.Optional[ta.Any] = None,
|
170
|
+
success_status_codes: ta.Optional[ta.Container[int]] = None,
|
171
|
+
) -> ta.Optional[ta.Any]:
|
172
|
+
url = f'{self._service_url}/{path}'
|
173
|
+
|
174
|
+
if content is not None and json_content is not None:
|
175
|
+
raise RuntimeError('Must not pass both content and json_content')
|
176
|
+
elif json_content is not None:
|
177
|
+
content = json_dumps_compact(json_content).encode('utf-8')
|
178
|
+
header_json_content = True
|
179
|
+
else:
|
180
|
+
header_json_content = False
|
181
|
+
|
182
|
+
if method is None:
|
183
|
+
method = 'POST' if content is not None else 'GET'
|
184
|
+
|
185
|
+
#
|
186
|
+
|
187
|
+
req = urllib.request.Request( # noqa
|
188
|
+
url,
|
189
|
+
method=method,
|
190
|
+
headers=self.build_request_headers(
|
191
|
+
headers,
|
192
|
+
content_type=content_type,
|
193
|
+
json_content=header_json_content,
|
194
|
+
),
|
195
|
+
data=content,
|
196
|
+
)
|
197
|
+
|
198
|
+
resp, body = await self.send_url_request(req)
|
199
|
+
|
200
|
+
#
|
201
|
+
|
202
|
+
if success_status_codes is not None:
|
203
|
+
is_success = resp.status in success_status_codes
|
204
|
+
else:
|
205
|
+
is_success = (200 <= resp.status <= 300)
|
206
|
+
if not is_success:
|
207
|
+
raise self.ServiceRequestError(resp.status, body)
|
208
|
+
|
209
|
+
return self.load_json_bytes(body)
|
210
|
+
|
211
|
+
#
|
212
|
+
|
213
|
+
KEY_PART_SEPARATOR = '--'
|
214
|
+
|
215
|
+
def fix_key(self, s: str, partial_suffix: bool = False) -> str:
|
216
|
+
return self.KEY_PART_SEPARATOR.join([
|
217
|
+
*([self._key_prefix] if self._key_prefix else []),
|
218
|
+
s,
|
219
|
+
('' if partial_suffix else self._key_suffix),
|
220
|
+
])
|
221
|
+
|
222
|
+
#
|
223
|
+
|
224
|
+
@dc.dataclass(frozen=True)
|
225
|
+
class Entry(GithubCacheClient.Entry):
|
226
|
+
artifact: GithubCacheServiceV1.ArtifactCacheEntry
|
227
|
+
|
228
|
+
#
|
229
|
+
|
230
|
+
def build_get_entry_url_path(self, *keys: str) -> str:
|
231
|
+
qp = dict(
|
232
|
+
keys=','.join(urllib.parse.quote_plus(k) for k in keys),
|
233
|
+
version=str(self._cache_version),
|
234
|
+
)
|
235
|
+
|
236
|
+
return '?'.join([
|
237
|
+
'cache',
|
238
|
+
'&'.join([
|
239
|
+
f'{k}={v}'
|
240
|
+
for k, v in qp.items()
|
241
|
+
]),
|
242
|
+
])
|
243
|
+
|
244
|
+
GET_ENTRY_SUCCESS_STATUS_CODES = (200, 204)
|
245
|
+
|
246
|
+
|
247
|
+
##
|
248
|
+
|
249
|
+
|
250
|
+
class GithubCacheServiceV1Client(GithubCacheServiceV1BaseClient):
|
251
|
+
DEFAULT_CONCURRENCY = 4
|
252
|
+
|
253
|
+
DEFAULT_CHUNK_SIZE = 32 * 1024 * 1024
|
254
|
+
|
255
|
+
def __init__(
|
256
|
+
self,
|
257
|
+
*,
|
258
|
+
concurrency: int = DEFAULT_CONCURRENCY,
|
259
|
+
chunk_size: int = DEFAULT_CHUNK_SIZE,
|
260
|
+
**kwargs: ta.Any,
|
261
|
+
) -> None:
|
262
|
+
super().__init__(**kwargs)
|
263
|
+
|
264
|
+
check.arg(concurrency > 0)
|
265
|
+
self._concurrency = concurrency
|
266
|
+
|
267
|
+
check.arg(chunk_size > 0)
|
268
|
+
self._chunk_size = chunk_size
|
269
|
+
|
270
|
+
#
|
271
|
+
|
272
|
+
async def get_entry(self, key: str) -> ta.Optional[GithubCacheServiceV1BaseClient.Entry]:
|
273
|
+
obj = await self.send_service_request(
|
274
|
+
self.build_get_entry_url_path(self.fix_key(key, partial_suffix=True)),
|
275
|
+
)
|
276
|
+
if obj is None:
|
277
|
+
return None
|
278
|
+
|
279
|
+
return self.Entry(GithubCacheServiceV1.dataclass_from_json(
|
280
|
+
GithubCacheServiceV1.ArtifactCacheEntry,
|
281
|
+
obj,
|
282
|
+
))
|
283
|
+
|
284
|
+
#
|
285
|
+
|
286
|
+
@dc.dataclass(frozen=True)
|
287
|
+
class _DownloadChunk:
|
288
|
+
key: str
|
289
|
+
url: str
|
290
|
+
out_file: str
|
291
|
+
offset: int
|
292
|
+
size: int
|
293
|
+
|
294
|
+
async def _download_file_chunk_urllib(self, chunk: _DownloadChunk) -> None:
|
295
|
+
req = urllib.request.Request( # noqa
|
296
|
+
chunk.url,
|
297
|
+
headers={
|
298
|
+
'Range': f'bytes={chunk.offset}-{chunk.offset + chunk.size - 1}',
|
299
|
+
},
|
300
|
+
)
|
301
|
+
|
302
|
+
_, buf_ = await self.send_url_request(req)
|
303
|
+
|
304
|
+
buf = check.not_none(buf_)
|
305
|
+
check.equal(len(buf), chunk.size)
|
306
|
+
|
307
|
+
#
|
308
|
+
|
309
|
+
def write_sync():
|
310
|
+
with open(chunk.out_file, 'r+b') as f: # noqa
|
311
|
+
f.seek(chunk.offset, os.SEEK_SET)
|
312
|
+
f.write(buf)
|
313
|
+
|
314
|
+
await self._get_loop().run_in_executor(None, write_sync) # noqa
|
315
|
+
|
316
|
+
# async def _download_file_chunk_curl(self, chunk: _DownloadChunk) -> None:
|
317
|
+
# async with contextlib.AsyncExitStack() as es:
|
318
|
+
# f = open(chunk.out_file, 'r+b')
|
319
|
+
# f.seek(chunk.offset, os.SEEK_SET)
|
320
|
+
#
|
321
|
+
# tmp_file = es.enter_context(temp_file_context()) # noqa
|
322
|
+
#
|
323
|
+
# proc = await es.enter_async_context(asyncio_subprocesses.popen(
|
324
|
+
# 'curl',
|
325
|
+
# '-s',
|
326
|
+
# '-w', '%{json}',
|
327
|
+
# '-H', f'Range: bytes={chunk.offset}-{chunk.offset + chunk.size - 1}',
|
328
|
+
# chunk.url,
|
329
|
+
# output=subprocess.PIPE,
|
330
|
+
# ))
|
331
|
+
#
|
332
|
+
# futs = asyncio.gather(
|
333
|
+
#
|
334
|
+
# )
|
335
|
+
#
|
336
|
+
# await proc.wait()
|
337
|
+
#
|
338
|
+
# with open(tmp_file, 'r') as f: # noqa
|
339
|
+
# curl_json = tmp_file.read()
|
340
|
+
#
|
341
|
+
# curl_res = json.loads(curl_json.decode().strip())
|
342
|
+
#
|
343
|
+
# status_code = check.isinstance(curl_res['response_code'], int)
|
344
|
+
#
|
345
|
+
# if not (200 <= status_code <= 300):
|
346
|
+
# raise RuntimeError(f'Curl chunk download {chunk} failed: {curl_res}')
|
347
|
+
|
348
|
+
async def _download_file_chunk(self, chunk: _DownloadChunk) -> None:
|
349
|
+
with log_timing_context(
|
350
|
+
'Downloading github cache '
|
351
|
+
f'key {chunk.key} '
|
352
|
+
f'file {chunk.out_file} '
|
353
|
+
f'chunk {chunk.offset} - {chunk.offset + chunk.size}',
|
354
|
+
):
|
355
|
+
await self._download_file_chunk_urllib(chunk)
|
356
|
+
|
357
|
+
async def _download_file(self, entry: GithubCacheServiceV1BaseClient.Entry, out_file: str) -> None:
|
358
|
+
key = check.non_empty_str(entry.artifact.cache_key)
|
359
|
+
url = check.non_empty_str(entry.artifact.archive_location)
|
360
|
+
|
361
|
+
head_resp, _ = await self.send_url_request(urllib.request.Request( # noqa
|
362
|
+
url,
|
363
|
+
method='HEAD',
|
364
|
+
))
|
365
|
+
file_size = int(head_resp.headers['Content-Length'])
|
366
|
+
|
367
|
+
#
|
368
|
+
|
369
|
+
with open(out_file, 'xb') as f: # noqa
|
370
|
+
f.truncate(file_size)
|
371
|
+
|
372
|
+
#
|
373
|
+
|
374
|
+
download_tasks = []
|
375
|
+
chunk_size = self._chunk_size
|
376
|
+
for i in range((file_size // chunk_size) + (1 if file_size % chunk_size else 0)):
|
377
|
+
offset = i * chunk_size
|
378
|
+
size = min(chunk_size, file_size - offset)
|
379
|
+
chunk = self._DownloadChunk(
|
380
|
+
key,
|
381
|
+
url,
|
382
|
+
out_file,
|
383
|
+
offset,
|
384
|
+
size,
|
385
|
+
)
|
386
|
+
download_tasks.append(self._download_file_chunk(chunk))
|
387
|
+
|
388
|
+
await asyncio_wait_concurrent(download_tasks, self._concurrency)
|
389
|
+
|
390
|
+
async def download_file(self, entry: GithubCacheClient.Entry, out_file: str) -> None:
|
391
|
+
entry1 = check.isinstance(entry, self.Entry)
|
392
|
+
with log_timing_context(
|
393
|
+
'Downloading github cache '
|
394
|
+
f'key {entry1.artifact.cache_key} '
|
395
|
+
f'version {entry1.artifact.cache_version} '
|
396
|
+
f'to {out_file}',
|
397
|
+
):
|
398
|
+
await self._download_file(entry1, out_file)
|
399
|
+
|
400
|
+
#
|
401
|
+
|
402
|
+
async def _upload_file_chunk(
|
403
|
+
self,
|
404
|
+
key: str,
|
405
|
+
cache_id: int,
|
406
|
+
in_file: str,
|
407
|
+
offset: int,
|
408
|
+
size: int,
|
409
|
+
) -> None:
|
410
|
+
with log_timing_context(
|
411
|
+
f'Uploading github cache {key} '
|
412
|
+
f'file {in_file} '
|
413
|
+
f'chunk {offset} - {offset + size}',
|
414
|
+
):
|
415
|
+
with open(in_file, 'rb') as f: # noqa
|
416
|
+
f.seek(offset)
|
417
|
+
buf = f.read(size)
|
418
|
+
|
419
|
+
check.equal(len(buf), size)
|
420
|
+
|
421
|
+
await self.send_service_request(
|
422
|
+
f'caches/{cache_id}',
|
423
|
+
method='PATCH',
|
424
|
+
content_type='application/octet-stream',
|
425
|
+
headers={
|
426
|
+
'Content-Range': f'bytes {offset}-{offset + size - 1}/*',
|
427
|
+
},
|
428
|
+
content=buf,
|
429
|
+
success_status_codes=[204],
|
430
|
+
)
|
431
|
+
|
432
|
+
async def _upload_file(self, key: str, in_file: str) -> None:
|
433
|
+
fixed_key = self.fix_key(key)
|
434
|
+
|
435
|
+
check.state(os.path.isfile(in_file))
|
436
|
+
|
437
|
+
file_size = os.stat(in_file).st_size
|
438
|
+
|
439
|
+
#
|
440
|
+
|
441
|
+
reserve_req = GithubCacheServiceV1.ReserveCacheRequest(
|
442
|
+
key=fixed_key,
|
443
|
+
cache_size=file_size,
|
444
|
+
version=str(self._cache_version),
|
445
|
+
)
|
446
|
+
reserve_resp_obj = await self.send_service_request(
|
447
|
+
'caches',
|
448
|
+
json_content=GithubCacheServiceV1.dataclass_to_json(reserve_req),
|
449
|
+
success_status_codes=[201],
|
450
|
+
)
|
451
|
+
reserve_resp = GithubCacheServiceV1.dataclass_from_json( # noqa
|
452
|
+
GithubCacheServiceV1.ReserveCacheResponse,
|
453
|
+
reserve_resp_obj,
|
454
|
+
)
|
455
|
+
cache_id = check.isinstance(reserve_resp.cache_id, int)
|
456
|
+
|
457
|
+
log.debug(f'Github cache file {os.path.basename(in_file)} got id {cache_id}') # noqa
|
458
|
+
|
459
|
+
#
|
460
|
+
|
461
|
+
upload_tasks = []
|
462
|
+
chunk_size = self._chunk_size
|
463
|
+
for i in range((file_size // chunk_size) + (1 if file_size % chunk_size else 0)):
|
464
|
+
offset = i * chunk_size
|
465
|
+
size = min(chunk_size, file_size - offset)
|
466
|
+
upload_tasks.append(self._upload_file_chunk(
|
467
|
+
fixed_key,
|
468
|
+
cache_id,
|
469
|
+
in_file,
|
470
|
+
offset,
|
471
|
+
size,
|
472
|
+
))
|
473
|
+
|
474
|
+
await asyncio_wait_concurrent(upload_tasks, self._concurrency)
|
475
|
+
|
476
|
+
#
|
477
|
+
|
478
|
+
commit_req = GithubCacheServiceV1.CommitCacheRequest(
|
479
|
+
size=file_size,
|
480
|
+
)
|
481
|
+
await self.send_service_request(
|
482
|
+
f'caches/{cache_id}',
|
483
|
+
json_content=GithubCacheServiceV1.dataclass_to_json(commit_req),
|
484
|
+
success_status_codes=[204],
|
485
|
+
)
|
486
|
+
|
487
|
+
async def upload_file(self, key: str, in_file: str) -> None:
|
488
|
+
with log_timing_context(
|
489
|
+
f'Uploading github cache file {os.path.basename(in_file)} '
|
490
|
+
f'key {key}',
|
491
|
+
):
|
492
|
+
await self._upload_file(key, in_file)
|
omdev/ci/github/env.py
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import dataclasses as dc
|
3
|
+
import os
|
4
|
+
import typing as ta
|
5
|
+
|
6
|
+
|
7
|
+
@dc.dataclass(frozen=True)
|
8
|
+
class GithubEnvVar:
|
9
|
+
k: str
|
10
|
+
|
11
|
+
def __call__(self) -> ta.Optional[str]:
|
12
|
+
return os.environ.get(self.k)
|
13
|
+
|
14
|
+
|
15
|
+
GITHUB_ENV_VARS: ta.Set[GithubEnvVar] = set()
|
16
|
+
|
17
|
+
|
18
|
+
def register_github_env_var(k: str) -> GithubEnvVar:
|
19
|
+
ev = GithubEnvVar(k)
|
20
|
+
GITHUB_ENV_VARS.add(ev)
|
21
|
+
return ev
|
omdev/ci/requirements.py
CHANGED
omdev/ci/shell.py
CHANGED
omdev/ci/utils.py
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
-
# @omlish-lite
|
3
2
|
import hashlib
|
4
3
|
import logging
|
5
|
-
import os.path
|
6
|
-
import tempfile
|
7
4
|
import time
|
8
5
|
import typing as ta
|
9
6
|
|
@@ -13,15 +10,6 @@ from omlish.lite.logs import log
|
|
13
10
|
##
|
14
11
|
|
15
12
|
|
16
|
-
def make_temp_file() -> str:
|
17
|
-
file_fd, file = tempfile.mkstemp()
|
18
|
-
os.close(file_fd)
|
19
|
-
return file
|
20
|
-
|
21
|
-
|
22
|
-
##
|
23
|
-
|
24
|
-
|
25
13
|
def read_yaml_file(yaml_file: str) -> ta.Any:
|
26
14
|
yaml = __import__('yaml')
|
27
15
|
|
@@ -65,7 +53,7 @@ class LogTimingContext:
|
|
65
53
|
def __enter__(self) -> 'LogTimingContext':
|
66
54
|
self._begin_time = time.time()
|
67
55
|
|
68
|
-
self._log.log(self._level, f'Begin {self._description}') # noqa
|
56
|
+
self._log.log(self._level, f'Begin : {self._description}') # noqa
|
69
57
|
|
70
58
|
return self
|
71
59
|
|
@@ -74,7 +62,7 @@ class LogTimingContext:
|
|
74
62
|
|
75
63
|
self._log.log(
|
76
64
|
self._level,
|
77
|
-
f'End {self._description} - {self._end_time - self._begin_time:0.2f} s elapsed',
|
65
|
+
f'End : {self._description} - {self._end_time - self._begin_time:0.2f} s elapsed',
|
78
66
|
)
|
79
67
|
|
80
68
|
|
omdev/git/shallow.py
CHANGED