rbx.cp 0.7.0__py3-none-any.whl → 0.9.0__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.
- rbx/box/cd.py +2 -2
- rbx/box/cli.py +87 -33
- rbx/box/code.py +133 -84
- rbx/box/contest/build_contest_statements.py +2 -2
- rbx/box/contest/contest_package.py +1 -1
- rbx/box/contest/main.py +29 -2
- rbx/box/environment.py +140 -80
- rbx/box/formatting.py +2 -1
- rbx/box/global_package.py +74 -0
- rbx/box/package.py +11 -24
- rbx/box/packaging/__init__.py +0 -0
- rbx/box/packaging/boca/__init__.py +0 -0
- rbx/box/packaging/polygon/packager.py +3 -3
- rbx/box/presets/__init__.py +369 -53
- rbx/box/presets/lock_schema.py +42 -2
- rbx/box/presets/schema.py +4 -0
- rbx/box/remote.py +21 -2
- rbx/box/retries.py +3 -2
- rbx/box/sanitizers/warning_stack.py +5 -5
- rbx/box/solutions.py +37 -25
- rbx/box/statements/build_statements.py +6 -6
- rbx/box/statements/builders.py +1 -1
- rbx/box/stats.py +10 -0
- rbx/box/stresses.py +47 -66
- rbx/box/stressing/finder_parser.py +11 -16
- rbx/box/tasks.py +33 -22
- rbx/box/testcase_utils.py +3 -3
- rbx/box/tooling/boca/scraper.py +1 -1
- rbx/grading/caching.py +98 -47
- rbx/grading/debug_context.py +31 -0
- rbx/grading/grading_context.py +96 -0
- rbx/grading/judge/cacher.py +93 -21
- rbx/grading/judge/sandbox.py +8 -4
- rbx/grading/judge/sandboxes/isolate.py +3 -2
- rbx/grading/judge/sandboxes/stupid_sandbox.py +3 -2
- rbx/grading/judge/sandboxes/timeit.py +1 -1
- rbx/grading/judge/storage.py +170 -35
- rbx/grading/profiling.py +126 -0
- rbx/grading/steps.py +46 -17
- rbx/grading/steps_with_caching.py +52 -26
- rbx/resources/envs/default.rbx.yml +2 -3
- rbx/resources/envs/isolate.rbx.yml +2 -3
- rbx/resources/presets/default/contest/.gitignore +6 -0
- rbx/resources/presets/default/contest/contest.rbx.yml +14 -1
- rbx/resources/presets/default/contest/statement/contest.rbx.tex +24 -86
- rbx/resources/presets/default/contest/statement/instructions.tex +40 -0
- rbx/resources/presets/default/contest/statement/logo.png +0 -0
- rbx/resources/presets/default/env.rbx.yml +67 -0
- rbx/resources/presets/default/preset.rbx.yml +6 -2
- rbx/resources/presets/default/problem/.gitignore +1 -1
- rbx/resources/presets/default/problem/problem.rbx.yml +12 -8
- rbx/resources/presets/default/shared/contest_template.rbx.tex +57 -0
- rbx/resources/presets/default/shared/icpc.sty +322 -0
- rbx/resources/presets/default/shared/problem_template.rbx.tex +57 -0
- rbx/submitors/codeforces.py +3 -2
- rbx/test.py +1 -1
- rbx/utils.py +6 -1
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/METADATA +4 -1
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/RECORD +67 -58
- rbx/resources/presets/default/contest/statement/olymp.sty +0 -250
- rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -42
- rbx/resources/presets/default/problem/statement/olymp.sty +0 -250
- rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -89
- /rbx/resources/presets/default/problem/{gen.cpp → gens/gen.cpp} +0 -0
- /rbx/resources/presets/default/problem/{tests → manual_tests}/samples/000.in +0 -0
- /rbx/resources/presets/default/problem/{tests → manual_tests}/samples/001.in +0 -0
- /rbx/resources/presets/default/problem/{random.py → testplan/random.py} +0 -0
- /rbx/resources/presets/default/problem/{random.txt → testplan/random.txt} +0 -0
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/LICENSE +0 -0
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/WHEEL +0 -0
- {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/entry_points.txt +0 -0
rbx/box/tasks.py
CHANGED
@@ -5,7 +5,8 @@ from rbx.box import checkers, package, state
|
|
5
5
|
from rbx.box.code import CommunicationItem, run_communication, run_item
|
6
6
|
from rbx.box.environment import EnvironmentSandbox, ExecutionConfig, VerificationLevel
|
7
7
|
from rbx.box.retries import Retrier, get_retrier_config
|
8
|
-
from rbx.box.schema import
|
8
|
+
from rbx.box.schema import CodeItem, Testcase
|
9
|
+
from rbx.grading import profiling
|
9
10
|
from rbx.grading.judge.sandbox import SandboxBase
|
10
11
|
from rbx.grading.limits import Limits
|
11
12
|
from rbx.grading.steps import (
|
@@ -39,7 +40,7 @@ def get_limits_for_language(
|
|
39
40
|
|
40
41
|
|
41
42
|
async def run_solution_on_testcase(
|
42
|
-
solution:
|
43
|
+
solution: CodeItem,
|
43
44
|
compiled_digest: str,
|
44
45
|
checker_digest: Optional[str],
|
45
46
|
testcase: Testcase,
|
@@ -52,6 +53,8 @@ async def run_solution_on_testcase(
|
|
52
53
|
use_timelimit: bool = True,
|
53
54
|
capture_pipes: bool = False,
|
54
55
|
nruns: int = 0,
|
56
|
+
filestem: Optional[str] = None,
|
57
|
+
is_stress: bool = False,
|
55
58
|
) -> Evaluation:
|
56
59
|
if interactor_digest is not None:
|
57
60
|
return await _run_communication_solution_on_testcase(
|
@@ -68,6 +71,8 @@ async def run_solution_on_testcase(
|
|
68
71
|
use_timelimit=use_timelimit,
|
69
72
|
capture_pipes=capture_pipes,
|
70
73
|
nruns=nruns,
|
74
|
+
filestem=filestem,
|
75
|
+
is_stress=is_stress,
|
71
76
|
)
|
72
77
|
|
73
78
|
async def run_fn(retry_index: int) -> Evaluation:
|
@@ -85,29 +90,32 @@ async def run_solution_on_testcase(
|
|
85
90
|
assert testcase.outputPath is not None
|
86
91
|
output_path = testcase.outputPath
|
87
92
|
else:
|
88
|
-
|
93
|
+
stem = filestem or testcase.inputPath.stem
|
94
|
+
output_path = output_dir / pathlib.PosixPath(stem).with_suffix('.out')
|
89
95
|
error_path = output_path.with_suffix('.err')
|
90
96
|
log_path = output_path.with_suffix('.log')
|
91
97
|
eval_path = output_path.with_suffix('.eval')
|
92
98
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
93
99
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
100
|
+
with profiling.PushContext('tasks.run_solution_on_testcase'):
|
101
|
+
run_log = await run_item(
|
102
|
+
solution,
|
103
|
+
DigestOrSource.create(compiled_digest),
|
104
|
+
stdin=DigestOrSource.create(testcase.inputPath),
|
105
|
+
stdout=DigestOrDest.create(output_path),
|
106
|
+
stderr=DigestOrDest.create(error_path),
|
107
|
+
extra_config=extra_config,
|
108
|
+
retry_index=retry_index,
|
109
|
+
)
|
103
110
|
|
104
111
|
if checker_digest is not None:
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
112
|
+
with profiling.PushContext('tasks.run_solution_on_testcase.check'):
|
113
|
+
checker_result = await checkers.check(
|
114
|
+
checker_digest,
|
115
|
+
run_log,
|
116
|
+
testcase,
|
117
|
+
program_output=output_path,
|
118
|
+
)
|
111
119
|
else:
|
112
120
|
checker_result = checkers.check_with_no_output(run_log)
|
113
121
|
|
@@ -134,7 +142,7 @@ async def run_solution_on_testcase(
|
|
134
142
|
if not use_retries:
|
135
143
|
return await run_fn(0)
|
136
144
|
|
137
|
-
retrier = Retrier(get_retrier_config(nruns))
|
145
|
+
retrier = Retrier(get_retrier_config(nruns), is_stress=is_stress)
|
138
146
|
return await retrier.repeat(run_fn)
|
139
147
|
|
140
148
|
|
@@ -156,7 +164,7 @@ def _get_execution_config(
|
|
156
164
|
|
157
165
|
|
158
166
|
async def _run_communication_solution_on_testcase(
|
159
|
-
solution:
|
167
|
+
solution: CodeItem,
|
160
168
|
compiled_digest: str,
|
161
169
|
interactor_digest: str,
|
162
170
|
checker_digest: Optional[str],
|
@@ -169,6 +177,8 @@ async def _run_communication_solution_on_testcase(
|
|
169
177
|
use_timelimit: bool = True,
|
170
178
|
capture_pipes: bool = False,
|
171
179
|
nruns: int = 0,
|
180
|
+
filestem: Optional[str] = None,
|
181
|
+
is_stress: bool = False,
|
172
182
|
) -> Evaluation:
|
173
183
|
capture_pipes = capture_pipes or state.STATE.debug_logs
|
174
184
|
|
@@ -200,7 +210,8 @@ async def _run_communication_solution_on_testcase(
|
|
200
210
|
assert testcase.outputPath is not None
|
201
211
|
output_path = testcase.outputPath
|
202
212
|
else:
|
203
|
-
|
213
|
+
stem = filestem or testcase.inputPath.stem
|
214
|
+
output_path = output_dir / pathlib.PosixPath(stem).with_suffix('.out')
|
204
215
|
solution_error_path = output_path.with_suffix('.sol.err')
|
205
216
|
interactor_error_path = output_path.with_suffix('.int.err')
|
206
217
|
log_path = output_path.with_suffix('.log')
|
@@ -294,5 +305,5 @@ async def _run_communication_solution_on_testcase(
|
|
294
305
|
if not use_retries:
|
295
306
|
return await run_fn(0)
|
296
307
|
|
297
|
-
retrier = Retrier(get_retrier_config(nruns))
|
308
|
+
retrier = Retrier(get_retrier_config(nruns), is_stress=is_stress)
|
298
309
|
return await retrier.repeat(run_fn)
|
rbx/box/testcase_utils.py
CHANGED
@@ -7,7 +7,7 @@ import rich.text
|
|
7
7
|
import typer
|
8
8
|
from pydantic import BaseModel
|
9
9
|
|
10
|
-
from rbx import console
|
10
|
+
from rbx import console, utils
|
11
11
|
from rbx.box import package
|
12
12
|
from rbx.box.package import get_build_testgroup_path, get_build_tests_path
|
13
13
|
from rbx.box.schema import Testcase, TestcaseGroup
|
@@ -141,8 +141,8 @@ def get_samples() -> List[Testcase]:
|
|
141
141
|
tcs = find_built_testcases(package.get_testgroup('samples'))
|
142
142
|
return [
|
143
143
|
Testcase(
|
144
|
-
inputPath=tc.inputPath
|
145
|
-
outputPath=tc.outputPath
|
144
|
+
inputPath=utils.abspath(tc.inputPath),
|
145
|
+
outputPath=utils.abspath(tc.outputPath)
|
146
146
|
if tc.outputPath is not None and tc.outputPath.is_file()
|
147
147
|
else None,
|
148
148
|
)
|
rbx/box/tooling/boca/scraper.py
CHANGED
rbx/grading/caching.py
CHANGED
@@ -3,13 +3,18 @@ import io
|
|
3
3
|
import os
|
4
4
|
import pathlib
|
5
5
|
import shelve
|
6
|
+
import shutil
|
7
|
+
import tempfile
|
6
8
|
from typing import Any, Dict, List, Optional
|
7
9
|
|
8
10
|
from pydantic import BaseModel
|
9
11
|
|
10
12
|
from rbx import console
|
13
|
+
from rbx.grading import grading_context
|
14
|
+
from rbx.grading.judge.cacher import FileCacher
|
11
15
|
from rbx.grading.judge.digester import digest_cooperatively
|
12
|
-
from rbx.grading.judge.storage import
|
16
|
+
from rbx.grading.judge.storage import copyfileobj
|
17
|
+
from rbx.grading.profiling import Profiler
|
13
18
|
from rbx.grading.steps import DigestHolder, GradingArtifacts, GradingLogsHolder
|
14
19
|
|
15
20
|
VERBOSE = False
|
@@ -89,11 +94,15 @@ def _build_digest_list(artifacts_list: List[GradingArtifacts]) -> List[DigestHol
|
|
89
94
|
return digests
|
90
95
|
|
91
96
|
|
92
|
-
def _build_fingerprint_list(
|
97
|
+
def _build_fingerprint_list(
|
98
|
+
artifacts_list: List[GradingArtifacts], cacher: FileCacher
|
99
|
+
) -> List[str]:
|
93
100
|
fingerprints = []
|
94
101
|
for artifacts in artifacts_list:
|
95
102
|
for input in artifacts.inputs:
|
96
|
-
if input.src is None:
|
103
|
+
if input.src is None or not input.hash:
|
104
|
+
continue
|
105
|
+
if cacher.digest_from_symlink(input.src) is not None:
|
97
106
|
continue
|
98
107
|
with input.src.open('rb') as f:
|
99
108
|
fingerprints.append(digest_cooperatively(f))
|
@@ -124,9 +133,10 @@ def _build_logs_list(artifacts_list: List[GradingArtifacts]) -> List[GradingLogs
|
|
124
133
|
|
125
134
|
def _build_cache_fingerprint(
|
126
135
|
artifacts_list: List[GradingArtifacts],
|
136
|
+
cacher: FileCacher,
|
127
137
|
) -> CacheFingerprint:
|
128
138
|
digests = [digest.value for digest in _build_digest_list(artifacts_list)]
|
129
|
-
fingerprints = _build_fingerprint_list(artifacts_list)
|
139
|
+
fingerprints = _build_fingerprint_list(artifacts_list, cacher)
|
130
140
|
output_fingerprints = _build_output_fingerprint_list(artifacts_list)
|
131
141
|
logs = _build_logs_list(artifacts_list)
|
132
142
|
return CacheFingerprint(
|
@@ -155,6 +165,7 @@ def _build_cache_input(
|
|
155
165
|
commands: List[str],
|
156
166
|
artifact_list: List[GradingArtifacts],
|
157
167
|
extra_params: Dict[str, Any],
|
168
|
+
cacher: FileCacher,
|
158
169
|
) -> CacheInput:
|
159
170
|
cloned_artifact_list = [
|
160
171
|
artifacts.model_copy(deep=True) for artifacts in artifact_list
|
@@ -164,6 +175,15 @@ def _build_cache_input(
|
|
164
175
|
# part of the cache key.
|
165
176
|
artifacts.logs = None
|
166
177
|
|
178
|
+
for input in artifacts.inputs:
|
179
|
+
if input.src is None:
|
180
|
+
continue
|
181
|
+
inferred_digest = cacher.digest_from_symlink(input.src)
|
182
|
+
if inferred_digest is not None:
|
183
|
+
# Consume cache from digest instead of file.
|
184
|
+
input.digest = DigestHolder(value=inferred_digest)
|
185
|
+
input.src = None
|
186
|
+
|
167
187
|
for output in artifacts.outputs:
|
168
188
|
if output.hash:
|
169
189
|
# Cleanup dest field from hash artifacts
|
@@ -185,7 +205,7 @@ def _build_cache_key(input: CacheInput) -> str:
|
|
185
205
|
return digest_cooperatively(fobj)
|
186
206
|
|
187
207
|
|
188
|
-
def _copy_hashed_files(artifact_list: List[GradingArtifacts],
|
208
|
+
def _copy_hashed_files(artifact_list: List[GradingArtifacts], cacher: FileCacher):
|
189
209
|
for artifact in artifact_list:
|
190
210
|
for output in artifact.outputs:
|
191
211
|
if not output.hash or output.dest is None:
|
@@ -194,19 +214,27 @@ def _copy_hashed_files(artifact_list: List[GradingArtifacts], storage: Storage):
|
|
194
214
|
if output.optional and output.digest.value is None:
|
195
215
|
continue
|
196
216
|
assert output.digest.value is not None
|
197
|
-
|
198
|
-
|
199
|
-
|
217
|
+
if (
|
218
|
+
path_to_symlink := cacher.path_for_symlink(output.digest.value)
|
219
|
+
) is not None:
|
220
|
+
# Use a symlink to the file in the persistent cache, if available.
|
221
|
+
output.dest.unlink(missing_ok=True)
|
222
|
+
output.dest.symlink_to(path_to_symlink)
|
223
|
+
else:
|
224
|
+
# Otherwise, copy it.
|
225
|
+
with cacher.get_file(output.digest.value) as fobj:
|
226
|
+
with output.dest.open('wb') as f:
|
227
|
+
copyfileobj(fobj, f, maxlen=output.maxlen)
|
200
228
|
if output.executable:
|
201
229
|
output.dest.chmod(0o755)
|
202
230
|
|
203
231
|
|
204
|
-
def is_artifact_ok(artifact: GradingArtifacts,
|
232
|
+
def is_artifact_ok(artifact: GradingArtifacts, cacher: FileCacher) -> bool:
|
205
233
|
for output in artifact.outputs:
|
206
234
|
if output.optional or output.intermediate:
|
207
235
|
continue
|
208
236
|
if output.digest is not None:
|
209
|
-
if output.digest.value is None or not
|
237
|
+
if output.digest.value is None or not cacher.exists(output.digest.value):
|
210
238
|
return False
|
211
239
|
return True
|
212
240
|
assert output.dest is not None
|
@@ -219,9 +247,9 @@ def is_artifact_ok(artifact: GradingArtifacts, storage: Storage) -> bool:
|
|
219
247
|
return True
|
220
248
|
|
221
249
|
|
222
|
-
def are_artifacts_ok(artifacts: List[GradingArtifacts],
|
250
|
+
def are_artifacts_ok(artifacts: List[GradingArtifacts], cacher: FileCacher) -> bool:
|
223
251
|
for artifact in artifacts:
|
224
|
-
if not is_artifact_ok(artifact,
|
252
|
+
if not is_artifact_ok(artifact, cacher):
|
225
253
|
return False
|
226
254
|
return True
|
227
255
|
|
@@ -244,53 +272,70 @@ class DependencyCacheBlock:
|
|
244
272
|
self._key = None
|
245
273
|
|
246
274
|
def __enter__(self):
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
self.
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
263
|
-
if exc_type is None:
|
264
|
-
self.cache.store_in_cache(
|
275
|
+
with Profiler('enter_in_cache'):
|
276
|
+
if grading_context.is_no_cache():
|
277
|
+
return False
|
278
|
+
input = _build_cache_input(
|
279
|
+
commands=self.commands,
|
280
|
+
artifact_list=self.artifact_list,
|
281
|
+
extra_params=self.extra_params,
|
282
|
+
cacher=self.cache.cacher,
|
283
|
+
)
|
284
|
+
if VERBOSE:
|
285
|
+
console.console.log(f'Cache input is: {input}')
|
286
|
+
self._key = _build_cache_key(input)
|
287
|
+
if VERBOSE:
|
288
|
+
console.console.log(f'Cache key is: {self._key}')
|
289
|
+
found = self.cache.find_in_cache(
|
265
290
|
self.commands, self.artifact_list, self.extra_params, key=self._key
|
266
291
|
)
|
267
|
-
|
268
|
-
|
269
|
-
|
292
|
+
return found
|
293
|
+
|
294
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
295
|
+
with Profiler('exit_in_cache'):
|
296
|
+
if grading_context.is_no_cache():
|
297
|
+
return True if exc_type is NoCacheException else None
|
298
|
+
if exc_type is None:
|
299
|
+
self.cache.store_in_cache(
|
300
|
+
self.commands, self.artifact_list, self.extra_params, key=self._key
|
301
|
+
)
|
302
|
+
if exc_type is NoCacheException:
|
303
|
+
return True
|
304
|
+
return None
|
270
305
|
|
271
306
|
|
272
307
|
class DependencyCache:
|
273
308
|
root: pathlib.Path
|
274
|
-
|
309
|
+
cacher: FileCacher
|
275
310
|
|
276
|
-
def __init__(self, root: pathlib.Path,
|
311
|
+
def __init__(self, root: pathlib.Path, cacher: FileCacher):
|
277
312
|
self.root = root
|
278
|
-
self.
|
313
|
+
self.cacher = cacher
|
279
314
|
self.db = shelve.open(self._cache_name())
|
315
|
+
tmp_dir = pathlib.Path(tempfile.mkdtemp())
|
316
|
+
self.transient_db = shelve.open(tmp_dir / '.cache_db')
|
280
317
|
atexit.register(lambda: self.db.close())
|
318
|
+
atexit.register(lambda: self.transient_db.close())
|
319
|
+
atexit.register(lambda: shutil.rmtree(tmp_dir))
|
281
320
|
|
282
321
|
def _cache_name(self) -> str:
|
283
322
|
return str(self.root / '.cache_db')
|
284
323
|
|
324
|
+
def get_db(self) -> shelve.Shelf:
|
325
|
+
if grading_context.is_transient():
|
326
|
+
return self.transient_db
|
327
|
+
return self.db
|
328
|
+
|
285
329
|
def _find_in_cache(self, key: str) -> Optional[CacheFingerprint]:
|
286
|
-
return self.
|
330
|
+
return self.get_db().get(key)
|
287
331
|
|
288
332
|
def _store_in_cache(self, key: str, fingerprint: CacheFingerprint):
|
289
|
-
self.
|
333
|
+
self.get_db()[key] = fingerprint
|
290
334
|
|
291
335
|
def _evict_from_cache(self, key: str):
|
292
|
-
|
293
|
-
|
336
|
+
db = self.get_db()
|
337
|
+
if key in db:
|
338
|
+
del db[key]
|
294
339
|
|
295
340
|
def __call__(
|
296
341
|
self,
|
@@ -309,7 +354,10 @@ class DependencyCache:
|
|
309
354
|
key: Optional[str] = None,
|
310
355
|
) -> bool:
|
311
356
|
input = _build_cache_input(
|
312
|
-
commands=commands,
|
357
|
+
commands=commands,
|
358
|
+
artifact_list=artifact_list,
|
359
|
+
extra_params=extra_params,
|
360
|
+
cacher=self.cacher,
|
313
361
|
)
|
314
362
|
key = key or _build_cache_key(input)
|
315
363
|
|
@@ -317,7 +365,7 @@ class DependencyCache:
|
|
317
365
|
if fingerprint is None:
|
318
366
|
return False
|
319
367
|
|
320
|
-
reference_fingerprint = _build_cache_fingerprint(artifact_list)
|
368
|
+
reference_fingerprint = _build_cache_fingerprint(artifact_list, self.cacher)
|
321
369
|
|
322
370
|
if not _fingerprints_match(fingerprint, reference_fingerprint):
|
323
371
|
self._evict_from_cache(key)
|
@@ -334,7 +382,7 @@ class DependencyCache:
|
|
334
382
|
for digest, reference_digest in zip(fingerprint.digests, reference_digests):
|
335
383
|
reference_digest.value = digest
|
336
384
|
|
337
|
-
if not are_artifacts_ok(artifact_list, self.
|
385
|
+
if not are_artifacts_ok(artifact_list, self.cacher):
|
338
386
|
# Rollback digest changes.
|
339
387
|
for old_digest_value, reference_digest in zip(
|
340
388
|
old_digest_values, reference_digests
|
@@ -344,7 +392,7 @@ class DependencyCache:
|
|
344
392
|
return False
|
345
393
|
|
346
394
|
# Copy hashed files to file system.
|
347
|
-
_copy_hashed_files(artifact_list, self.
|
395
|
+
_copy_hashed_files(artifact_list, self.cacher)
|
348
396
|
|
349
397
|
# Apply logs changes.
|
350
398
|
for logs, reference_logs in zip(fingerprint.logs, reference_fingerprint.logs):
|
@@ -366,11 +414,14 @@ class DependencyCache:
|
|
366
414
|
key: Optional[str] = None,
|
367
415
|
):
|
368
416
|
input = _build_cache_input(
|
369
|
-
commands=commands,
|
417
|
+
commands=commands,
|
418
|
+
artifact_list=artifact_list,
|
419
|
+
extra_params=extra_params,
|
420
|
+
cacher=self.cacher,
|
370
421
|
)
|
371
422
|
key = key or _build_cache_key(input)
|
372
423
|
|
373
|
-
if not are_artifacts_ok(artifact_list, self.
|
424
|
+
if not are_artifacts_ok(artifact_list, self.cacher):
|
374
425
|
return
|
375
426
|
|
376
|
-
self._store_in_cache(key, _build_cache_fingerprint(artifact_list))
|
427
|
+
self._store_in_cache(key, _build_cache_fingerprint(artifact_list, self.cacher))
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import contextvars
|
2
|
+
import dataclasses
|
3
|
+
from dataclasses import dataclass
|
4
|
+
|
5
|
+
|
6
|
+
@dataclass(frozen=True)
|
7
|
+
class DebugContext:
|
8
|
+
enable: bool = False
|
9
|
+
|
10
|
+
|
11
|
+
debug_var = contextvars.ContextVar('debug', default=DebugContext())
|
12
|
+
|
13
|
+
|
14
|
+
def get_debug_context() -> DebugContext:
|
15
|
+
return debug_var.get()
|
16
|
+
|
17
|
+
|
18
|
+
class Debug:
|
19
|
+
def __init__(self, *args, **kwargs):
|
20
|
+
self.args = args
|
21
|
+
self.kwargs = kwargs
|
22
|
+
self.token = None
|
23
|
+
|
24
|
+
def __enter__(self):
|
25
|
+
self.token = debug_var.set(
|
26
|
+
dataclasses.replace(debug_var.get(), *self.args, **self.kwargs)
|
27
|
+
)
|
28
|
+
|
29
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
30
|
+
if self.token is not None:
|
31
|
+
debug_var.reset(self.token)
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import contextvars
|
2
|
+
from enum import Enum
|
3
|
+
from typing import Callable, Optional, Union
|
4
|
+
|
5
|
+
Condition = Union[bool, Callable[[], bool]]
|
6
|
+
|
7
|
+
|
8
|
+
class ConditionedContext:
|
9
|
+
def __init__(self, when: Condition = True):
|
10
|
+
self.when = when
|
11
|
+
|
12
|
+
def should_enter(self) -> bool:
|
13
|
+
if isinstance(self.when, bool):
|
14
|
+
return self.when
|
15
|
+
return self.when()
|
16
|
+
|
17
|
+
|
18
|
+
class CacheLevel(Enum):
|
19
|
+
NO_CACHE = 0
|
20
|
+
CACHE_TRANSIENTLY = 1
|
21
|
+
CACHE_COMPILATION = 2
|
22
|
+
CACHE_ALL = 3
|
23
|
+
|
24
|
+
|
25
|
+
cache_level_var = contextvars.ContextVar('cache_level', default=CacheLevel.CACHE_ALL)
|
26
|
+
|
27
|
+
|
28
|
+
def is_compilation_only() -> bool:
|
29
|
+
return cache_level_var.get() == CacheLevel.CACHE_COMPILATION
|
30
|
+
|
31
|
+
|
32
|
+
def is_transient() -> bool:
|
33
|
+
return cache_level_var.get().value <= CacheLevel.CACHE_TRANSIENTLY.value
|
34
|
+
|
35
|
+
|
36
|
+
def is_no_cache() -> bool:
|
37
|
+
return cache_level_var.get().value <= CacheLevel.NO_CACHE.value
|
38
|
+
|
39
|
+
|
40
|
+
class cache_level(ConditionedContext):
|
41
|
+
def __init__(self, level: CacheLevel, when: Condition = True):
|
42
|
+
super().__init__(when)
|
43
|
+
self.level = level
|
44
|
+
self.token = None
|
45
|
+
|
46
|
+
def __enter__(self):
|
47
|
+
if self.should_enter():
|
48
|
+
self.token = cache_level_var.set(self.level)
|
49
|
+
return self
|
50
|
+
|
51
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
52
|
+
if self.token is not None:
|
53
|
+
cache_level_var.reset(self.token)
|
54
|
+
return None
|
55
|
+
|
56
|
+
|
57
|
+
compression_level_var = contextvars.ContextVar('compression_level', default=5)
|
58
|
+
use_compression_var = contextvars.ContextVar('use_compression', default=False)
|
59
|
+
|
60
|
+
|
61
|
+
def get_compression_level() -> int:
|
62
|
+
return compression_level_var.get()
|
63
|
+
|
64
|
+
|
65
|
+
def should_compress() -> bool:
|
66
|
+
return use_compression_var.get()
|
67
|
+
|
68
|
+
|
69
|
+
class compression(ConditionedContext):
|
70
|
+
def __init__(
|
71
|
+
self,
|
72
|
+
level: Optional[int] = None,
|
73
|
+
use_compression: Optional[bool] = None,
|
74
|
+
when: Condition = True,
|
75
|
+
):
|
76
|
+
super().__init__(when)
|
77
|
+
self.level = level
|
78
|
+
self.use_compression = use_compression
|
79
|
+
self.level_token = None
|
80
|
+
self.use_compression_token = None
|
81
|
+
|
82
|
+
def __enter__(self):
|
83
|
+
if not self.should_enter():
|
84
|
+
return self
|
85
|
+
if self.level is not None:
|
86
|
+
self.level_token = compression_level_var.set(self.level)
|
87
|
+
if self.use_compression is not None:
|
88
|
+
self.use_compression_token = use_compression_var.set(self.use_compression)
|
89
|
+
return self
|
90
|
+
|
91
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
92
|
+
if self.level_token is not None:
|
93
|
+
compression_level_var.reset(self.level_token)
|
94
|
+
if self.use_compression_token is not None:
|
95
|
+
use_compression_var.reset(self.use_compression_token)
|
96
|
+
return None
|