omdev 0.0.0.dev223__py3-none-any.whl → 0.0.0.dev225__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/amalg/typing.py +7 -1
- omdev/ci/cache.py +108 -0
- omdev/ci/ci.py +1 -1
- omdev/ci/docker/cacheserved.py +262 -0
- omdev/ci/docker/dataserver.py +204 -0
- omdev/ci/docker/imagepulling.py +2 -1
- omdev/ci/docker/packing.py +72 -0
- omdev/ci/docker/repositories.py +40 -0
- omdev/ci/github/cache.py +20 -1
- omdev/ci/github/client.py +9 -2
- omdev/ci/github/inject.py +4 -4
- omdev/ci/requirements.py +1 -1
- omdev/ci/utils.py +0 -49
- omdev/dataserver/targets.py +32 -0
- omdev/git/revisions.py +2 -2
- omdev/git/shallow.py +1 -1
- omdev/git/status.py +1 -1
- omdev/oci/data.py +19 -0
- omdev/oci/dataserver.py +4 -1
- omdev/oci/pack/__init__.py +0 -0
- omdev/oci/pack/packing.py +185 -0
- omdev/oci/pack/repositories.py +162 -0
- omdev/oci/{packing.py → pack/unpacking.py} +0 -177
- omdev/oci/repositories.py +6 -0
- omdev/precheck/lite.py +1 -1
- omdev/pyproject/pkg.py +1 -1
- omdev/scripts/ci.py +773 -552
- omdev/scripts/interp.py +230 -299
- omdev/scripts/pyproject.py +328 -256
- {omdev-0.0.0.dev223.dist-info → omdev-0.0.0.dev225.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev223.dist-info → omdev-0.0.0.dev225.dist-info}/RECORD +35 -28
- {omdev-0.0.0.dev223.dist-info → omdev-0.0.0.dev225.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev223.dist-info → omdev-0.0.0.dev225.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev223.dist-info → omdev-0.0.0.dev225.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev223.dist-info → omdev-0.0.0.dev225.dist-info}/top_level.txt +0 -0
omdev/scripts/ci.py
CHANGED
@@ -89,7 +89,7 @@ InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
|
|
89
89
|
InjectorProviderFnMap = ta.Mapping['InjectorKey', 'InjectorProviderFn']
|
90
90
|
InjectorBindingOrBindings = ta.Union['InjectorBinding', 'InjectorBindings']
|
91
91
|
|
92
|
-
# ../../omlish/subprocesses.py
|
92
|
+
# ../../omlish/subprocesses/base.py
|
93
93
|
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
|
94
94
|
|
95
95
|
|
@@ -159,6 +159,27 @@ class ShellCmd:
|
|
159
159
|
)
|
160
160
|
|
161
161
|
|
162
|
+
########################################
|
163
|
+
# ../utils.py
|
164
|
+
|
165
|
+
|
166
|
+
##
|
167
|
+
|
168
|
+
|
169
|
+
def read_yaml_file(yaml_file: str) -> ta.Any:
|
170
|
+
yaml = __import__('yaml')
|
171
|
+
|
172
|
+
with open(yaml_file) as f:
|
173
|
+
return yaml.safe_load(f)
|
174
|
+
|
175
|
+
|
176
|
+
##
|
177
|
+
|
178
|
+
|
179
|
+
def sha256_str(s: str) -> str:
|
180
|
+
return hashlib.sha256(s.encode('utf-8')).hexdigest()
|
181
|
+
|
182
|
+
|
162
183
|
########################################
|
163
184
|
# ../../../omlish/asyncs/asyncio/asyncio.py
|
164
185
|
|
@@ -1146,6 +1167,63 @@ class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
|
|
1146
1167
|
self._underlying.handleError(record)
|
1147
1168
|
|
1148
1169
|
|
1170
|
+
########################################
|
1171
|
+
# ../../../omlish/logs/timing.py
|
1172
|
+
|
1173
|
+
|
1174
|
+
##
|
1175
|
+
|
1176
|
+
|
1177
|
+
class LogTimingContext:
|
1178
|
+
DEFAULT_LOG: ta.ClassVar[ta.Optional[logging.Logger]] = None
|
1179
|
+
|
1180
|
+
class _NOT_SPECIFIED: # noqa
|
1181
|
+
def __new__(cls, *args, **kwargs): # noqa
|
1182
|
+
raise TypeError
|
1183
|
+
|
1184
|
+
def __init__(
|
1185
|
+
self,
|
1186
|
+
description: str,
|
1187
|
+
*,
|
1188
|
+
log: ta.Union[logging.Logger, ta.Type[_NOT_SPECIFIED], None] = _NOT_SPECIFIED, # noqa
|
1189
|
+
level: int = logging.DEBUG,
|
1190
|
+
) -> None:
|
1191
|
+
super().__init__()
|
1192
|
+
|
1193
|
+
self._description = description
|
1194
|
+
if log is self._NOT_SPECIFIED:
|
1195
|
+
log = self.DEFAULT_LOG # noqa
|
1196
|
+
self._log: ta.Optional[logging.Logger] = log # type: ignore
|
1197
|
+
self._level = level
|
1198
|
+
|
1199
|
+
def set_description(self, description: str) -> 'LogTimingContext':
|
1200
|
+
self._description = description
|
1201
|
+
return self
|
1202
|
+
|
1203
|
+
_begin_time: float
|
1204
|
+
_end_time: float
|
1205
|
+
|
1206
|
+
def __enter__(self) -> 'LogTimingContext':
|
1207
|
+
self._begin_time = time.time()
|
1208
|
+
|
1209
|
+
if self._log is not None:
|
1210
|
+
self._log.log(self._level, f'Begin : {self._description}') # noqa
|
1211
|
+
|
1212
|
+
return self
|
1213
|
+
|
1214
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
1215
|
+
self._end_time = time.time()
|
1216
|
+
|
1217
|
+
if self._log is not None:
|
1218
|
+
self._log.log(
|
1219
|
+
self._level,
|
1220
|
+
f'End : {self._description} - {self._end_time - self._begin_time:0.2f} s elapsed',
|
1221
|
+
)
|
1222
|
+
|
1223
|
+
|
1224
|
+
log_timing_context = LogTimingContext
|
1225
|
+
|
1226
|
+
|
1149
1227
|
########################################
|
1150
1228
|
# ../../../omlish/os/files.py
|
1151
1229
|
|
@@ -1183,159 +1261,47 @@ def unlinking_if_exists(path: str) -> ta.Iterator[None]:
|
|
1183
1261
|
|
1184
1262
|
|
1185
1263
|
########################################
|
1186
|
-
# ../
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1264
|
+
# ../docker/utils.py
|
1265
|
+
"""
|
1266
|
+
TODO:
|
1267
|
+
- some less stupid Dockerfile hash
|
1268
|
+
- doesn't change too much though
|
1269
|
+
"""
|
1190
1270
|
|
1191
1271
|
|
1192
1272
|
##
|
1193
1273
|
|
1194
1274
|
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
def __init__(
|
1199
|
-
self,
|
1200
|
-
*,
|
1201
|
-
version: ta.Optional[CacheVersion] = None,
|
1202
|
-
) -> None:
|
1203
|
-
super().__init__()
|
1204
|
-
|
1205
|
-
if version is None:
|
1206
|
-
version = self.DEFAULT_CACHE_VERSION
|
1207
|
-
check.isinstance(version, int)
|
1208
|
-
check.arg(version >= 0)
|
1209
|
-
self._version: CacheVersion = version
|
1210
|
-
|
1211
|
-
@property
|
1212
|
-
def version(self) -> CacheVersion:
|
1213
|
-
return self._version
|
1214
|
-
|
1215
|
-
#
|
1216
|
-
|
1217
|
-
@abc.abstractmethod
|
1218
|
-
def get_file(self, key: str) -> ta.Awaitable[ta.Optional[str]]:
|
1219
|
-
raise NotImplementedError
|
1220
|
-
|
1221
|
-
@abc.abstractmethod
|
1222
|
-
def put_file(
|
1223
|
-
self,
|
1224
|
-
key: str,
|
1225
|
-
file_path: str,
|
1226
|
-
*,
|
1227
|
-
steal: bool = False,
|
1228
|
-
) -> ta.Awaitable[str]:
|
1229
|
-
raise NotImplementedError
|
1230
|
-
|
1231
|
-
|
1232
|
-
#
|
1233
|
-
|
1234
|
-
|
1235
|
-
class DirectoryFileCache(FileCache):
|
1236
|
-
@dc.dataclass(frozen=True)
|
1237
|
-
class Config:
|
1238
|
-
dir: str
|
1239
|
-
|
1240
|
-
no_create: bool = False
|
1241
|
-
no_purge: bool = False
|
1242
|
-
|
1243
|
-
def __init__(
|
1244
|
-
self,
|
1245
|
-
config: Config,
|
1246
|
-
*,
|
1247
|
-
version: ta.Optional[CacheVersion] = None,
|
1248
|
-
) -> None: # noqa
|
1249
|
-
super().__init__(
|
1250
|
-
version=version,
|
1251
|
-
)
|
1252
|
-
|
1253
|
-
self._config = config
|
1254
|
-
|
1255
|
-
@property
|
1256
|
-
def dir(self) -> str:
|
1257
|
-
return self._config.dir
|
1258
|
-
|
1259
|
-
#
|
1260
|
-
|
1261
|
-
VERSION_FILE_NAME = '.ci-cache-version'
|
1262
|
-
|
1263
|
-
@cached_nullary
|
1264
|
-
def setup_dir(self) -> None:
|
1265
|
-
version_file = os.path.join(self.dir, self.VERSION_FILE_NAME)
|
1266
|
-
|
1267
|
-
if self._config.no_create:
|
1268
|
-
check.state(os.path.isdir(self.dir))
|
1269
|
-
|
1270
|
-
elif not os.path.isdir(self.dir):
|
1271
|
-
os.makedirs(self.dir)
|
1272
|
-
with open(version_file, 'w') as f:
|
1273
|
-
f.write(str(self._version))
|
1274
|
-
return
|
1275
|
-
|
1276
|
-
# NOTE: intentionally raises FileNotFoundError to refuse to use an existing non-cache dir as a cache dir.
|
1277
|
-
with open(version_file) as f:
|
1278
|
-
dir_version = int(f.read().strip())
|
1279
|
-
|
1280
|
-
if dir_version == self._version:
|
1281
|
-
return
|
1282
|
-
|
1283
|
-
if self._config.no_purge:
|
1284
|
-
raise RuntimeError(f'{dir_version=} != {self._version=}')
|
1285
|
-
|
1286
|
-
dirs = [n for n in sorted(os.listdir(self.dir)) if os.path.isdir(os.path.join(self.dir, n))]
|
1287
|
-
if dirs:
|
1288
|
-
raise RuntimeError(
|
1289
|
-
f'Refusing to remove stale cache dir {self.dir!r} '
|
1290
|
-
f'due to present directories: {", ".join(dirs)}',
|
1291
|
-
)
|
1275
|
+
def build_docker_file_hash(docker_file: str) -> str:
|
1276
|
+
with open(docker_file) as f:
|
1277
|
+
contents = f.read()
|
1292
1278
|
|
1293
|
-
|
1294
|
-
if n.startswith('.'):
|
1295
|
-
continue
|
1296
|
-
fp = os.path.join(self.dir, n)
|
1297
|
-
check.state(os.path.isfile(fp))
|
1298
|
-
log.debug('Purging stale cache file: %s', fp)
|
1299
|
-
os.unlink(fp)
|
1279
|
+
return sha256_str(contents)
|
1300
1280
|
|
1301
|
-
os.unlink(version_file)
|
1302
1281
|
|
1303
|
-
|
1304
|
-
f.write(str(self._version))
|
1282
|
+
##
|
1305
1283
|
|
1306
|
-
#
|
1307
1284
|
|
1308
|
-
|
1309
|
-
|
1310
|
-
|
1311
|
-
|
1312
|
-
self.setup_dir()
|
1313
|
-
return os.path.join(self.dir, key)
|
1285
|
+
def read_docker_tar_image_tag(tar_file: str) -> str:
|
1286
|
+
with tarfile.open(tar_file) as tf:
|
1287
|
+
with contextlib.closing(check.not_none(tf.extractfile('manifest.json'))) as mf:
|
1288
|
+
m = mf.read()
|
1314
1289
|
|
1315
|
-
|
1316
|
-
|
1290
|
+
manifests = json.loads(m.decode('utf-8'))
|
1291
|
+
manifest = check.single(manifests)
|
1292
|
+
tag = check.non_empty_str(check.single(manifest['RepoTags']))
|
1293
|
+
return tag
|
1317
1294
|
|
1318
|
-
#
|
1319
1295
|
|
1320
|
-
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
return cache_file_path
|
1296
|
+
def read_docker_tar_image_id(tar_file: str) -> str:
|
1297
|
+
with tarfile.open(tar_file) as tf:
|
1298
|
+
with contextlib.closing(check.not_none(tf.extractfile('index.json'))) as mf:
|
1299
|
+
i = mf.read()
|
1325
1300
|
|
1326
|
-
|
1327
|
-
|
1328
|
-
|
1329
|
-
|
1330
|
-
*,
|
1331
|
-
steal: bool = False,
|
1332
|
-
) -> str:
|
1333
|
-
cache_file_path = self.get_cache_file_path(key)
|
1334
|
-
if steal:
|
1335
|
-
shutil.move(file_path, cache_file_path)
|
1336
|
-
else:
|
1337
|
-
shutil.copyfile(file_path, cache_file_path)
|
1338
|
-
return cache_file_path
|
1301
|
+
index = json.loads(i.decode('utf-8'))
|
1302
|
+
manifest = check.single(index['manifests'])
|
1303
|
+
image_id = check.non_empty_str(manifest['digest'])
|
1304
|
+
return image_id
|
1339
1305
|
|
1340
1306
|
|
1341
1307
|
########################################
|
@@ -1560,99 +1526,33 @@ def is_in_github_actions() -> bool:
|
|
1560
1526
|
|
1561
1527
|
|
1562
1528
|
########################################
|
1563
|
-
#
|
1529
|
+
# ../../../omlish/argparse/cli.py
|
1530
|
+
"""
|
1531
|
+
TODO:
|
1532
|
+
- default command
|
1533
|
+
- auto match all underscores to hyphens
|
1534
|
+
- pre-run, post-run hooks
|
1535
|
+
- exitstack?
|
1536
|
+
"""
|
1564
1537
|
|
1565
1538
|
|
1566
1539
|
##
|
1567
1540
|
|
1568
1541
|
|
1569
|
-
|
1570
|
-
|
1542
|
+
@dc.dataclass(eq=False)
|
1543
|
+
class ArgparseArg:
|
1544
|
+
args: ta.Sequence[ta.Any]
|
1545
|
+
kwargs: ta.Mapping[str, ta.Any]
|
1546
|
+
dest: ta.Optional[str] = None
|
1571
1547
|
|
1572
|
-
|
1573
|
-
|
1548
|
+
def __get__(self, instance, owner=None):
|
1549
|
+
if instance is None:
|
1550
|
+
return self
|
1551
|
+
return getattr(instance.args, self.dest) # type: ignore
|
1574
1552
|
|
1575
1553
|
|
1576
|
-
|
1577
|
-
|
1578
|
-
|
1579
|
-
def sha256_str(s: str) -> str:
|
1580
|
-
return hashlib.sha256(s.encode('utf-8')).hexdigest()
|
1581
|
-
|
1582
|
-
|
1583
|
-
##
|
1584
|
-
|
1585
|
-
|
1586
|
-
class LogTimingContext:
|
1587
|
-
DEFAULT_LOG: ta.ClassVar[logging.Logger] = log
|
1588
|
-
|
1589
|
-
def __init__(
|
1590
|
-
self,
|
1591
|
-
description: str,
|
1592
|
-
*,
|
1593
|
-
log: ta.Optional[logging.Logger] = None, # noqa
|
1594
|
-
level: int = logging.DEBUG,
|
1595
|
-
) -> None:
|
1596
|
-
super().__init__()
|
1597
|
-
|
1598
|
-
self._description = description
|
1599
|
-
self._log = log if log is not None else self.DEFAULT_LOG
|
1600
|
-
self._level = level
|
1601
|
-
|
1602
|
-
def set_description(self, description: str) -> 'LogTimingContext':
|
1603
|
-
self._description = description
|
1604
|
-
return self
|
1605
|
-
|
1606
|
-
_begin_time: float
|
1607
|
-
_end_time: float
|
1608
|
-
|
1609
|
-
def __enter__(self) -> 'LogTimingContext':
|
1610
|
-
self._begin_time = time.time()
|
1611
|
-
|
1612
|
-
self._log.log(self._level, f'Begin : {self._description}') # noqa
|
1613
|
-
|
1614
|
-
return self
|
1615
|
-
|
1616
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
1617
|
-
self._end_time = time.time()
|
1618
|
-
|
1619
|
-
self._log.log(
|
1620
|
-
self._level,
|
1621
|
-
f'End : {self._description} - {self._end_time - self._begin_time:0.2f} s elapsed',
|
1622
|
-
)
|
1623
|
-
|
1624
|
-
|
1625
|
-
log_timing_context = LogTimingContext
|
1626
|
-
|
1627
|
-
|
1628
|
-
########################################
|
1629
|
-
# ../../../omlish/argparse/cli.py
|
1630
|
-
"""
|
1631
|
-
TODO:
|
1632
|
-
- default command
|
1633
|
-
- auto match all underscores to hyphens
|
1634
|
-
- pre-run, post-run hooks
|
1635
|
-
- exitstack?
|
1636
|
-
"""
|
1637
|
-
|
1638
|
-
|
1639
|
-
##
|
1640
|
-
|
1641
|
-
|
1642
|
-
@dc.dataclass(eq=False)
|
1643
|
-
class ArgparseArg:
|
1644
|
-
args: ta.Sequence[ta.Any]
|
1645
|
-
kwargs: ta.Mapping[str, ta.Any]
|
1646
|
-
dest: ta.Optional[str] = None
|
1647
|
-
|
1648
|
-
def __get__(self, instance, owner=None):
|
1649
|
-
if instance is None:
|
1650
|
-
return self
|
1651
|
-
return getattr(instance.args, self.dest) # type: ignore
|
1652
|
-
|
1653
|
-
|
1654
|
-
def argparse_arg(*args, **kwargs) -> ArgparseArg:
|
1655
|
-
return ArgparseArg(args, kwargs)
|
1554
|
+
def argparse_arg(*args, **kwargs) -> ArgparseArg:
|
1555
|
+
return ArgparseArg(args, kwargs)
|
1656
1556
|
|
1657
1557
|
|
1658
1558
|
#
|
@@ -3115,6 +3015,15 @@ def check_lite_runtime_version() -> None:
|
|
3115
3015
|
raise OSError(f'Requires python {LITE_REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
|
3116
3016
|
|
3117
3017
|
|
3018
|
+
########################################
|
3019
|
+
# ../../../omlish/lite/timing.py
|
3020
|
+
|
3021
|
+
|
3022
|
+
LogTimingContext.DEFAULT_LOG = log
|
3023
|
+
|
3024
|
+
log_timing_context = log_timing_context # noqa
|
3025
|
+
|
3026
|
+
|
3118
3027
|
########################################
|
3119
3028
|
# ../../../omlish/logs/json.py
|
3120
3029
|
"""
|
@@ -3215,155 +3124,466 @@ def temp_named_file_context(
|
|
3215
3124
|
|
3216
3125
|
|
3217
3126
|
########################################
|
3218
|
-
#
|
3219
|
-
"""
|
3220
|
-
TODO:
|
3221
|
-
- some less stupid Dockerfile hash
|
3222
|
-
- doesn't change too much though
|
3223
|
-
"""
|
3127
|
+
# ../../../omlish/subprocesses/run.py
|
3224
3128
|
|
3225
3129
|
|
3226
3130
|
##
|
3227
3131
|
|
3228
3132
|
|
3229
|
-
|
3230
|
-
|
3231
|
-
|
3133
|
+
@dc.dataclass(frozen=True)
|
3134
|
+
class SubprocessRunOutput(ta.Generic[T]):
|
3135
|
+
proc: T
|
3232
3136
|
|
3233
|
-
|
3137
|
+
returncode: int # noqa
|
3138
|
+
|
3139
|
+
stdout: ta.Optional[bytes] = None
|
3140
|
+
stderr: ta.Optional[bytes] = None
|
3234
3141
|
|
3235
3142
|
|
3236
3143
|
##
|
3237
3144
|
|
3238
3145
|
|
3239
|
-
|
3240
|
-
|
3241
|
-
|
3242
|
-
|
3146
|
+
@dc.dataclass(frozen=True)
|
3147
|
+
class SubprocessRun:
|
3148
|
+
cmd: ta.Sequence[str]
|
3149
|
+
input: ta.Any = None
|
3150
|
+
timeout: ta.Optional[float] = None
|
3151
|
+
check: bool = False
|
3152
|
+
capture_output: ta.Optional[bool] = None
|
3153
|
+
kwargs: ta.Optional[ta.Mapping[str, ta.Any]] = None
|
3243
3154
|
|
3244
|
-
|
3245
|
-
|
3246
|
-
|
3247
|
-
|
3155
|
+
@classmethod
|
3156
|
+
def of(
|
3157
|
+
cls,
|
3158
|
+
*cmd: str,
|
3159
|
+
input: ta.Any = None, # noqa
|
3160
|
+
timeout: ta.Optional[float] = None,
|
3161
|
+
check: bool = False, # noqa
|
3162
|
+
capture_output: ta.Optional[bool] = None,
|
3163
|
+
**kwargs: ta.Any,
|
3164
|
+
) -> 'SubprocessRun':
|
3165
|
+
return cls(
|
3166
|
+
cmd=cmd,
|
3167
|
+
input=input,
|
3168
|
+
timeout=timeout,
|
3169
|
+
check=check,
|
3170
|
+
capture_output=capture_output,
|
3171
|
+
kwargs=kwargs,
|
3172
|
+
)
|
3248
3173
|
|
3174
|
+
#
|
3249
3175
|
|
3250
|
-
|
3251
|
-
with tarfile.open(tar_file) as tf:
|
3252
|
-
with contextlib.closing(check.not_none(tf.extractfile('index.json'))) as mf:
|
3253
|
-
i = mf.read()
|
3176
|
+
_DEFAULT_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractSubprocesses
|
3254
3177
|
|
3255
|
-
|
3256
|
-
|
3257
|
-
|
3258
|
-
|
3178
|
+
def run(
|
3179
|
+
self,
|
3180
|
+
subprocesses: ta.Optional[ta.Any] = None, # AbstractSubprocesses
|
3181
|
+
) -> SubprocessRunOutput:
|
3182
|
+
if subprocesses is None:
|
3183
|
+
subprocesses = self._DEFAULT_SUBPROCESSES
|
3184
|
+
return check.not_none(subprocesses).run_(self) # type: ignore[attr-defined]
|
3259
3185
|
|
3186
|
+
_DEFAULT_ASYNC_SUBPROCESSES: ta.ClassVar[ta.Optional[ta.Any]] = None # AbstractAsyncSubprocesses
|
3260
3187
|
|
3261
|
-
|
3262
|
-
|
3188
|
+
async def async_run(
|
3189
|
+
self,
|
3190
|
+
async_subprocesses: ta.Optional[ta.Any] = None, # AbstractAsyncSubprocesses
|
3191
|
+
) -> SubprocessRunOutput:
|
3192
|
+
if async_subprocesses is None:
|
3193
|
+
async_subprocesses = self._DEFAULT_ASYNC_SUBPROCESSES
|
3194
|
+
return await check.not_none(async_subprocesses).run_(self) # type: ignore[attr-defined]
|
3263
3195
|
|
3264
3196
|
|
3265
3197
|
##
|
3266
3198
|
|
3267
3199
|
|
3268
|
-
class
|
3269
|
-
class Entry(abc.ABC): # noqa
|
3270
|
-
pass
|
3271
|
-
|
3200
|
+
class SubprocessRunnable(abc.ABC, ta.Generic[T]):
|
3272
3201
|
@abc.abstractmethod
|
3273
|
-
def
|
3202
|
+
def make_run(self) -> SubprocessRun:
|
3274
3203
|
raise NotImplementedError
|
3275
3204
|
|
3276
3205
|
@abc.abstractmethod
|
3277
|
-
def
|
3206
|
+
def handle_run_output(self, output: SubprocessRunOutput) -> T:
|
3278
3207
|
raise NotImplementedError
|
3279
3208
|
|
3280
|
-
|
3281
|
-
def upload_file(self, key: str, in_file: str) -> ta.Awaitable[None]:
|
3282
|
-
raise NotImplementedError
|
3209
|
+
#
|
3283
3210
|
|
3211
|
+
def run(self, subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractSubprocesses
|
3212
|
+
return self.handle_run_output(self.make_run().run(subprocesses))
|
3284
3213
|
|
3285
|
-
|
3214
|
+
async def async_run(self, async_subprocesses: ta.Optional[ta.Any] = None) -> T: # AbstractAsyncSubprocesses
|
3215
|
+
return self.handle_run_output(await self.make_run().async_run(async_subprocesses))
|
3286
3216
|
|
3287
3217
|
|
3288
|
-
|
3289
|
-
|
3290
|
-
AUTH_TOKEN_ENV_VAR = register_github_env_var('ACTIONS_RUNTIME_TOKEN') # noqa
|
3218
|
+
########################################
|
3219
|
+
# ../cache.py
|
3291
3220
|
|
3292
|
-
KEY_SUFFIX_ENV_VAR = register_github_env_var('GITHUB_RUN_ID')
|
3293
3221
|
|
3294
|
-
|
3222
|
+
CacheVersion = ta.NewType('CacheVersion', int)
|
3295
3223
|
|
3296
|
-
def __init__(
|
3297
|
-
self,
|
3298
|
-
*,
|
3299
|
-
base_url: ta.Optional[str] = None,
|
3300
|
-
auth_token: ta.Optional[str] = None,
|
3301
3224
|
|
3302
|
-
|
3303
|
-
key_suffix: ta.Optional[str] = None,
|
3225
|
+
##
|
3304
3226
|
|
3305
|
-
cache_version: int = CI_CACHE_VERSION,
|
3306
3227
|
|
3307
|
-
|
3228
|
+
class FileCache(abc.ABC):
|
3229
|
+
DEFAULT_CACHE_VERSION: ta.ClassVar[CacheVersion] = CacheVersion(CI_CACHE_VERSION)
|
3230
|
+
|
3231
|
+
def __init__(
|
3232
|
+
self,
|
3233
|
+
*,
|
3234
|
+
version: ta.Optional[CacheVersion] = None,
|
3308
3235
|
) -> None:
|
3309
3236
|
super().__init__()
|
3310
3237
|
|
3311
|
-
|
3312
|
-
|
3313
|
-
|
3314
|
-
|
3315
|
-
self.
|
3316
|
-
|
3317
|
-
if auth_token is None:
|
3318
|
-
auth_token = self.AUTH_TOKEN_ENV_VAR()
|
3319
|
-
self._auth_token = auth_token
|
3320
|
-
|
3321
|
-
#
|
3238
|
+
if version is None:
|
3239
|
+
version = self.DEFAULT_CACHE_VERSION
|
3240
|
+
check.isinstance(version, int)
|
3241
|
+
check.arg(version >= 0)
|
3242
|
+
self._version: CacheVersion = version
|
3322
3243
|
|
3323
|
-
|
3244
|
+
@property
|
3245
|
+
def version(self) -> CacheVersion:
|
3246
|
+
return self._version
|
3324
3247
|
|
3325
|
-
|
3326
|
-
key_suffix = self.KEY_SUFFIX_ENV_VAR()
|
3327
|
-
self._key_suffix = check.non_empty_str(key_suffix)
|
3248
|
+
#
|
3328
3249
|
|
3329
|
-
|
3250
|
+
@abc.abstractmethod
|
3251
|
+
def get_file(self, key: str) -> ta.Awaitable[ta.Optional[str]]:
|
3252
|
+
raise NotImplementedError
|
3330
3253
|
|
3331
|
-
|
3254
|
+
@abc.abstractmethod
|
3255
|
+
def put_file(
|
3256
|
+
self,
|
3257
|
+
key: str,
|
3258
|
+
file_path: str,
|
3259
|
+
*,
|
3260
|
+
steal: bool = False,
|
3261
|
+
) -> ta.Awaitable[str]:
|
3262
|
+
raise NotImplementedError
|
3332
3263
|
|
3333
|
-
#
|
3334
3264
|
|
3335
|
-
|
3265
|
+
#
|
3336
3266
|
|
3337
|
-
#
|
3338
3267
|
|
3339
|
-
|
3340
|
-
|
3341
|
-
|
3342
|
-
|
3268
|
+
class DirectoryFileCache(FileCache):
|
3269
|
+
@dc.dataclass(frozen=True)
|
3270
|
+
class Config:
|
3271
|
+
dir: str
|
3343
3272
|
|
3344
|
-
|
3273
|
+
no_create: bool = False
|
3274
|
+
no_purge: bool = False
|
3345
3275
|
|
3346
|
-
def
|
3276
|
+
def __init__(
|
3347
3277
|
self,
|
3348
|
-
|
3278
|
+
config: Config,
|
3349
3279
|
*,
|
3350
|
-
|
3351
|
-
|
3352
|
-
|
3353
|
-
|
3354
|
-
|
3355
|
-
'application/json',
|
3356
|
-
f'api-version={GithubCacheServiceV1.API_VERSION}',
|
3357
|
-
]),
|
3358
|
-
}
|
3280
|
+
version: ta.Optional[CacheVersion] = None,
|
3281
|
+
) -> None: # noqa
|
3282
|
+
super().__init__(
|
3283
|
+
version=version,
|
3284
|
+
)
|
3359
3285
|
|
3360
|
-
|
3361
|
-
dct['Authorization'] = f'Bearer {auth_token}'
|
3286
|
+
self._config = config
|
3362
3287
|
|
3363
|
-
|
3364
|
-
|
3365
|
-
|
3366
|
-
|
3288
|
+
@property
|
3289
|
+
def dir(self) -> str:
|
3290
|
+
return self._config.dir
|
3291
|
+
|
3292
|
+
#
|
3293
|
+
|
3294
|
+
VERSION_FILE_NAME = '.ci-cache-version'
|
3295
|
+
|
3296
|
+
@cached_nullary
|
3297
|
+
def setup_dir(self) -> None:
|
3298
|
+
version_file = os.path.join(self.dir, self.VERSION_FILE_NAME)
|
3299
|
+
|
3300
|
+
if self._config.no_create:
|
3301
|
+
check.state(os.path.isdir(self.dir))
|
3302
|
+
|
3303
|
+
elif not os.path.isdir(self.dir):
|
3304
|
+
os.makedirs(self.dir)
|
3305
|
+
with open(version_file, 'w') as f:
|
3306
|
+
f.write(str(self._version))
|
3307
|
+
return
|
3308
|
+
|
3309
|
+
# NOTE: intentionally raises FileNotFoundError to refuse to use an existing non-cache dir as a cache dir.
|
3310
|
+
with open(version_file) as f:
|
3311
|
+
dir_version = int(f.read().strip())
|
3312
|
+
|
3313
|
+
if dir_version == self._version:
|
3314
|
+
return
|
3315
|
+
|
3316
|
+
if self._config.no_purge:
|
3317
|
+
raise RuntimeError(f'{dir_version=} != {self._version=}')
|
3318
|
+
|
3319
|
+
dirs = [n for n in sorted(os.listdir(self.dir)) if os.path.isdir(os.path.join(self.dir, n))]
|
3320
|
+
if dirs:
|
3321
|
+
raise RuntimeError(
|
3322
|
+
f'Refusing to remove stale cache dir {self.dir!r} '
|
3323
|
+
f'due to present directories: {", ".join(dirs)}',
|
3324
|
+
)
|
3325
|
+
|
3326
|
+
for n in sorted(os.listdir(self.dir)):
|
3327
|
+
if n.startswith('.'):
|
3328
|
+
continue
|
3329
|
+
fp = os.path.join(self.dir, n)
|
3330
|
+
check.state(os.path.isfile(fp))
|
3331
|
+
log.debug('Purging stale cache file: %s', fp)
|
3332
|
+
os.unlink(fp)
|
3333
|
+
|
3334
|
+
os.unlink(version_file)
|
3335
|
+
|
3336
|
+
with open(version_file, 'w') as f:
|
3337
|
+
f.write(str(self._version))
|
3338
|
+
|
3339
|
+
#
|
3340
|
+
|
3341
|
+
def get_cache_file_path(
|
3342
|
+
self,
|
3343
|
+
key: str,
|
3344
|
+
) -> str:
|
3345
|
+
self.setup_dir()
|
3346
|
+
return os.path.join(self.dir, key)
|
3347
|
+
|
3348
|
+
def format_incomplete_file(self, f: str) -> str:
|
3349
|
+
return os.path.join(os.path.dirname(f), f'_{os.path.basename(f)}.incomplete')
|
3350
|
+
|
3351
|
+
#
|
3352
|
+
|
3353
|
+
async def get_file(self, key: str) -> ta.Optional[str]:
|
3354
|
+
cache_file_path = self.get_cache_file_path(key)
|
3355
|
+
if not os.path.exists(cache_file_path):
|
3356
|
+
return None
|
3357
|
+
return cache_file_path
|
3358
|
+
|
3359
|
+
async def put_file(
|
3360
|
+
self,
|
3361
|
+
key: str,
|
3362
|
+
file_path: str,
|
3363
|
+
*,
|
3364
|
+
steal: bool = False,
|
3365
|
+
) -> str:
|
3366
|
+
cache_file_path = self.get_cache_file_path(key)
|
3367
|
+
if steal:
|
3368
|
+
shutil.move(file_path, cache_file_path)
|
3369
|
+
else:
|
3370
|
+
shutil.copyfile(file_path, cache_file_path)
|
3371
|
+
return cache_file_path
|
3372
|
+
|
3373
|
+
|
3374
|
+
##
|
3375
|
+
|
3376
|
+
|
3377
|
+
class DataCache:
|
3378
|
+
@dc.dataclass(frozen=True)
|
3379
|
+
class Data(abc.ABC): # noqa
|
3380
|
+
pass
|
3381
|
+
|
3382
|
+
@dc.dataclass(frozen=True)
|
3383
|
+
class BytesData(Data):
|
3384
|
+
data: bytes
|
3385
|
+
|
3386
|
+
@dc.dataclass(frozen=True)
|
3387
|
+
class FileData(Data):
|
3388
|
+
file_path: str
|
3389
|
+
|
3390
|
+
@dc.dataclass(frozen=True)
|
3391
|
+
class UrlData(Data):
|
3392
|
+
url: str
|
3393
|
+
|
3394
|
+
#
|
3395
|
+
|
3396
|
+
@abc.abstractmethod
|
3397
|
+
def get_data(self, key: str) -> ta.Awaitable[ta.Optional[Data]]:
|
3398
|
+
raise NotImplementedError
|
3399
|
+
|
3400
|
+
@abc.abstractmethod
|
3401
|
+
def put_data(self, key: str, data: Data) -> ta.Awaitable[None]:
|
3402
|
+
raise NotImplementedError
|
3403
|
+
|
3404
|
+
|
3405
|
+
#
|
3406
|
+
|
3407
|
+
|
3408
|
+
@functools.singledispatch
|
3409
|
+
async def read_data_cache_data(data: DataCache.Data) -> bytes:
|
3410
|
+
raise TypeError(data)
|
3411
|
+
|
3412
|
+
|
3413
|
+
@read_data_cache_data.register
|
3414
|
+
async def _(data: DataCache.BytesData) -> bytes:
|
3415
|
+
return data.data
|
3416
|
+
|
3417
|
+
|
3418
|
+
@read_data_cache_data.register
|
3419
|
+
async def _(data: DataCache.FileData) -> bytes:
|
3420
|
+
with open(data.file_path, 'rb') as f: # noqa
|
3421
|
+
return f.read()
|
3422
|
+
|
3423
|
+
|
3424
|
+
@read_data_cache_data.register
|
3425
|
+
async def _(data: DataCache.UrlData) -> bytes:
|
3426
|
+
def inner() -> bytes:
|
3427
|
+
with urllib.request.urlopen(urllib.request.Request( # noqa
|
3428
|
+
data.url,
|
3429
|
+
)) as resp:
|
3430
|
+
return resp.read()
|
3431
|
+
|
3432
|
+
return await asyncio.get_running_loop().run_in_executor(None, inner)
|
3433
|
+
|
3434
|
+
|
3435
|
+
#
|
3436
|
+
|
3437
|
+
|
3438
|
+
class FileCacheDataCache(DataCache):
|
3439
|
+
def __init__(
|
3440
|
+
self,
|
3441
|
+
file_cache: FileCache,
|
3442
|
+
) -> None:
|
3443
|
+
super().__init__()
|
3444
|
+
|
3445
|
+
self._file_cache = file_cache
|
3446
|
+
|
3447
|
+
async def get_data(self, key: str) -> ta.Optional[DataCache.Data]:
|
3448
|
+
if (file_path := await self._file_cache.get_file(key)) is None:
|
3449
|
+
return None
|
3450
|
+
|
3451
|
+
return DataCache.FileData(file_path)
|
3452
|
+
|
3453
|
+
async def put_data(self, key: str, data: DataCache.Data) -> None:
|
3454
|
+
steal = False
|
3455
|
+
|
3456
|
+
if isinstance(data, DataCache.BytesData):
|
3457
|
+
file_path = make_temp_file()
|
3458
|
+
with open(file_path, 'wb') as f: # noqa
|
3459
|
+
f.write(data.data)
|
3460
|
+
steal = True
|
3461
|
+
|
3462
|
+
elif isinstance(data, DataCache.FileData):
|
3463
|
+
file_path = data.file_path
|
3464
|
+
|
3465
|
+
elif isinstance(data, DataCache.UrlData):
|
3466
|
+
raise NotImplementedError
|
3467
|
+
|
3468
|
+
else:
|
3469
|
+
raise TypeError(data)
|
3470
|
+
|
3471
|
+
await self._file_cache.put_file(
|
3472
|
+
key,
|
3473
|
+
file_path,
|
3474
|
+
steal=steal,
|
3475
|
+
)
|
3476
|
+
|
3477
|
+
|
3478
|
+
########################################
|
3479
|
+
# ../github/client.py
|
3480
|
+
|
3481
|
+
|
3482
|
+
##
|
3483
|
+
|
3484
|
+
|
3485
|
+
class GithubCacheClient(abc.ABC):
|
3486
|
+
class Entry(abc.ABC): # noqa
|
3487
|
+
pass
|
3488
|
+
|
3489
|
+
@abc.abstractmethod
|
3490
|
+
def get_entry(self, key: str) -> ta.Awaitable[ta.Optional[Entry]]:
|
3491
|
+
raise NotImplementedError
|
3492
|
+
|
3493
|
+
def get_entry_url(self, entry: Entry) -> ta.Optional[str]:
|
3494
|
+
return None
|
3495
|
+
|
3496
|
+
@abc.abstractmethod
|
3497
|
+
def download_file(self, entry: Entry, out_file: str) -> ta.Awaitable[None]:
|
3498
|
+
raise NotImplementedError
|
3499
|
+
|
3500
|
+
@abc.abstractmethod
|
3501
|
+
def upload_file(self, key: str, in_file: str) -> ta.Awaitable[None]:
|
3502
|
+
raise NotImplementedError
|
3503
|
+
|
3504
|
+
|
3505
|
+
##
|
3506
|
+
|
3507
|
+
|
3508
|
+
class GithubCacheServiceV1BaseClient(GithubCacheClient, abc.ABC):
|
3509
|
+
BASE_URL_ENV_VAR = register_github_env_var('ACTIONS_CACHE_URL')
|
3510
|
+
AUTH_TOKEN_ENV_VAR = register_github_env_var('ACTIONS_RUNTIME_TOKEN') # noqa
|
3511
|
+
|
3512
|
+
KEY_SUFFIX_ENV_VAR = register_github_env_var('GITHUB_RUN_ID')
|
3513
|
+
|
3514
|
+
#
|
3515
|
+
|
3516
|
+
def __init__(
|
3517
|
+
self,
|
3518
|
+
*,
|
3519
|
+
base_url: ta.Optional[str] = None,
|
3520
|
+
auth_token: ta.Optional[str] = None,
|
3521
|
+
|
3522
|
+
key_prefix: ta.Optional[str] = None,
|
3523
|
+
key_suffix: ta.Optional[str] = None,
|
3524
|
+
|
3525
|
+
cache_version: int = CI_CACHE_VERSION,
|
3526
|
+
|
3527
|
+
loop: ta.Optional[asyncio.AbstractEventLoop] = None,
|
3528
|
+
) -> None:
|
3529
|
+
super().__init__()
|
3530
|
+
|
3531
|
+
#
|
3532
|
+
|
3533
|
+
if base_url is None:
|
3534
|
+
base_url = check.non_empty_str(self.BASE_URL_ENV_VAR())
|
3535
|
+
self._service_url = GithubCacheServiceV1.get_service_url(base_url)
|
3536
|
+
|
3537
|
+
if auth_token is None:
|
3538
|
+
auth_token = self.AUTH_TOKEN_ENV_VAR()
|
3539
|
+
self._auth_token = auth_token
|
3540
|
+
|
3541
|
+
#
|
3542
|
+
|
3543
|
+
self._key_prefix = key_prefix
|
3544
|
+
|
3545
|
+
if key_suffix is None:
|
3546
|
+
key_suffix = self.KEY_SUFFIX_ENV_VAR()
|
3547
|
+
self._key_suffix = check.non_empty_str(key_suffix)
|
3548
|
+
|
3549
|
+
#
|
3550
|
+
|
3551
|
+
self._cache_version = check.isinstance(cache_version, int)
|
3552
|
+
|
3553
|
+
#
|
3554
|
+
|
3555
|
+
self._given_loop = loop
|
3556
|
+
|
3557
|
+
#
|
3558
|
+
|
3559
|
+
def _get_loop(self) -> asyncio.AbstractEventLoop:
|
3560
|
+
if (loop := self._given_loop) is not None:
|
3561
|
+
return loop
|
3562
|
+
return asyncio.get_running_loop()
|
3563
|
+
|
3564
|
+
#
|
3565
|
+
|
3566
|
+
def build_request_headers(
|
3567
|
+
self,
|
3568
|
+
headers: ta.Optional[ta.Mapping[str, str]] = None,
|
3569
|
+
*,
|
3570
|
+
content_type: ta.Optional[str] = None,
|
3571
|
+
json_content: bool = False,
|
3572
|
+
) -> ta.Dict[str, str]:
|
3573
|
+
dct = {
|
3574
|
+
'Accept': ';'.join([
|
3575
|
+
'application/json',
|
3576
|
+
f'api-version={GithubCacheServiceV1.API_VERSION}',
|
3577
|
+
]),
|
3578
|
+
}
|
3579
|
+
|
3580
|
+
if (auth_token := self._auth_token):
|
3581
|
+
dct['Authorization'] = f'Bearer {auth_token}'
|
3582
|
+
|
3583
|
+
if content_type is None and json_content:
|
3584
|
+
content_type = 'application/json'
|
3585
|
+
if content_type is not None:
|
3586
|
+
dct['Content-Type'] = content_type
|
3367
3587
|
|
3368
3588
|
if headers:
|
3369
3589
|
dct.update(headers)
|
@@ -3467,6 +3687,10 @@ class GithubCacheServiceV1BaseClient(GithubCacheClient, abc.ABC):
|
|
3467
3687
|
class Entry(GithubCacheClient.Entry):
|
3468
3688
|
artifact: GithubCacheServiceV1.ArtifactCacheEntry
|
3469
3689
|
|
3690
|
+
def get_entry_url(self, entry: GithubCacheClient.Entry) -> ta.Optional[str]:
|
3691
|
+
entry1 = check.isinstance(entry, self.Entry)
|
3692
|
+
return entry1.artifact.cache_key
|
3693
|
+
|
3470
3694
|
#
|
3471
3695
|
|
3472
3696
|
def build_get_entry_url_path(self, *keys: str) -> str:
|
@@ -3857,23 +4081,7 @@ def configure_standard_logging(
|
|
3857
4081
|
|
3858
4082
|
|
3859
4083
|
########################################
|
3860
|
-
# ../../../omlish/subprocesses.py
|
3861
|
-
|
3862
|
-
|
3863
|
-
##
|
3864
|
-
|
3865
|
-
|
3866
|
-
# Valid channel type kwarg values:
|
3867
|
-
# - A special flag negative int
|
3868
|
-
# - A positive fd int
|
3869
|
-
# - A file-like object
|
3870
|
-
# - None
|
3871
|
-
|
3872
|
-
SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
|
3873
|
-
'pipe': subprocess.PIPE,
|
3874
|
-
'stdout': subprocess.STDOUT,
|
3875
|
-
'devnull': subprocess.DEVNULL,
|
3876
|
-
}
|
4084
|
+
# ../../../omlish/subprocesses/wrap.py
|
3877
4085
|
|
3878
4086
|
|
3879
4087
|
##
|
@@ -3893,22 +4101,143 @@ def subprocess_maybe_shell_wrap_exec(*cmd: str) -> ta.Tuple[str, ...]:
|
|
3893
4101
|
return cmd
|
3894
4102
|
|
3895
4103
|
|
4104
|
+
########################################
|
4105
|
+
# ../github/cache.py
|
4106
|
+
|
4107
|
+
|
3896
4108
|
##
|
3897
4109
|
|
3898
4110
|
|
3899
|
-
|
3900
|
-
|
3901
|
-
|
3902
|
-
|
3903
|
-
# TODO: terminate, sleep, kill
|
3904
|
-
if proc.stdout:
|
3905
|
-
proc.stdout.close()
|
3906
|
-
if proc.stderr:
|
3907
|
-
proc.stderr.close()
|
3908
|
-
if proc.stdin:
|
3909
|
-
proc.stdin.close()
|
4111
|
+
class GithubCache(FileCache, DataCache):
|
4112
|
+
@dc.dataclass(frozen=True)
|
4113
|
+
class Config:
|
4114
|
+
dir: str
|
3910
4115
|
|
3911
|
-
|
4116
|
+
def __init__(
|
4117
|
+
self,
|
4118
|
+
config: Config,
|
4119
|
+
*,
|
4120
|
+
client: ta.Optional[GithubCacheClient] = None,
|
4121
|
+
version: ta.Optional[CacheVersion] = None,
|
4122
|
+
) -> None:
|
4123
|
+
super().__init__(
|
4124
|
+
version=version,
|
4125
|
+
)
|
4126
|
+
|
4127
|
+
self._config = config
|
4128
|
+
|
4129
|
+
if client is None:
|
4130
|
+
client = GithubCacheServiceV1Client(
|
4131
|
+
cache_version=self._version,
|
4132
|
+
)
|
4133
|
+
self._client: GithubCacheClient = client
|
4134
|
+
|
4135
|
+
self._local = DirectoryFileCache(
|
4136
|
+
DirectoryFileCache.Config(
|
4137
|
+
dir=check.non_empty_str(config.dir),
|
4138
|
+
),
|
4139
|
+
version=self._version,
|
4140
|
+
)
|
4141
|
+
|
4142
|
+
#
|
4143
|
+
|
4144
|
+
async def get_file(self, key: str) -> ta.Optional[str]:
|
4145
|
+
local_file = self._local.get_cache_file_path(key)
|
4146
|
+
if os.path.exists(local_file):
|
4147
|
+
return local_file
|
4148
|
+
|
4149
|
+
if (entry := await self._client.get_entry(key)) is None:
|
4150
|
+
return None
|
4151
|
+
|
4152
|
+
tmp_file = self._local.format_incomplete_file(local_file)
|
4153
|
+
with unlinking_if_exists(tmp_file):
|
4154
|
+
await self._client.download_file(entry, tmp_file)
|
4155
|
+
|
4156
|
+
os.replace(tmp_file, local_file)
|
4157
|
+
|
4158
|
+
return local_file
|
4159
|
+
|
4160
|
+
async def put_file(
|
4161
|
+
self,
|
4162
|
+
key: str,
|
4163
|
+
file_path: str,
|
4164
|
+
*,
|
4165
|
+
steal: bool = False,
|
4166
|
+
) -> str:
|
4167
|
+
cache_file_path = await self._local.put_file(
|
4168
|
+
key,
|
4169
|
+
file_path,
|
4170
|
+
steal=steal,
|
4171
|
+
)
|
4172
|
+
|
4173
|
+
await self._client.upload_file(key, cache_file_path)
|
4174
|
+
|
4175
|
+
return cache_file_path
|
4176
|
+
|
4177
|
+
#
|
4178
|
+
|
4179
|
+
async def get_data(self, key: str) -> ta.Optional[DataCache.Data]:
|
4180
|
+
local_file = self._local.get_cache_file_path(key)
|
4181
|
+
if os.path.exists(local_file):
|
4182
|
+
return DataCache.FileData(local_file)
|
4183
|
+
|
4184
|
+
if (entry := await self._client.get_entry(key)) is None:
|
4185
|
+
return None
|
4186
|
+
|
4187
|
+
return DataCache.UrlData(check.non_empty_str(self._client.get_entry_url(entry)))
|
4188
|
+
|
4189
|
+
async def put_data(self, key: str, data: DataCache.Data) -> None:
|
4190
|
+
await FileCacheDataCache(self).put_data(key, data)
|
4191
|
+
|
4192
|
+
|
4193
|
+
########################################
|
4194
|
+
# ../github/cli.py
|
4195
|
+
"""
|
4196
|
+
See:
|
4197
|
+
- https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28
|
4198
|
+
"""
|
4199
|
+
|
4200
|
+
|
4201
|
+
class GithubCli(ArgparseCli):
|
4202
|
+
@argparse_cmd()
|
4203
|
+
def list_referenced_env_vars(self) -> None:
|
4204
|
+
print('\n'.join(sorted(ev.k for ev in GITHUB_ENV_VARS)))
|
4205
|
+
|
4206
|
+
@argparse_cmd(
|
4207
|
+
argparse_arg('key'),
|
4208
|
+
)
|
4209
|
+
async def get_cache_entry(self) -> None:
|
4210
|
+
client = GithubCacheServiceV1Client()
|
4211
|
+
entry = await client.get_entry(self.args.key)
|
4212
|
+
if entry is None:
|
4213
|
+
return
|
4214
|
+
print(json_dumps_pretty(dc.asdict(entry))) # noqa
|
4215
|
+
|
4216
|
+
@argparse_cmd(
|
4217
|
+
argparse_arg('repository-id'),
|
4218
|
+
)
|
4219
|
+
def list_cache_entries(self) -> None:
|
4220
|
+
raise NotImplementedError
|
4221
|
+
|
4222
|
+
|
4223
|
+
########################################
|
4224
|
+
# ../../../omlish/subprocesses/base.py
|
4225
|
+
|
4226
|
+
|
4227
|
+
##
|
4228
|
+
|
4229
|
+
|
4230
|
+
# Valid channel type kwarg values:
|
4231
|
+
# - A special flag negative int
|
4232
|
+
# - A positive fd int
|
4233
|
+
# - A file-like object
|
4234
|
+
# - None
|
4235
|
+
|
4236
|
+
SUBPROCESS_CHANNEL_OPTION_VALUES: ta.Mapping[SubprocessChannelOption, int] = {
|
4237
|
+
'pipe': subprocess.PIPE,
|
4238
|
+
'stdout': subprocess.STDOUT,
|
4239
|
+
'devnull': subprocess.DEVNULL,
|
4240
|
+
}
|
3912
4241
|
|
3913
4242
|
|
3914
4243
|
##
|
@@ -4095,32 +4424,41 @@ class BaseSubprocesses(abc.ABC): # noqa
|
|
4095
4424
|
return e
|
4096
4425
|
|
4097
4426
|
|
4427
|
+
########################################
|
4428
|
+
# ../github/inject.py
|
4429
|
+
|
4430
|
+
|
4098
4431
|
##
|
4099
4432
|
|
4100
4433
|
|
4101
|
-
|
4102
|
-
|
4103
|
-
|
4104
|
-
|
4105
|
-
|
4106
|
-
|
4107
|
-
|
4108
|
-
|
4434
|
+
def bind_github(
|
4435
|
+
*,
|
4436
|
+
cache_dir: ta.Optional[str] = None,
|
4437
|
+
) -> InjectorBindings:
|
4438
|
+
lst: ta.List[InjectorBindingOrBindings] = []
|
4439
|
+
|
4440
|
+
if cache_dir is not None:
|
4441
|
+
lst.extend([
|
4442
|
+
inj.bind(GithubCache.Config(
|
4443
|
+
dir=cache_dir,
|
4444
|
+
)),
|
4445
|
+
inj.bind(GithubCache, singleton=True),
|
4446
|
+
inj.bind(FileCache, to_key=GithubCache),
|
4447
|
+
])
|
4109
4448
|
|
4449
|
+
return inj.as_bindings(*lst)
|
4110
4450
|
|
4111
|
-
@dc.dataclass(frozen=True)
|
4112
|
-
class SubprocessRunOutput(ta.Generic[T]):
|
4113
|
-
proc: T
|
4114
4451
|
|
4115
|
-
|
4452
|
+
########################################
|
4453
|
+
# ../../../omlish/subprocesses/async_.py
|
4116
4454
|
|
4117
|
-
stdout: ta.Optional[bytes] = None
|
4118
|
-
stderr: ta.Optional[bytes] = None
|
4119
4455
|
|
4456
|
+
##
|
4120
4457
|
|
4121
|
-
|
4458
|
+
|
4459
|
+
class AbstractAsyncSubprocesses(BaseSubprocesses):
|
4122
4460
|
@abc.abstractmethod
|
4123
|
-
def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
|
4461
|
+
async def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
|
4124
4462
|
raise NotImplementedError
|
4125
4463
|
|
4126
4464
|
def run(
|
@@ -4131,7 +4469,7 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
|
|
4131
4469
|
check: bool = False,
|
4132
4470
|
capture_output: ta.Optional[bool] = None,
|
4133
4471
|
**kwargs: ta.Any,
|
4134
|
-
) -> SubprocessRunOutput:
|
4472
|
+
) -> ta.Awaitable[SubprocessRunOutput]:
|
4135
4473
|
return self.run_(SubprocessRun(
|
4136
4474
|
cmd=cmd,
|
4137
4475
|
input=input,
|
@@ -4144,7 +4482,7 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
|
|
4144
4482
|
#
|
4145
4483
|
|
4146
4484
|
@abc.abstractmethod
|
4147
|
-
def check_call(
|
4485
|
+
async def check_call(
|
4148
4486
|
self,
|
4149
4487
|
*cmd: str,
|
4150
4488
|
stdout: ta.Any = sys.stderr,
|
@@ -4153,7 +4491,7 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
|
|
4153
4491
|
raise NotImplementedError
|
4154
4492
|
|
4155
4493
|
@abc.abstractmethod
|
4156
|
-
def check_output(
|
4494
|
+
async def check_output(
|
4157
4495
|
self,
|
4158
4496
|
*cmd: str,
|
4159
4497
|
**kwargs: ta.Any,
|
@@ -4162,96 +4500,56 @@ class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
|
|
4162
4500
|
|
4163
4501
|
#
|
4164
4502
|
|
4165
|
-
def check_output_str(
|
4503
|
+
async def check_output_str(
|
4166
4504
|
self,
|
4167
4505
|
*cmd: str,
|
4168
4506
|
**kwargs: ta.Any,
|
4169
4507
|
) -> str:
|
4170
|
-
return self.check_output(*cmd, **kwargs).decode().strip()
|
4508
|
+
return (await self.check_output(*cmd, **kwargs)).decode().strip()
|
4171
4509
|
|
4172
4510
|
#
|
4173
4511
|
|
4174
|
-
def try_call(
|
4512
|
+
async def try_call(
|
4175
4513
|
self,
|
4176
4514
|
*cmd: str,
|
4177
4515
|
**kwargs: ta.Any,
|
4178
4516
|
) -> bool:
|
4179
|
-
if isinstance(self.
|
4517
|
+
if isinstance(await self.async_try_fn(self.check_call, *cmd, **kwargs), Exception):
|
4180
4518
|
return False
|
4181
4519
|
else:
|
4182
4520
|
return True
|
4183
4521
|
|
4184
|
-
def try_output(
|
4522
|
+
async def try_output(
|
4185
4523
|
self,
|
4186
4524
|
*cmd: str,
|
4187
4525
|
**kwargs: ta.Any,
|
4188
4526
|
) -> ta.Optional[bytes]:
|
4189
|
-
if isinstance(ret := self.
|
4527
|
+
if isinstance(ret := await self.async_try_fn(self.check_output, *cmd, **kwargs), Exception):
|
4190
4528
|
return None
|
4191
4529
|
else:
|
4192
4530
|
return ret
|
4193
4531
|
|
4194
|
-
def try_output_str(
|
4532
|
+
async def try_output_str(
|
4195
4533
|
self,
|
4196
4534
|
*cmd: str,
|
4197
4535
|
**kwargs: ta.Any,
|
4198
4536
|
) -> ta.Optional[str]:
|
4199
|
-
if (ret := self.try_output(*cmd, **kwargs)) is None:
|
4537
|
+
if (ret := await self.try_output(*cmd, **kwargs)) is None:
|
4200
4538
|
return None
|
4201
4539
|
else:
|
4202
4540
|
return ret.decode().strip()
|
4203
4541
|
|
4204
4542
|
|
4205
|
-
|
4206
|
-
|
4207
|
-
|
4208
|
-
class Subprocesses(AbstractSubprocesses):
|
4209
|
-
def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
|
4210
|
-
proc = subprocess.run(
|
4211
|
-
run.cmd,
|
4212
|
-
input=run.input,
|
4213
|
-
timeout=run.timeout,
|
4214
|
-
check=run.check,
|
4215
|
-
capture_output=run.capture_output or False,
|
4216
|
-
**(run.kwargs or {}),
|
4217
|
-
)
|
4218
|
-
|
4219
|
-
return SubprocessRunOutput(
|
4220
|
-
proc=proc,
|
4221
|
-
|
4222
|
-
returncode=proc.returncode,
|
4223
|
-
|
4224
|
-
stdout=proc.stdout, # noqa
|
4225
|
-
stderr=proc.stderr, # noqa
|
4226
|
-
)
|
4227
|
-
|
4228
|
-
def check_call(
|
4229
|
-
self,
|
4230
|
-
*cmd: str,
|
4231
|
-
stdout: ta.Any = sys.stderr,
|
4232
|
-
**kwargs: ta.Any,
|
4233
|
-
) -> None:
|
4234
|
-
with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
|
4235
|
-
subprocess.check_call(cmd, **kwargs)
|
4236
|
-
|
4237
|
-
def check_output(
|
4238
|
-
self,
|
4239
|
-
*cmd: str,
|
4240
|
-
**kwargs: ta.Any,
|
4241
|
-
) -> bytes:
|
4242
|
-
with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
|
4243
|
-
return subprocess.check_output(cmd, **kwargs)
|
4244
|
-
|
4245
|
-
|
4246
|
-
subprocesses = Subprocesses()
|
4543
|
+
########################################
|
4544
|
+
# ../../../omlish/subprocesses/sync.py
|
4247
4545
|
|
4248
4546
|
|
4249
4547
|
##
|
4250
4548
|
|
4251
4549
|
|
4252
|
-
class
|
4550
|
+
class AbstractSubprocesses(BaseSubprocesses, abc.ABC):
|
4253
4551
|
@abc.abstractmethod
|
4254
|
-
|
4552
|
+
def run_(self, run: SubprocessRun) -> SubprocessRunOutput:
|
4255
4553
|
raise NotImplementedError
|
4256
4554
|
|
4257
4555
|
def run(
|
@@ -4262,7 +4560,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
|
|
4262
4560
|
check: bool = False,
|
4263
4561
|
capture_output: ta.Optional[bool] = None,
|
4264
4562
|
**kwargs: ta.Any,
|
4265
|
-
) ->
|
4563
|
+
) -> SubprocessRunOutput:
|
4266
4564
|
return self.run_(SubprocessRun(
|
4267
4565
|
cmd=cmd,
|
4268
4566
|
input=input,
|
@@ -4275,7 +4573,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
|
|
4275
4573
|
#
|
4276
4574
|
|
4277
4575
|
@abc.abstractmethod
|
4278
|
-
|
4576
|
+
def check_call(
|
4279
4577
|
self,
|
4280
4578
|
*cmd: str,
|
4281
4579
|
stdout: ta.Any = sys.stderr,
|
@@ -4284,7 +4582,7 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
|
|
4284
4582
|
raise NotImplementedError
|
4285
4583
|
|
4286
4584
|
@abc.abstractmethod
|
4287
|
-
|
4585
|
+
def check_output(
|
4288
4586
|
self,
|
4289
4587
|
*cmd: str,
|
4290
4588
|
**kwargs: ta.Any,
|
@@ -4293,146 +4591,94 @@ class AbstractAsyncSubprocesses(BaseSubprocesses):
|
|
4293
4591
|
|
4294
4592
|
#
|
4295
4593
|
|
4296
|
-
|
4594
|
+
def check_output_str(
|
4297
4595
|
self,
|
4298
4596
|
*cmd: str,
|
4299
4597
|
**kwargs: ta.Any,
|
4300
4598
|
) -> str:
|
4301
|
-
return
|
4599
|
+
return self.check_output(*cmd, **kwargs).decode().strip()
|
4302
4600
|
|
4303
4601
|
#
|
4304
4602
|
|
4305
|
-
|
4603
|
+
def try_call(
|
4306
4604
|
self,
|
4307
4605
|
*cmd: str,
|
4308
4606
|
**kwargs: ta.Any,
|
4309
4607
|
) -> bool:
|
4310
|
-
if isinstance(
|
4608
|
+
if isinstance(self.try_fn(self.check_call, *cmd, **kwargs), Exception):
|
4311
4609
|
return False
|
4312
4610
|
else:
|
4313
4611
|
return True
|
4314
4612
|
|
4315
|
-
|
4613
|
+
def try_output(
|
4316
4614
|
self,
|
4317
4615
|
*cmd: str,
|
4318
4616
|
**kwargs: ta.Any,
|
4319
4617
|
) -> ta.Optional[bytes]:
|
4320
|
-
if isinstance(ret :=
|
4618
|
+
if isinstance(ret := self.try_fn(self.check_output, *cmd, **kwargs), Exception):
|
4321
4619
|
return None
|
4322
4620
|
else:
|
4323
4621
|
return ret
|
4324
4622
|
|
4325
|
-
|
4623
|
+
def try_output_str(
|
4326
4624
|
self,
|
4327
4625
|
*cmd: str,
|
4328
4626
|
**kwargs: ta.Any,
|
4329
4627
|
) -> ta.Optional[str]:
|
4330
|
-
if (ret :=
|
4628
|
+
if (ret := self.try_output(*cmd, **kwargs)) is None:
|
4331
4629
|
return None
|
4332
4630
|
else:
|
4333
4631
|
return ret.decode().strip()
|
4334
4632
|
|
4335
4633
|
|
4336
|
-
########################################
|
4337
|
-
# ../github/cache.py
|
4338
|
-
|
4339
|
-
|
4340
4634
|
##
|
4341
4635
|
|
4342
4636
|
|
4343
|
-
class
|
4344
|
-
|
4345
|
-
|
4346
|
-
|
4347
|
-
|
4348
|
-
|
4349
|
-
|
4350
|
-
|
4351
|
-
|
4352
|
-
|
4353
|
-
|
4354
|
-
) -> None:
|
4355
|
-
super().__init__(
|
4356
|
-
version=version,
|
4357
|
-
)
|
4637
|
+
class Subprocesses(AbstractSubprocesses):
|
4638
|
+
def run_(self, run: SubprocessRun) -> SubprocessRunOutput[subprocess.CompletedProcess]:
|
4639
|
+
with self.prepare_and_wrap(
|
4640
|
+
*run.cmd,
|
4641
|
+
input=run.input,
|
4642
|
+
timeout=run.timeout,
|
4643
|
+
check=run.check,
|
4644
|
+
capture_output=run.capture_output or False,
|
4645
|
+
**(run.kwargs or {}),
|
4646
|
+
) as (cmd, kwargs):
|
4647
|
+
proc = subprocess.run(cmd, **kwargs) # noqa
|
4358
4648
|
|
4359
|
-
|
4649
|
+
return SubprocessRunOutput(
|
4650
|
+
proc=proc,
|
4360
4651
|
|
4361
|
-
|
4362
|
-
client = GithubCacheServiceV1Client(
|
4363
|
-
cache_version=self._version,
|
4364
|
-
)
|
4365
|
-
self._client: GithubCacheClient = client
|
4652
|
+
returncode=proc.returncode,
|
4366
4653
|
|
4367
|
-
|
4368
|
-
|
4369
|
-
dir=check.non_empty_str(config.dir),
|
4370
|
-
),
|
4371
|
-
version=self._version,
|
4654
|
+
stdout=proc.stdout, # noqa
|
4655
|
+
stderr=proc.stderr, # noqa
|
4372
4656
|
)
|
4373
4657
|
|
4374
|
-
|
4375
|
-
local_file = self._local.get_cache_file_path(key)
|
4376
|
-
if os.path.exists(local_file):
|
4377
|
-
return local_file
|
4378
|
-
|
4379
|
-
if (entry := await self._client.get_entry(key)) is None:
|
4380
|
-
return None
|
4381
|
-
|
4382
|
-
tmp_file = self._local.format_incomplete_file(local_file)
|
4383
|
-
with unlinking_if_exists(tmp_file):
|
4384
|
-
await self._client.download_file(entry, tmp_file)
|
4385
|
-
|
4386
|
-
os.replace(tmp_file, local_file)
|
4387
|
-
|
4388
|
-
return local_file
|
4389
|
-
|
4390
|
-
async def put_file(
|
4658
|
+
def check_call(
|
4391
4659
|
self,
|
4392
|
-
|
4393
|
-
|
4394
|
-
|
4395
|
-
|
4396
|
-
|
4397
|
-
|
4398
|
-
key,
|
4399
|
-
file_path,
|
4400
|
-
steal=steal,
|
4401
|
-
)
|
4402
|
-
|
4403
|
-
await self._client.upload_file(key, cache_file_path)
|
4404
|
-
|
4405
|
-
return cache_file_path
|
4660
|
+
*cmd: str,
|
4661
|
+
stdout: ta.Any = sys.stderr,
|
4662
|
+
**kwargs: ta.Any,
|
4663
|
+
) -> None:
|
4664
|
+
with self.prepare_and_wrap(*cmd, stdout=stdout, **kwargs) as (cmd, kwargs): # noqa
|
4665
|
+
subprocess.check_call(cmd, **kwargs)
|
4406
4666
|
|
4667
|
+
def check_output(
|
4668
|
+
self,
|
4669
|
+
*cmd: str,
|
4670
|
+
**kwargs: ta.Any,
|
4671
|
+
) -> bytes:
|
4672
|
+
with self.prepare_and_wrap(*cmd, **kwargs) as (cmd, kwargs): # noqa
|
4673
|
+
return subprocess.check_output(cmd, **kwargs)
|
4407
4674
|
|
4408
|
-
########################################
|
4409
|
-
# ../github/cli.py
|
4410
|
-
"""
|
4411
|
-
See:
|
4412
|
-
- https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28
|
4413
|
-
"""
|
4414
4675
|
|
4676
|
+
##
|
4415
4677
|
|
4416
|
-
class GithubCli(ArgparseCli):
|
4417
|
-
@argparse_cmd()
|
4418
|
-
def list_referenced_env_vars(self) -> None:
|
4419
|
-
print('\n'.join(sorted(ev.k for ev in GITHUB_ENV_VARS)))
|
4420
4678
|
|
4421
|
-
|
4422
|
-
argparse_arg('key'),
|
4423
|
-
)
|
4424
|
-
async def get_cache_entry(self) -> None:
|
4425
|
-
client = GithubCacheServiceV1Client()
|
4426
|
-
entry = await client.get_entry(self.args.key)
|
4427
|
-
if entry is None:
|
4428
|
-
return
|
4429
|
-
print(json_dumps_pretty(dc.asdict(entry))) # noqa
|
4679
|
+
subprocesses = Subprocesses()
|
4430
4680
|
|
4431
|
-
|
4432
|
-
argparse_arg('repository-id'),
|
4433
|
-
)
|
4434
|
-
def list_cache_entries(self) -> None:
|
4435
|
-
raise NotImplementedError
|
4681
|
+
SubprocessRun._DEFAULT_SUBPROCESSES = subprocesses # noqa
|
4436
4682
|
|
4437
4683
|
|
4438
4684
|
########################################
|
@@ -4648,19 +4894,19 @@ class AsyncioSubprocesses(AbstractAsyncSubprocesses):
|
|
4648
4894
|
timeout: ta.Optional[float] = None,
|
4649
4895
|
**kwargs: ta.Any,
|
4650
4896
|
) -> ta.AsyncGenerator[asyncio.subprocess.Process, None]:
|
4651
|
-
fac: ta.Any
|
4652
|
-
if shell:
|
4653
|
-
fac = functools.partial(
|
4654
|
-
asyncio.create_subprocess_shell,
|
4655
|
-
check.single(cmd),
|
4656
|
-
)
|
4657
|
-
else:
|
4658
|
-
fac = functools.partial(
|
4659
|
-
asyncio.create_subprocess_exec,
|
4660
|
-
*cmd,
|
4661
|
-
)
|
4662
|
-
|
4663
4897
|
with self.prepare_and_wrap( *cmd, shell=shell, **kwargs) as (cmd, kwargs): # noqa
|
4898
|
+
fac: ta.Any
|
4899
|
+
if shell:
|
4900
|
+
fac = functools.partial(
|
4901
|
+
asyncio.create_subprocess_shell,
|
4902
|
+
check.single(cmd),
|
4903
|
+
)
|
4904
|
+
else:
|
4905
|
+
fac = functools.partial(
|
4906
|
+
asyncio.create_subprocess_exec,
|
4907
|
+
*cmd,
|
4908
|
+
)
|
4909
|
+
|
4664
4910
|
proc: asyncio.subprocess.Process = await fac(**kwargs)
|
4665
4911
|
try:
|
4666
4912
|
yield proc
|
@@ -5004,31 +5250,6 @@ async def load_docker_tar(
|
|
5004
5250
|
return await load_docker_tar_cmd(ShellCmd(f'cat {shlex.quote(tar_file)}'))
|
5005
5251
|
|
5006
5252
|
|
5007
|
-
########################################
|
5008
|
-
# ../github/inject.py
|
5009
|
-
|
5010
|
-
|
5011
|
-
##
|
5012
|
-
|
5013
|
-
|
5014
|
-
def bind_github(
|
5015
|
-
*,
|
5016
|
-
cache_dir: ta.Optional[str] = None,
|
5017
|
-
) -> InjectorBindings:
|
5018
|
-
lst: ta.List[InjectorBindingOrBindings] = []
|
5019
|
-
|
5020
|
-
if cache_dir is not None:
|
5021
|
-
lst.extend([
|
5022
|
-
inj.bind(GithubFileCache.Config(
|
5023
|
-
dir=cache_dir,
|
5024
|
-
)),
|
5025
|
-
inj.bind(GithubFileCache, singleton=True),
|
5026
|
-
inj.bind(FileCache, to_key=GithubFileCache),
|
5027
|
-
])
|
5028
|
-
|
5029
|
-
return inj.as_bindings(*lst)
|
5030
|
-
|
5031
|
-
|
5032
5253
|
########################################
|
5033
5254
|
# ../docker/cache.py
|
5034
5255
|
|