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/cd.py
CHANGED
@@ -5,7 +5,7 @@ from typing import List, Optional
|
|
5
5
|
|
6
6
|
import typer
|
7
7
|
|
8
|
-
from rbx import console
|
8
|
+
from rbx import console, utils
|
9
9
|
from rbx.box.sanitizers import warning_stack
|
10
10
|
from rbx.utils import new_cd
|
11
11
|
|
@@ -13,7 +13,7 @@ from rbx.utils import new_cd
|
|
13
13
|
def find_package(
|
14
14
|
root: pathlib.Path = pathlib.Path(), consider_presets: bool = False
|
15
15
|
) -> Optional[pathlib.Path]:
|
16
|
-
root =
|
16
|
+
root = utils.abspath(root)
|
17
17
|
|
18
18
|
def has_file():
|
19
19
|
problem_yaml_path = root / 'problem.rbx.yml'
|
rbx/box/cli.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import atexit
|
1
2
|
import pathlib
|
2
3
|
import shlex
|
3
4
|
import shutil
|
@@ -9,6 +10,7 @@ import rich
|
|
9
10
|
import rich.prompt
|
10
11
|
import syncer
|
11
12
|
import typer
|
13
|
+
from ordered_set import OrderedSet
|
12
14
|
|
13
15
|
from rbx import annotations, config, console, utils
|
14
16
|
from rbx.box import (
|
@@ -18,6 +20,7 @@ from rbx.box import (
|
|
18
20
|
download,
|
19
21
|
environment,
|
20
22
|
generators,
|
23
|
+
global_package,
|
21
24
|
package,
|
22
25
|
presets,
|
23
26
|
setter_config,
|
@@ -43,6 +46,7 @@ from rbx.box.statements import build_statements
|
|
43
46
|
from rbx.box.testcase_utils import TestcaseEntry
|
44
47
|
from rbx.box.testcases import main as testcases
|
45
48
|
from rbx.box.tooling import main as tooling
|
49
|
+
from rbx.grading import grading_context
|
46
50
|
|
47
51
|
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
48
52
|
app.add_typer(
|
@@ -105,6 +109,15 @@ app.add_typer(
|
|
105
109
|
|
106
110
|
@app.callback()
|
107
111
|
def main(
|
112
|
+
cache: Annotated[
|
113
|
+
int,
|
114
|
+
typer.Option(
|
115
|
+
'-c',
|
116
|
+
'--cache',
|
117
|
+
help='Which degree of caching to use.',
|
118
|
+
default_factory=lambda: grading_context.CacheLevel.CACHE_ALL.value,
|
119
|
+
),
|
120
|
+
],
|
108
121
|
sanitized: bool = typer.Option(
|
109
122
|
False,
|
110
123
|
'--sanitized',
|
@@ -118,12 +131,23 @@ def main(
|
|
118
131
|
flag_value=False,
|
119
132
|
help='Whether to save extra logs and outputs from interactive solutions.',
|
120
133
|
),
|
134
|
+
profile: bool = typer.Option(
|
135
|
+
False,
|
136
|
+
'--profile',
|
137
|
+
'-p',
|
138
|
+
help='Whether to profile the execution.',
|
139
|
+
),
|
121
140
|
):
|
122
141
|
if cd.is_problem_package() and not package.is_cache_valid():
|
123
142
|
console.console.print(
|
124
143
|
'[warning]Cache is incompatible with the current version of [item]rbx[/item], so it will be cleared.[/warning]'
|
125
144
|
)
|
126
145
|
clear()
|
146
|
+
if not global_package.is_global_cache_valid():
|
147
|
+
console.console.print(
|
148
|
+
'[warning]Global cache is incompatible with the current version of [item]rbx[/item], so it will be cleared.[/warning]'
|
149
|
+
)
|
150
|
+
clear(global_cache=True)
|
127
151
|
|
128
152
|
state.STATE.run_through_cli = True
|
129
153
|
state.STATE.sanitized = sanitized
|
@@ -134,6 +158,13 @@ def main(
|
|
134
158
|
)
|
135
159
|
state.STATE.debug_logs = capture
|
136
160
|
|
161
|
+
grading_context.cache_level_var.set(grading_context.CacheLevel(cache))
|
162
|
+
|
163
|
+
if profile:
|
164
|
+
from rbx.grading import profiling
|
165
|
+
|
166
|
+
atexit.register(profiling.print_summary)
|
167
|
+
|
137
168
|
|
138
169
|
@app.command('ui', hidden=True)
|
139
170
|
@package.within_problem
|
@@ -252,17 +283,17 @@ async def run(
|
|
252
283
|
)
|
253
284
|
check = False
|
254
285
|
|
255
|
-
tracked_solutions = None
|
286
|
+
tracked_solutions: Optional[OrderedSet[str]] = None
|
256
287
|
if outcome is not None:
|
257
|
-
tracked_solutions =
|
288
|
+
tracked_solutions = OrderedSet(
|
258
289
|
str(solution.path)
|
259
290
|
for solution in get_matching_solutions(ExpectedOutcome(outcome))
|
260
|
-
|
291
|
+
)
|
261
292
|
if solutions:
|
262
|
-
tracked_solutions =
|
293
|
+
tracked_solutions = OrderedSet(solutions)
|
263
294
|
|
264
295
|
if choice:
|
265
|
-
tracked_solutions =
|
296
|
+
tracked_solutions = OrderedSet(
|
266
297
|
await pick_solutions(
|
267
298
|
tracked_solutions,
|
268
299
|
extra_solutions=solutions,
|
@@ -307,10 +338,10 @@ async def run(
|
|
307
338
|
console.console.print(
|
308
339
|
'[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
|
309
340
|
)
|
310
|
-
tracked_solutions =
|
341
|
+
tracked_solutions = OrderedSet(
|
311
342
|
str(solution.path)
|
312
343
|
for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
|
313
|
-
|
344
|
+
)
|
314
345
|
|
315
346
|
with utils.StatusProgress('Running solutions...') as s:
|
316
347
|
solution_result = run_solutions(
|
@@ -343,10 +374,10 @@ async def _time_impl(check: bool, detailed: bool, runs: int = 0) -> Optional[int
|
|
343
374
|
verification = VerificationLevel.ALL_SOLUTIONS.value
|
344
375
|
|
345
376
|
with utils.StatusProgress('Running ACCEPTED solutions...') as s:
|
346
|
-
tracked_solutions =
|
377
|
+
tracked_solutions = OrderedSet(
|
347
378
|
str(solution.path)
|
348
379
|
for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
|
349
|
-
|
380
|
+
)
|
350
381
|
solution_result = run_solutions(
|
351
382
|
progress=s,
|
352
383
|
tracked_solutions=tracked_solutions,
|
@@ -495,17 +526,17 @@ async def irun(
|
|
495
526
|
)
|
496
527
|
return
|
497
528
|
|
498
|
-
tracked_solutions = None
|
529
|
+
tracked_solutions: Optional[OrderedSet[str]] = None
|
499
530
|
if outcome is not None:
|
500
|
-
tracked_solutions =
|
531
|
+
tracked_solutions = OrderedSet(
|
501
532
|
str(solution.path)
|
502
533
|
for solution in get_matching_solutions(ExpectedOutcome(outcome))
|
503
|
-
|
534
|
+
)
|
504
535
|
if solutions:
|
505
|
-
tracked_solutions =
|
536
|
+
tracked_solutions = OrderedSet(solutions)
|
506
537
|
|
507
538
|
if choice:
|
508
|
-
tracked_solutions =
|
539
|
+
tracked_solutions = OrderedSet(
|
509
540
|
await pick_solutions(
|
510
541
|
tracked_solutions,
|
511
542
|
extra_solutions=solutions,
|
@@ -519,10 +550,10 @@ async def irun(
|
|
519
550
|
console.console.print(
|
520
551
|
'[warning]Sanitizers are running, and no solutions were specified to run. Will only run [item]ACCEPTED[/item] solutions.'
|
521
552
|
)
|
522
|
-
tracked_solutions =
|
553
|
+
tracked_solutions = OrderedSet(
|
523
554
|
str(solution.path)
|
524
555
|
for solution in get_exact_matching_solutions(ExpectedOutcome.ACCEPTED)
|
525
|
-
|
556
|
+
)
|
526
557
|
|
527
558
|
with utils.StatusProgress('Running solutions...') as s:
|
528
559
|
await run_and_print_interactive_solutions(
|
@@ -546,7 +577,13 @@ async def irun(
|
|
546
577
|
help='Create a new problem package.',
|
547
578
|
)
|
548
579
|
def create(
|
549
|
-
name:
|
580
|
+
name: Annotated[
|
581
|
+
str,
|
582
|
+
typer.Option(
|
583
|
+
help='Name of the problem to create, which will be used as the name of the new folder.',
|
584
|
+
prompt='What should the name of the problem be?',
|
585
|
+
),
|
586
|
+
],
|
550
587
|
preset: Annotated[
|
551
588
|
Optional[str], typer.Option(help='Preset to use when creating the problem.')
|
552
589
|
] = None,
|
@@ -631,16 +668,17 @@ async def stress(
|
|
631
668
|
from rbx.box import stresses
|
632
669
|
|
633
670
|
with utils.StatusProgress('Running stress...') as s:
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
671
|
+
with grading_context.cache_level(grading_context.CacheLevel.CACHE_COMPILATION):
|
672
|
+
report = await stresses.run_stress(
|
673
|
+
timeout,
|
674
|
+
name=name,
|
675
|
+
generator_call=generator_args,
|
676
|
+
finder=finder,
|
677
|
+
findingsLimit=findings,
|
678
|
+
progress=s,
|
679
|
+
verbose=verbose,
|
680
|
+
sanitized=sanitized,
|
681
|
+
)
|
644
682
|
|
645
683
|
stresses.print_stress_report(report)
|
646
684
|
|
@@ -882,7 +920,7 @@ def languages():
|
|
882
920
|
|
883
921
|
for language in env.languages:
|
884
922
|
console.console.print(
|
885
|
-
f'[item]{language.name}[/item], aka [item]{language.
|
923
|
+
f'[item]{language.name}[/item], aka [item]{language.readableName or language.name}[/item]:'
|
886
924
|
)
|
887
925
|
console.console.print(language)
|
888
926
|
console.console.print()
|
@@ -922,13 +960,29 @@ def fix(print_diff: bool = typer.Option(False, '--print-diff', '-p')):
|
|
922
960
|
linting.fix_package(print_diff=print_diff)
|
923
961
|
|
924
962
|
|
963
|
+
@cd.within_closest_package
|
964
|
+
def _clear_package_cache():
|
965
|
+
console.console.print('Cleaning cache and build directories...')
|
966
|
+
shutil.rmtree('.box', ignore_errors=True)
|
967
|
+
shutil.rmtree('build', ignore_errors=True)
|
968
|
+
|
969
|
+
|
925
970
|
@app.command(
|
926
971
|
'clear, clean',
|
927
972
|
rich_help_panel='Management',
|
928
973
|
help='Clears cache and build directories.',
|
929
974
|
)
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
975
|
+
def clear(global_cache: bool = typer.Option(False, '--global', '-g')):
|
976
|
+
cleared = False
|
977
|
+
if global_cache:
|
978
|
+
console.console.print('Cleaning global cache...')
|
979
|
+
global_package.clear_global_cache()
|
980
|
+
cleared = True
|
981
|
+
|
982
|
+
closest_package = cd.find_package()
|
983
|
+
if closest_package is not None:
|
984
|
+
_clear_package_cache()
|
985
|
+
cleared = True
|
986
|
+
|
987
|
+
if not cleared:
|
988
|
+
console.console.print('[error]No cache or build directories to clean.[/error]')
|
rbx/box/code.py
CHANGED
@@ -10,9 +10,10 @@ from typing import List, Optional
|
|
10
10
|
import rich
|
11
11
|
import rich.text
|
12
12
|
import typer
|
13
|
+
from pydantic import BaseModel
|
13
14
|
|
14
|
-
from rbx import console
|
15
|
-
from rbx.box import download, package, setter_config, state
|
15
|
+
from rbx import console, utils
|
16
|
+
from rbx.box import download, global_package, package, setter_config, state
|
16
17
|
from rbx.box.environment import (
|
17
18
|
CompilationConfig,
|
18
19
|
ExecutionConfig,
|
@@ -27,10 +28,11 @@ from rbx.box.environment import (
|
|
27
28
|
merge_execution_configs,
|
28
29
|
)
|
29
30
|
from rbx.box.formatting import get_formatted_memory
|
31
|
+
from rbx.box.remote import is_path_remote
|
30
32
|
from rbx.box.sanitizers import warning_stack
|
31
33
|
from rbx.box.schema import CodeItem
|
32
|
-
from rbx.grading import steps, steps_with_caching
|
33
|
-
from rbx.grading.judge.sandbox import
|
34
|
+
from rbx.grading import grading_context, profiling, steps, steps_with_caching
|
35
|
+
from rbx.grading.judge.sandbox import SandboxParams
|
34
36
|
from rbx.grading.steps import (
|
35
37
|
DigestHolder,
|
36
38
|
DigestOrDest,
|
@@ -59,6 +61,10 @@ class SanitizationLevel(Enum):
|
|
59
61
|
return self.value >= SanitizationLevel.FORCE.value
|
60
62
|
|
61
63
|
|
64
|
+
class CompilationMetadata(BaseModel):
|
65
|
+
is_sanitized: bool
|
66
|
+
|
67
|
+
|
62
68
|
def substitute_commands(commands: List[str], sanitized: bool = False) -> List[str]:
|
63
69
|
cfg = setter_config.get_setter_config()
|
64
70
|
return [cfg.substitute_command(command, sanitized) for command in commands]
|
@@ -78,8 +84,15 @@ def find_language_name(code: CodeItem) -> str:
|
|
78
84
|
def is_executable_sanitized(executable: DigestOrSource) -> bool:
|
79
85
|
if executable.digest is None:
|
80
86
|
return False
|
81
|
-
|
82
|
-
|
87
|
+
if executable.digest.value is None:
|
88
|
+
return False
|
89
|
+
cacher = package.get_file_cacher()
|
90
|
+
desc = cacher.get_metadata(
|
91
|
+
executable.digest.value, 'compilation', CompilationMetadata
|
92
|
+
)
|
93
|
+
if desc is None:
|
94
|
+
return False
|
95
|
+
return desc.is_sanitized
|
83
96
|
|
84
97
|
|
85
98
|
def add_sanitizer_flags_to_command(command: str) -> str:
|
@@ -283,7 +296,7 @@ def _prepare_for_communication(
|
|
283
296
|
)
|
284
297
|
|
285
298
|
if capture.merged_capture is not None:
|
286
|
-
merged_output_path = package.get_merged_capture_path()
|
299
|
+
merged_output_path = utils.abspath(package.get_merged_capture_path())
|
287
300
|
run.sandbox_params.timeit_dups['Do'].append(merged_output_path)
|
288
301
|
|
289
302
|
run.artifacts.outputs.append(
|
@@ -402,7 +415,6 @@ def _should_precompile(commands: List[str]) -> bool:
|
|
402
415
|
def _precompile_header(
|
403
416
|
compilation_options: CompilationConfig,
|
404
417
|
sanitized: SanitizationLevel,
|
405
|
-
sandbox: SandboxBase,
|
406
418
|
sandbox_params: SandboxParams,
|
407
419
|
artifacts: GradingArtifacts,
|
408
420
|
input_artifact: GradingFileInput,
|
@@ -417,7 +429,8 @@ def _precompile_header(
|
|
417
429
|
"""
|
418
430
|
assert compilation_options.commands is not None
|
419
431
|
|
420
|
-
|
432
|
+
sandbox = global_package.get_global_sandbox()
|
433
|
+
dependency_cache = global_package.get_global_dependency_cache()
|
421
434
|
|
422
435
|
# TODO: deduplicate code with compile_item.
|
423
436
|
commands = get_mapped_commands(
|
@@ -455,41 +468,60 @@ def _precompile_header(
|
|
455
468
|
GradingFileOutput(
|
456
469
|
src=PosixPath('precompilable.h.gch'),
|
457
470
|
digest=precompiled_digest,
|
458
|
-
executable=True,
|
459
471
|
)
|
460
472
|
)
|
461
473
|
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
474
|
+
with profiling.PushContext('code.precompile_header'):
|
475
|
+
if not steps_with_caching.compile(
|
476
|
+
commands,
|
477
|
+
params=sandbox_params,
|
478
|
+
artifacts=precompilation_artifacts,
|
479
|
+
sandbox=sandbox,
|
480
|
+
dependency_cache=dependency_cache,
|
481
|
+
):
|
482
|
+
console.console.print(
|
483
|
+
f'[error]Failed to precompile header file: [item]{input_artifact.src}[/item][/error]'
|
484
|
+
)
|
485
|
+
raise typer.Exit(1)
|
473
486
|
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
487
|
+
if verbose:
|
488
|
+
console.console.print(
|
489
|
+
f'[status]Precompiled header file: [item]{input_artifact.src}[/item]'
|
490
|
+
)
|
478
491
|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
492
|
+
if (
|
493
|
+
precompilation_artifacts.logs is not None
|
494
|
+
and precompilation_artifacts.logs.preprocess is not None
|
495
|
+
):
|
496
|
+
for log in precompilation_artifacts.logs.preprocess:
|
497
|
+
console.console.print(
|
498
|
+
f'[status]Command:[/status] {log.get_command()}'
|
499
|
+
)
|
500
|
+
console.console.print(
|
501
|
+
f'[status]Summary:[/status] {log.get_summary()}'
|
502
|
+
)
|
486
503
|
|
487
504
|
assert precompiled_digest.value is not None
|
488
505
|
|
506
|
+
digest_path = dependency_cache.cacher.path_for_symlink(precompiled_digest.value)
|
507
|
+
if digest_path is not None and digest_path.is_file():
|
508
|
+
# If storage backend supports symlinks, use it as the grading input.
|
509
|
+
input = DigestOrSource.create(digest_path)
|
510
|
+
else:
|
511
|
+
# Otherwise, copy the file to the local cache, transiently.
|
512
|
+
local_cacher = package.get_file_cacher()
|
513
|
+
with dependency_cache.cacher.get_file(precompiled_digest.value) as f:
|
514
|
+
with grading_context.cache_level(
|
515
|
+
grading_context.CacheLevel.CACHE_TRANSIENTLY
|
516
|
+
):
|
517
|
+
input = DigestOrSource.create(local_cacher.put_file_from_fobj(f))
|
518
|
+
|
489
519
|
return GradingFileInput(
|
490
|
-
|
520
|
+
**input.expand(),
|
491
521
|
dest=input_artifact.dest.with_suffix('.h.gch'),
|
492
|
-
|
522
|
+
# Do not track fingerprint of the precompiled header file,
|
523
|
+
# trust the compilation step above.
|
524
|
+
hash=False,
|
493
525
|
)
|
494
526
|
|
495
527
|
|
@@ -576,37 +608,43 @@ def compile_item(
|
|
576
608
|
|
577
609
|
# Precompile C++ interesting header files.
|
578
610
|
if precompile and _should_precompile(commands):
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
611
|
+
with profiling.Profiler('code.precompile'):
|
612
|
+
precompilation_inputs = []
|
613
|
+
for input in artifacts.inputs:
|
614
|
+
if (
|
615
|
+
input.src is not None
|
616
|
+
and input.src.suffix == '.h'
|
617
|
+
and input.dest.name in ['stdc++.h', 'jngen.h', 'testlib.h']
|
618
|
+
):
|
619
|
+
precompilation_inputs.append(
|
620
|
+
_precompile_header(
|
621
|
+
compilation_options,
|
622
|
+
sanitized,
|
623
|
+
sandbox_params,
|
624
|
+
artifacts,
|
625
|
+
input,
|
626
|
+
force_warnings,
|
627
|
+
verbose=False,
|
628
|
+
)
|
596
629
|
)
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
630
|
+
if precompilation_inputs:
|
631
|
+
artifacts.inputs.extend(precompilation_inputs)
|
632
|
+
|
633
|
+
with profiling.Profiler('code.compile'):
|
634
|
+
# Compile the code.
|
635
|
+
# Do not cache remote solutions.
|
636
|
+
with grading_context.cache_level(
|
637
|
+
grading_context.CacheLevel.NO_CACHE,
|
638
|
+
when=lambda: is_path_remote(code.path),
|
639
|
+
):
|
640
|
+
if not steps_with_caching.compile(
|
641
|
+
commands,
|
642
|
+
params=sandbox_params,
|
643
|
+
artifacts=artifacts,
|
644
|
+
sandbox=sandbox,
|
645
|
+
dependency_cache=dependency_cache,
|
646
|
+
):
|
647
|
+
raise typer.Exit(1)
|
610
648
|
|
611
649
|
assert compiled_digest.value is not None
|
612
650
|
|
@@ -629,13 +667,13 @@ def compile_item(
|
|
629
667
|
warning_stack.get_warning_stack().add_warning(code)
|
630
668
|
|
631
669
|
# Create sentinel to indicate this executable is sanitized.
|
632
|
-
|
670
|
+
cacher = package.get_file_cacher()
|
633
671
|
if sanitized.should_sanitize():
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
672
|
+
cacher.set_metadata(
|
673
|
+
compiled_digest.value, 'compilation', CompilationMetadata(is_sanitized=True)
|
674
|
+
)
|
675
|
+
else:
|
676
|
+
cacher.set_metadata(compiled_digest.value, 'compilation', None)
|
639
677
|
|
640
678
|
return compiled_digest.value
|
641
679
|
|
@@ -669,14 +707,20 @@ async def run_item(
|
|
669
707
|
retry_index,
|
670
708
|
)
|
671
709
|
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
710
|
+
with profiling.PushContext('code.run_item'):
|
711
|
+
# Do not cache remote solutions.
|
712
|
+
with grading_context.cache_level(
|
713
|
+
grading_context.CacheLevel.NO_CACHE,
|
714
|
+
when=lambda: is_path_remote(code.path),
|
715
|
+
):
|
716
|
+
run_log = await steps_with_caching.run(
|
717
|
+
prepared.command,
|
718
|
+
params=prepared.sandbox_params,
|
719
|
+
sandbox=package.get_singleton_sandbox(),
|
720
|
+
artifacts=prepared.artifacts,
|
721
|
+
dependency_cache=dependency_cache,
|
722
|
+
metadata=prepared.metadata,
|
723
|
+
)
|
680
724
|
|
681
725
|
# Find sanitizer logs.
|
682
726
|
if run_log is not None and run_log.warnings:
|
@@ -686,7 +730,7 @@ async def run_item(
|
|
686
730
|
)
|
687
731
|
if stderr_output is not None:
|
688
732
|
warning_stack.get_warning_stack().add_sanitizer_warning(
|
689
|
-
package.
|
733
|
+
package.get_file_cacher(), code, stderr_output
|
690
734
|
)
|
691
735
|
return run_log
|
692
736
|
|
@@ -773,8 +817,13 @@ async def run_communication(
|
|
773
817
|
metadata=solution_prepared.metadata,
|
774
818
|
)
|
775
819
|
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
)
|
820
|
+
# Do not cache remote solutions.
|
821
|
+
with grading_context.cache_level(
|
822
|
+
grading_context.CacheLevel.NO_CACHE,
|
823
|
+
when=lambda: is_path_remote(solution.code.path),
|
824
|
+
):
|
825
|
+
return await steps_with_caching.run_coordinated(
|
826
|
+
interactor_run_params,
|
827
|
+
solution_run_params,
|
828
|
+
dependency_cache=package.get_dependency_cache(),
|
829
|
+
)
|
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
6
6
|
|
7
7
|
import typer
|
8
8
|
|
9
|
-
from rbx import console, testing_utils
|
9
|
+
from rbx import console, testing_utils, utils
|
10
10
|
from rbx.box import cd, package
|
11
11
|
from rbx.box.contest.contest_package import get_problems
|
12
12
|
from rbx.box.contest.schema import Contest, ContestProblem, ContestStatement
|
@@ -156,7 +156,7 @@ def _build_problem_statements(
|
|
156
156
|
console.console.print('Building problem-level statements...')
|
157
157
|
extracted_problems = get_problems_for_statement(contest, statement)
|
158
158
|
res = []
|
159
|
-
contest_cwd_absolute = pathlib.Path()
|
159
|
+
contest_cwd_absolute = utils.abspath(pathlib.Path())
|
160
160
|
contest_assets = get_relative_assets(statement.path, statement.assets)
|
161
161
|
|
162
162
|
extra_vars = dict(statement.override.vars if statement.override is not None else {})
|
@@ -17,7 +17,7 @@ YAML_NAME = 'contest.rbx.yml'
|
|
17
17
|
|
18
18
|
@functools.cache
|
19
19
|
def find_contest_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
|
20
|
-
root =
|
20
|
+
root = utils.abspath(root)
|
21
21
|
contest_yaml_path = root / YAML_NAME
|
22
22
|
while root != pathlib.PosixPath('/') and not contest_yaml_path.is_file():
|
23
23
|
root = root.parent
|
rbx/box/contest/main.py
CHANGED
@@ -38,7 +38,13 @@ app.add_typer(
|
|
38
38
|
|
39
39
|
@app.command('create, c', help='Create a new contest package.')
|
40
40
|
def create(
|
41
|
-
path:
|
41
|
+
path: Annotated[
|
42
|
+
str,
|
43
|
+
typer.Option(
|
44
|
+
help='Path where to create the contest.',
|
45
|
+
prompt='Where should the contest be created, relative to the current directory? (e.g. "contests/ioi2024")',
|
46
|
+
),
|
47
|
+
],
|
42
48
|
preset: Annotated[
|
43
49
|
Optional[str],
|
44
50
|
typer.Option(
|
@@ -108,7 +114,28 @@ def edit():
|
|
108
114
|
|
109
115
|
@app.command('add, a', help='Add new problem to contest.')
|
110
116
|
@within_contest
|
111
|
-
def add(
|
117
|
+
def add(
|
118
|
+
path: Annotated[
|
119
|
+
str,
|
120
|
+
typer.Option(
|
121
|
+
help='Path where to create the problem. Name part of the path will be used as the problem name.',
|
122
|
+
prompt='Where should the problem be created, relative to the contest root? (e.g. problems/choco will create a problem named "choco" in this directory)',
|
123
|
+
),
|
124
|
+
],
|
125
|
+
short_name: Annotated[
|
126
|
+
str,
|
127
|
+
typer.Option(
|
128
|
+
help='Short name of the problem. Will be used as the identifier in the contest.',
|
129
|
+
prompt='What should the problem be named? (e.g. "A", "B1", "B2", "Z")',
|
130
|
+
),
|
131
|
+
],
|
132
|
+
preset: Annotated[
|
133
|
+
Optional[str],
|
134
|
+
typer.Option(
|
135
|
+
help='Preset to use when creating the problem. If not specified, the active preset will be used.',
|
136
|
+
),
|
137
|
+
] = None,
|
138
|
+
):
|
112
139
|
problem_path = pathlib.Path(path)
|
113
140
|
name = problem_path.stem
|
114
141
|
utils.validate_field(ContestProblem, 'short_name', short_name)
|