rbx.cp 0.5.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/__init__.py +0 -0
- rbx/annotations.py +127 -0
- rbx/autoenum.py +333 -0
- rbx/box/__init__.py +0 -0
- rbx/box/builder.py +77 -0
- rbx/box/cd.py +37 -0
- rbx/box/checkers.py +134 -0
- rbx/box/code.py +185 -0
- rbx/box/compile.py +56 -0
- rbx/box/conftest.py +42 -0
- rbx/box/contest/__init__.py +0 -0
- rbx/box/contest/build_contest_statements.py +347 -0
- rbx/box/contest/contest_package.py +76 -0
- rbx/box/contest/contest_utils.py +20 -0
- rbx/box/contest/main.py +179 -0
- rbx/box/contest/schema.py +155 -0
- rbx/box/contest/statements.py +82 -0
- rbx/box/creation.py +72 -0
- rbx/box/download.py +64 -0
- rbx/box/environment.py +345 -0
- rbx/box/extensions.py +26 -0
- rbx/box/generators.py +478 -0
- rbx/box/generators_test.py +63 -0
- rbx/box/main.py +449 -0
- rbx/box/package.py +316 -0
- rbx/box/packaging/boca/extension.py +27 -0
- rbx/box/packaging/boca/packager.py +245 -0
- rbx/box/packaging/contest_main.py +82 -0
- rbx/box/packaging/main.py +68 -0
- rbx/box/packaging/packager.py +117 -0
- rbx/box/packaging/polygon/packager.py +320 -0
- rbx/box/packaging/polygon/test.py +81 -0
- rbx/box/packaging/polygon/xml_schema.py +106 -0
- rbx/box/presets/__init__.py +503 -0
- rbx/box/presets/fetch.py +70 -0
- rbx/box/presets/lock_schema.py +20 -0
- rbx/box/presets/schema.py +59 -0
- rbx/box/schema.py +394 -0
- rbx/box/solutions.py +792 -0
- rbx/box/solutions_test.py +41 -0
- rbx/box/statements/__init__.py +0 -0
- rbx/box/statements/build_statements.py +359 -0
- rbx/box/statements/builders.py +375 -0
- rbx/box/statements/joiners.py +113 -0
- rbx/box/statements/latex.py +47 -0
- rbx/box/statements/latex_jinja.py +214 -0
- rbx/box/statements/schema.py +138 -0
- rbx/box/stresses.py +292 -0
- rbx/box/stressing/__init__.py +0 -0
- rbx/box/stressing/finder_parser.py +359 -0
- rbx/box/stressing/generator_parser.py +258 -0
- rbx/box/testcases.py +54 -0
- rbx/box/ui/__init__.py +0 -0
- rbx/box/ui/captured_log.py +372 -0
- rbx/box/ui/css/app.tcss +48 -0
- rbx/box/ui/main.py +38 -0
- rbx/box/ui/run.py +209 -0
- rbx/box/validators.py +245 -0
- rbx/box/validators_test.py +15 -0
- rbx/checker.py +128 -0
- rbx/clone.py +197 -0
- rbx/config.py +271 -0
- rbx/conftest.py +38 -0
- rbx/console.py +27 -0
- rbx/create.py +37 -0
- rbx/edit.py +24 -0
- rbx/grading/__init__.py +0 -0
- rbx/grading/caching.py +356 -0
- rbx/grading/conftest.py +33 -0
- rbx/grading/judge/__init__.py +0 -0
- rbx/grading/judge/cacher.py +503 -0
- rbx/grading/judge/digester.py +35 -0
- rbx/grading/judge/sandbox.py +748 -0
- rbx/grading/judge/sandboxes/__init__.py +0 -0
- rbx/grading/judge/sandboxes/isolate.py +683 -0
- rbx/grading/judge/sandboxes/stupid_sandbox.py +310 -0
- rbx/grading/judge/sandboxes/timeit.py +217 -0
- rbx/grading/judge/storage.py +284 -0
- rbx/grading/judge/test.py +38 -0
- rbx/grading/judge/testiso.py +54 -0
- rbx/grading/steps.py +522 -0
- rbx/grading/steps_with_caching.py +59 -0
- rbx/grading/steps_with_caching_run_test.py +429 -0
- rbx/grading_utils.py +148 -0
- rbx/hydration.py +101 -0
- rbx/main.py +122 -0
- rbx/metadata.py +105 -0
- rbx/providers/__init__.py +43 -0
- rbx/providers/codeforces.py +73 -0
- rbx/providers/provider.py +26 -0
- rbx/resources/checkers/boilerplate.cpp +20 -0
- rbx/resources/default_config.json +48 -0
- rbx/resources/envs/default.rbx.yml +37 -0
- rbx/resources/envs/isolate.rbx.yml +37 -0
- rbx/resources/packagers/boca/checker.sh +43 -0
- rbx/resources/packagers/boca/compare +53 -0
- rbx/resources/packagers/boca/compile/c +172 -0
- rbx/resources/packagers/boca/compile/cc +173 -0
- rbx/resources/packagers/boca/compile/cpp +172 -0
- rbx/resources/packagers/boca/compile/java +194 -0
- rbx/resources/packagers/boca/compile/kt +155 -0
- rbx/resources/packagers/boca/compile/pas +172 -0
- rbx/resources/packagers/boca/compile/py2 +173 -0
- rbx/resources/packagers/boca/compile/py3 +173 -0
- rbx/resources/packagers/boca/run/c +128 -0
- rbx/resources/packagers/boca/run/cc +128 -0
- rbx/resources/packagers/boca/run/cpp +128 -0
- rbx/resources/packagers/boca/run/java +194 -0
- rbx/resources/packagers/boca/run/kt +159 -0
- rbx/resources/packagers/boca/run/py2 +166 -0
- rbx/resources/packagers/boca/run/py3 +166 -0
- rbx/resources/presets/default/contest/contest.rbx.yml +14 -0
- rbx/resources/presets/default/contest/statement/contest.rbx.tex +97 -0
- rbx/resources/presets/default/contest/statement/olymp.sty +250 -0
- rbx/resources/presets/default/contest/statement/template.rbx.tex +42 -0
- rbx/resources/presets/default/preset.rbx.yml +12 -0
- rbx/resources/presets/default/problem/.gitignore +6 -0
- rbx/resources/presets/default/problem/gen.cpp +9 -0
- rbx/resources/presets/default/problem/problem.rbx.yml +44 -0
- rbx/resources/presets/default/problem/random.py +3 -0
- rbx/resources/presets/default/problem/random.txt +2 -0
- rbx/resources/presets/default/problem/sols/main.cpp +9 -0
- rbx/resources/presets/default/problem/sols/slow.cpp +15 -0
- rbx/resources/presets/default/problem/sols/wa.cpp +9 -0
- rbx/resources/presets/default/problem/statement/olymp.sty +250 -0
- rbx/resources/presets/default/problem/statement/projecao.png +0 -0
- rbx/resources/presets/default/problem/statement/statement.rbx.tex +18 -0
- rbx/resources/presets/default/problem/statement/template.rbx.tex +89 -0
- rbx/resources/presets/default/problem/tests/samples/000.in +1 -0
- rbx/resources/presets/default/problem/tests/samples/001.in +1 -0
- rbx/resources/presets/default/problem/validator.cpp +16 -0
- rbx/resources/presets/default/problem/wcmp.cpp +34 -0
- rbx/resources/templates/template.cpp +19 -0
- rbx/run.py +45 -0
- rbx/schema.py +64 -0
- rbx/submit.py +61 -0
- rbx/submitors/__init__.py +18 -0
- rbx/submitors/codeforces.py +120 -0
- rbx/submitors/submitor.py +25 -0
- rbx/test.py +347 -0
- rbx/testcase.py +70 -0
- rbx/testcase_rendering.py +79 -0
- rbx/testdata/box1/gen1.cpp +7 -0
- rbx/testdata/box1/gen2.cpp +9 -0
- rbx/testdata/box1/genScript.py +2 -0
- rbx/testdata/box1/hard-tle.sol.cpp +26 -0
- rbx/testdata/box1/ole.cpp +17 -0
- rbx/testdata/box1/problem.rbx.yml +39 -0
- rbx/testdata/box1/re.sol.cpp +23 -0
- rbx/testdata/box1/sol.cpp +22 -0
- rbx/testdata/box1/tests/1.in +1 -0
- rbx/testdata/box1/tle-and-incorrect.sol.cpp +33 -0
- rbx/testdata/box1/tle.sol.cpp +35 -0
- rbx/testdata/box1/validator.cpp +11 -0
- rbx/testdata/box1/wa.sol.cpp +22 -0
- rbx/testdata/caching/executable.py +1 -0
- rbx/testdata/compatible +0 -0
- rbx/testing_utils.py +65 -0
- rbx/utils.py +162 -0
- rbx_cp-0.5.0.dist-info/LICENSE +201 -0
- rbx_cp-0.5.0.dist-info/METADATA +89 -0
- rbx_cp-0.5.0.dist-info/RECORD +164 -0
- rbx_cp-0.5.0.dist-info/WHEEL +4 -0
- rbx_cp-0.5.0.dist-info/entry_points.txt +4 -0
rbx/box/package.py
ADDED
@@ -0,0 +1,316 @@
|
|
1
|
+
import functools
|
2
|
+
import pathlib
|
3
|
+
from typing import Dict, List, Optional, Tuple
|
4
|
+
|
5
|
+
import typer
|
6
|
+
|
7
|
+
from rbx import config, console, utils
|
8
|
+
from rbx.box import environment
|
9
|
+
from rbx.box.environment import get_sandbox_type
|
10
|
+
from rbx.box.presets import get_installed_preset_or_null, get_preset_lock
|
11
|
+
from rbx.box.schema import (
|
12
|
+
CodeItem,
|
13
|
+
ExpectedOutcome,
|
14
|
+
Generator,
|
15
|
+
Package,
|
16
|
+
Solution,
|
17
|
+
Stress,
|
18
|
+
TestcaseGroup,
|
19
|
+
TestcaseSubgroup,
|
20
|
+
)
|
21
|
+
from rbx.config import get_builtin_checker
|
22
|
+
from rbx.grading.caching import DependencyCache
|
23
|
+
from rbx.grading.judge.cacher import FileCacher
|
24
|
+
from rbx.grading.judge.sandbox import SandboxBase
|
25
|
+
from rbx.grading.judge.storage import FilesystemStorage, Storage
|
26
|
+
|
27
|
+
YAML_NAME = 'problem.rbx.yml'
|
28
|
+
_DEFAULT_CHECKER = 'wcmp.cpp'
|
29
|
+
TEMP_DIR = None
|
30
|
+
|
31
|
+
|
32
|
+
def warn_preset_deactivated(root: pathlib.Path = pathlib.Path()):
|
33
|
+
preset_lock = get_preset_lock(root)
|
34
|
+
if preset_lock is None:
|
35
|
+
return
|
36
|
+
|
37
|
+
preset = get_installed_preset_or_null(preset_lock.preset_name)
|
38
|
+
if preset is None:
|
39
|
+
console.console.print(
|
40
|
+
f'[warning]WARNING: [item]{preset_lock.preset_name}[/item] is not installed. '
|
41
|
+
'Run [item]rbx presets sync && rbx activate[/item] to install and activate this preset.'
|
42
|
+
)
|
43
|
+
console.console.print()
|
44
|
+
return
|
45
|
+
|
46
|
+
if preset.env is not None and (
|
47
|
+
not environment.get_environment_path(preset.name).is_file()
|
48
|
+
or config.get_config().boxEnvironment != preset.name
|
49
|
+
):
|
50
|
+
console.console.print(
|
51
|
+
'[warning]WARNING: This package uses a preset that configures a custom environment, '
|
52
|
+
f' but instead you are using the environment [item]{config.get_config().boxEnvironment}[/item]. '
|
53
|
+
'Run [item]rbx activate[/item] to use the environment configured by your preset.'
|
54
|
+
)
|
55
|
+
console.console.print()
|
56
|
+
return
|
57
|
+
|
58
|
+
|
59
|
+
@functools.cache
|
60
|
+
def find_problem_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
|
61
|
+
root = root.resolve()
|
62
|
+
problem_yaml_path = root / YAML_NAME
|
63
|
+
while root != pathlib.PosixPath('/') and not problem_yaml_path.is_file():
|
64
|
+
root = root.parent
|
65
|
+
problem_yaml_path = root / YAML_NAME
|
66
|
+
if not problem_yaml_path.is_file():
|
67
|
+
return None
|
68
|
+
warn_preset_deactivated(root)
|
69
|
+
return problem_yaml_path
|
70
|
+
|
71
|
+
|
72
|
+
@functools.cache
|
73
|
+
def find_problem_package(root: pathlib.Path = pathlib.Path()) -> Optional[Package]:
|
74
|
+
problem_yaml_path = find_problem_yaml(root)
|
75
|
+
if not problem_yaml_path:
|
76
|
+
return None
|
77
|
+
return utils.model_from_yaml(Package, problem_yaml_path.read_text())
|
78
|
+
|
79
|
+
|
80
|
+
def find_problem_package_or_die(root: pathlib.Path = pathlib.Path()) -> Package:
|
81
|
+
package = find_problem_package(root)
|
82
|
+
if package is None:
|
83
|
+
console.console.print(f'[error]Problem not found in {root.absolute()}[/error]')
|
84
|
+
raise typer.Exit(1)
|
85
|
+
return package
|
86
|
+
|
87
|
+
|
88
|
+
def find_problem(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
89
|
+
found = find_problem_yaml(root)
|
90
|
+
if found is None:
|
91
|
+
console.console.print(f'[error]Problem not found in {root.absolute()}[/error]')
|
92
|
+
raise typer.Exit(1)
|
93
|
+
return found.parent
|
94
|
+
|
95
|
+
|
96
|
+
def within_problem(func):
|
97
|
+
@functools.wraps(func)
|
98
|
+
def wrapper(*args, **kwargs):
|
99
|
+
with utils.new_cd(find_problem()):
|
100
|
+
return func(*args, **kwargs)
|
101
|
+
|
102
|
+
return wrapper
|
103
|
+
|
104
|
+
|
105
|
+
def save_package(
|
106
|
+
package: Optional[Package] = None, root: pathlib.Path = pathlib.Path()
|
107
|
+
) -> None:
|
108
|
+
package = package or find_problem_package_or_die(root)
|
109
|
+
problem_yaml_path = find_problem_yaml(root)
|
110
|
+
if not problem_yaml_path:
|
111
|
+
console.console.print(f'[error]Problem not found in {root.absolute()}[/error]')
|
112
|
+
raise typer.Exit(1)
|
113
|
+
problem_yaml_path.write_text(utils.model_to_yaml(package))
|
114
|
+
|
115
|
+
|
116
|
+
@functools.cache
|
117
|
+
def get_problem_cache_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
118
|
+
cache_dir = find_problem(root) / '.box'
|
119
|
+
cache_dir.mkdir(parents=True, exist_ok=True)
|
120
|
+
return cache_dir
|
121
|
+
|
122
|
+
|
123
|
+
@functools.cache
|
124
|
+
def get_problem_storage_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
125
|
+
storage_dir = get_problem_cache_dir(root) / '.storage'
|
126
|
+
storage_dir.mkdir(parents=True, exist_ok=True)
|
127
|
+
return storage_dir
|
128
|
+
|
129
|
+
|
130
|
+
def get_problem_runs_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
131
|
+
runs_dir = get_problem_cache_dir(root) / 'runs'
|
132
|
+
runs_dir.mkdir(parents=True, exist_ok=True)
|
133
|
+
return runs_dir
|
134
|
+
|
135
|
+
|
136
|
+
@functools.cache
|
137
|
+
def get_cache_storage(root: pathlib.Path = pathlib.Path()) -> Storage:
|
138
|
+
return FilesystemStorage(get_problem_storage_dir(root))
|
139
|
+
|
140
|
+
|
141
|
+
@functools.cache
|
142
|
+
def get_dependency_cache(root: pathlib.Path = pathlib.Path()) -> DependencyCache:
|
143
|
+
return DependencyCache(get_problem_cache_dir(root), get_cache_storage(root))
|
144
|
+
|
145
|
+
|
146
|
+
@functools.cache
|
147
|
+
def get_file_cacher(root: pathlib.Path = pathlib.Path()) -> FileCacher:
|
148
|
+
return FileCacher(get_cache_storage(root))
|
149
|
+
|
150
|
+
|
151
|
+
@functools.cache
|
152
|
+
def get_digest_as_string(
|
153
|
+
digest: str, root: pathlib.Path = pathlib.Path()
|
154
|
+
) -> Optional[str]:
|
155
|
+
cacher = get_file_cacher(root)
|
156
|
+
try:
|
157
|
+
content = cacher.get_file_content(digest)
|
158
|
+
return content.decode()
|
159
|
+
except KeyError:
|
160
|
+
return None
|
161
|
+
|
162
|
+
|
163
|
+
def get_new_sandbox(root: pathlib.Path = pathlib.Path()) -> SandboxBase:
|
164
|
+
return get_sandbox_type()(file_cacher=get_file_cacher(root), temp_dir=TEMP_DIR)
|
165
|
+
|
166
|
+
|
167
|
+
@functools.cache
|
168
|
+
def get_singleton_sandbox(root: pathlib.Path = pathlib.Path()) -> SandboxBase:
|
169
|
+
return get_new_sandbox(root)
|
170
|
+
|
171
|
+
|
172
|
+
@functools.cache
|
173
|
+
def get_build_path(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
174
|
+
return find_problem(root) / 'build'
|
175
|
+
|
176
|
+
|
177
|
+
@functools.cache
|
178
|
+
def get_build_tests_path(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
179
|
+
return get_build_path(root) / 'tests'
|
180
|
+
|
181
|
+
|
182
|
+
@functools.cache
|
183
|
+
def get_build_testgroup_path(
|
184
|
+
group: str, root: pathlib.Path = pathlib.Path()
|
185
|
+
) -> pathlib.Path:
|
186
|
+
res = get_build_tests_path(root) / group
|
187
|
+
res.mkdir(exist_ok=True, parents=True)
|
188
|
+
return res
|
189
|
+
|
190
|
+
|
191
|
+
@functools.cache
|
192
|
+
def get_generator(name: str, root: pathlib.Path = pathlib.Path()) -> Generator:
|
193
|
+
package = find_problem_package_or_die(root)
|
194
|
+
for generator in package.generators:
|
195
|
+
if generator.name == name:
|
196
|
+
return generator
|
197
|
+
console.console.print(f'[error]Generator [item]{name}[/item] not found[/error]')
|
198
|
+
raise typer.Exit(1)
|
199
|
+
|
200
|
+
|
201
|
+
@functools.cache
|
202
|
+
def get_validator(root: pathlib.Path = pathlib.Path()) -> CodeItem:
|
203
|
+
package = find_problem_package_or_die(root)
|
204
|
+
if package.validator is None:
|
205
|
+
console.console.print(
|
206
|
+
'[error]Problem does not have a validator configured.[/error]'
|
207
|
+
)
|
208
|
+
raise typer.Exit(1)
|
209
|
+
return package.validator
|
210
|
+
|
211
|
+
|
212
|
+
@functools.cache
|
213
|
+
def get_checker(root: pathlib.Path = pathlib.Path()) -> CodeItem:
|
214
|
+
package = find_problem_package_or_die(root)
|
215
|
+
|
216
|
+
return package.checker or CodeItem(
|
217
|
+
path=get_builtin_checker(_DEFAULT_CHECKER).absolute()
|
218
|
+
)
|
219
|
+
|
220
|
+
|
221
|
+
@functools.cache
|
222
|
+
def get_solutions(root: pathlib.Path = pathlib.Path()) -> List[Solution]:
|
223
|
+
package = find_problem_package_or_die(root)
|
224
|
+
return package.solutions
|
225
|
+
|
226
|
+
|
227
|
+
@functools.cache
|
228
|
+
def get_main_solution(root: pathlib.Path = pathlib.Path()) -> Optional[Solution]:
|
229
|
+
for solution in get_solutions(root):
|
230
|
+
if solution.outcome == ExpectedOutcome.ACCEPTED:
|
231
|
+
return solution
|
232
|
+
return None
|
233
|
+
|
234
|
+
|
235
|
+
@functools.cache
|
236
|
+
def get_solution(name: str, root: pathlib.Path = pathlib.Path()) -> Solution:
|
237
|
+
for solution in get_solutions(root):
|
238
|
+
if str(solution.path) == name:
|
239
|
+
return solution
|
240
|
+
console.console.print(f'[error]Solution [item]{name}[/item] not found[/error]')
|
241
|
+
raise typer.Exit(1)
|
242
|
+
|
243
|
+
|
244
|
+
@functools.cache
|
245
|
+
def get_solution_or_nil(
|
246
|
+
name: str, root: pathlib.Path = pathlib.Path()
|
247
|
+
) -> Optional[Solution]:
|
248
|
+
for solution in get_solutions(root):
|
249
|
+
if str(solution.path) == name:
|
250
|
+
return solution
|
251
|
+
return None
|
252
|
+
|
253
|
+
|
254
|
+
@functools.cache
|
255
|
+
def get_stress(name: str, root: pathlib.Path = pathlib.Path()) -> Stress:
|
256
|
+
pkg = find_problem_package_or_die(root)
|
257
|
+
for stress in pkg.stresses:
|
258
|
+
if stress.name == name:
|
259
|
+
return stress
|
260
|
+
console.console.print(f'[error]Stress [item]{name}[/item] not found[/error]')
|
261
|
+
raise typer.Exit(1)
|
262
|
+
|
263
|
+
|
264
|
+
@functools.cache
|
265
|
+
def get_testgroup(name: str, root: pathlib.Path = pathlib.Path()) -> TestcaseGroup:
|
266
|
+
pkg = find_problem_package_or_die(root)
|
267
|
+
for testgroup in pkg.testcases:
|
268
|
+
if testgroup.name == name:
|
269
|
+
return testgroup
|
270
|
+
console.console.print(f'[error]Test group [item]{name}[/item] not found[/error]')
|
271
|
+
raise typer.Exit(1)
|
272
|
+
|
273
|
+
|
274
|
+
@functools.cache
|
275
|
+
def get_test_groups_by_name(
|
276
|
+
root: pathlib.Path = pathlib.Path(),
|
277
|
+
) -> Dict[str, TestcaseSubgroup]:
|
278
|
+
pkg = find_problem_package_or_die(root)
|
279
|
+
res = {}
|
280
|
+
|
281
|
+
for testgroup in pkg.testcases:
|
282
|
+
res[testgroup.name] = testgroup
|
283
|
+
for subgroup in testgroup.subgroups:
|
284
|
+
res[f'{testgroup.name}.{subgroup.name}'] = subgroup
|
285
|
+
|
286
|
+
return res
|
287
|
+
|
288
|
+
|
289
|
+
# Return each compilation file and to where it should be moved inside
|
290
|
+
# the sandbox.
|
291
|
+
def get_compilation_files(code: CodeItem) -> List[Tuple[pathlib.Path, pathlib.Path]]:
|
292
|
+
code_dir = code.path.parent.resolve()
|
293
|
+
|
294
|
+
res = []
|
295
|
+
for compilation_file in code.compilationFiles or []:
|
296
|
+
compilation_file_path = pathlib.Path(compilation_file).resolve()
|
297
|
+
if not compilation_file_path.is_file():
|
298
|
+
console.console.print(
|
299
|
+
f'[error]Compilation file [item]{compilation_file}[/item] for '
|
300
|
+
f'code [item]{code.path}[/item] does not exist.[/error]',
|
301
|
+
)
|
302
|
+
raise typer.Exit(1)
|
303
|
+
if not compilation_file_path.is_relative_to(code_dir):
|
304
|
+
console.console.print(
|
305
|
+
f'[error]Compilation file [item]{compilation_file}[/item] for '
|
306
|
+
f"code [item]{code.path}[/item] is not under the code's folder.[/error]",
|
307
|
+
)
|
308
|
+
raise typer.Exit(1)
|
309
|
+
|
310
|
+
res.append(
|
311
|
+
(
|
312
|
+
pathlib.Path(compilation_file),
|
313
|
+
compilation_file_path.relative_to(code_dir),
|
314
|
+
)
|
315
|
+
)
|
316
|
+
return res
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import typing
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
BocaLanguage = typing.Literal['c', 'cpp', 'cc', 'kt', 'java', 'py2', 'py3']
|
6
|
+
|
7
|
+
_MAX_REP_ERROR = 0.2 # 20% error allowed in time limit when adding reps
|
8
|
+
|
9
|
+
|
10
|
+
class BocaExtension(BaseModel):
|
11
|
+
languages: typing.List[BocaLanguage] = list(typing.get_args(BocaLanguage))
|
12
|
+
flags: typing.Dict[BocaLanguage, str] = {}
|
13
|
+
maximumTimeError: float = _MAX_REP_ERROR
|
14
|
+
|
15
|
+
def flags_with_defaults(self) -> typing.Dict[BocaLanguage, str]:
|
16
|
+
res: typing.Dict[BocaLanguage, str] = {
|
17
|
+
'c': '-std=gnu11 -O2 -static -lm',
|
18
|
+
'cpp': '-O2 -static -lm',
|
19
|
+
'cc': '-std=c++20 -O2 -static -lm',
|
20
|
+
}
|
21
|
+
res.update(self.flags)
|
22
|
+
return res
|
23
|
+
|
24
|
+
|
25
|
+
class BocaLanguageExtension(BaseModel):
|
26
|
+
# BocaLanguage this rbx language matches with.
|
27
|
+
bocaLanguage: typing.Optional[str] = None
|
@@ -0,0 +1,245 @@
|
|
1
|
+
import pathlib
|
2
|
+
import shutil
|
3
|
+
from math import fabs
|
4
|
+
from typing import List
|
5
|
+
|
6
|
+
import typer
|
7
|
+
|
8
|
+
from rbx import console
|
9
|
+
from rbx.box import package
|
10
|
+
from rbx.box.environment import get_extension_or_default
|
11
|
+
from rbx.box.packaging.boca.extension import BocaExtension, BocaLanguage
|
12
|
+
from rbx.box.packaging.packager import BasePackager, BuiltStatement
|
13
|
+
from rbx.box.statements.schema import Statement
|
14
|
+
from rbx.config import get_default_app_path, get_testlib
|
15
|
+
|
16
|
+
_MAX_REP_TIME = (
|
17
|
+
7 # TL to allow for additional rounding reps should be < _MAX_REP_TIME in seconds
|
18
|
+
)
|
19
|
+
_MAX_REPS = 10 # Maximum number of reps to add
|
20
|
+
|
21
|
+
|
22
|
+
def test_time(time):
|
23
|
+
return max(1, round(time))
|
24
|
+
|
25
|
+
|
26
|
+
class BocaPackager(BasePackager):
|
27
|
+
def _get_main_statement(self) -> Statement:
|
28
|
+
pkg = package.find_problem_package_or_die()
|
29
|
+
|
30
|
+
if not pkg.statements:
|
31
|
+
console.console.print('[error]No statements found.[/error]')
|
32
|
+
raise typer.Exit(1)
|
33
|
+
|
34
|
+
return pkg.statements[0]
|
35
|
+
|
36
|
+
def _get_main_built_statement(
|
37
|
+
self, built_statements: List[BuiltStatement]
|
38
|
+
) -> BuiltStatement:
|
39
|
+
statement = self._get_main_statement()
|
40
|
+
for built_statement in built_statements:
|
41
|
+
if built_statement.statement == statement:
|
42
|
+
return built_statement
|
43
|
+
|
44
|
+
console.console.print(
|
45
|
+
'[error]Main statement not found among built statements.[/error]'
|
46
|
+
)
|
47
|
+
raise typer.Exit(1)
|
48
|
+
|
49
|
+
def _get_problem_name(self) -> str:
|
50
|
+
pkg = package.find_problem_package_or_die()
|
51
|
+
return pkg.name
|
52
|
+
|
53
|
+
def _get_problem_info(self) -> str:
|
54
|
+
pkg = package.find_problem_package_or_die()
|
55
|
+
statement = self._get_main_statement()
|
56
|
+
return (
|
57
|
+
f'basename={pkg.name}\n'
|
58
|
+
f'fullname={statement.title}\n'
|
59
|
+
f'descfile={self._get_problem_name()}.pdf\n'
|
60
|
+
)
|
61
|
+
|
62
|
+
def _get_pkg_timelimit(self, language: BocaLanguage) -> int:
|
63
|
+
pkg = package.find_problem_package_or_die()
|
64
|
+
return pkg.timelimit_for_language(language)
|
65
|
+
|
66
|
+
def _get_pkg_memorylimit(self, language: BocaLanguage) -> int:
|
67
|
+
pkg = package.find_problem_package_or_die()
|
68
|
+
return pkg.memorylimit_for_language(language)
|
69
|
+
|
70
|
+
def _get_number_of_runs(self, language: BocaLanguage) -> int:
|
71
|
+
pkg = package.find_problem_package_or_die()
|
72
|
+
extension = get_extension_or_default('boca', BocaExtension)
|
73
|
+
pkg_timelimit = self._get_pkg_timelimit(language)
|
74
|
+
time = pkg_timelimit / 1000 # convert to seconds
|
75
|
+
|
76
|
+
if time >= _MAX_REP_TIME:
|
77
|
+
return 1
|
78
|
+
|
79
|
+
def rounding_error(time):
|
80
|
+
return fabs(time - test_time(time))
|
81
|
+
|
82
|
+
def error_percentage(time, runs):
|
83
|
+
return rounding_error(time * runs) / (time * runs)
|
84
|
+
|
85
|
+
if error_percentage(time, 1) < 1e-6:
|
86
|
+
return 1
|
87
|
+
|
88
|
+
for i in range(1, _MAX_REPS + 1):
|
89
|
+
if error_percentage(time, i) <= extension.maximumTimeError:
|
90
|
+
console.console.print(
|
91
|
+
f'[warning]Using {i} run(s) to define integer TL for BOCA when using language [item]{language}[/item] '
|
92
|
+
f'(original TL is {pkg_timelimit}ms, new TL is {test_time(time * i) * 1000}ms).[/warning]'
|
93
|
+
)
|
94
|
+
return i
|
95
|
+
|
96
|
+
percent_str = f'{round(extension.maximumTimeError * 100)}%'
|
97
|
+
console.console.print(
|
98
|
+
f'[error]Error while defining limits for problem [item]{pkg.name}[/item], language [item]{language}[/item].[/error]'
|
99
|
+
)
|
100
|
+
console.console.print(
|
101
|
+
f'[error]Introducing an error of less than {percent_str} in the TL in less than '
|
102
|
+
f'{_MAX_REPS} runs is not possible.[/error]'
|
103
|
+
)
|
104
|
+
console.console.print(
|
105
|
+
f'[error]Original TL for [item]{language}[/item] is {pkg_timelimit}ms, please review it.[/error]'
|
106
|
+
)
|
107
|
+
raise typer.Exit(1)
|
108
|
+
|
109
|
+
def _get_limits(self, language: BocaLanguage) -> str:
|
110
|
+
pkg = package.find_problem_package_or_die()
|
111
|
+
no_of_runs = self._get_number_of_runs(language)
|
112
|
+
return (
|
113
|
+
'#!/bin/bash\n'
|
114
|
+
f'echo {test_time(self._get_pkg_timelimit(language) / 1000 * no_of_runs)}\n'
|
115
|
+
f'echo {no_of_runs}\n'
|
116
|
+
f'echo {self._get_pkg_memorylimit(language)}\n'
|
117
|
+
f'echo {pkg.outputLimit}\n'
|
118
|
+
f'exit 0\n'
|
119
|
+
)
|
120
|
+
|
121
|
+
def _get_compare(self) -> str:
|
122
|
+
compare_path = get_default_app_path() / 'packagers' / 'boca' / 'compare'
|
123
|
+
if not compare_path.exists():
|
124
|
+
console.console.print(
|
125
|
+
'[error]BOCA template compare script not found.[/error]'
|
126
|
+
)
|
127
|
+
raise typer.Exit(1)
|
128
|
+
return compare_path.read_text()
|
129
|
+
|
130
|
+
def _get_checker(self) -> str:
|
131
|
+
checker_path = get_default_app_path() / 'packagers' / 'boca' / 'checker.sh'
|
132
|
+
if not checker_path.exists():
|
133
|
+
console.console.print(
|
134
|
+
'[error]BOCA template checker script not found.[/error]'
|
135
|
+
)
|
136
|
+
raise typer.Exit(1)
|
137
|
+
checker_text = checker_path.read_text()
|
138
|
+
testlib = get_testlib().read_text()
|
139
|
+
checker = package.get_checker().path.read_text()
|
140
|
+
return checker_text.replace('{{testlib_content}}', testlib).replace(
|
141
|
+
'{{checker_content}}', checker
|
142
|
+
)
|
143
|
+
|
144
|
+
def _get_compile(self, language: BocaLanguage) -> str:
|
145
|
+
extension = get_extension_or_default('boca', BocaExtension)
|
146
|
+
|
147
|
+
compile_path = (
|
148
|
+
get_default_app_path() / 'packagers' / 'boca' / 'compile' / language
|
149
|
+
)
|
150
|
+
if not compile_path.is_file():
|
151
|
+
console.console.print(
|
152
|
+
f'[error]Compile script for language [item]{language}[/item] not found.[/error]'
|
153
|
+
)
|
154
|
+
raise typer.Exit(1)
|
155
|
+
|
156
|
+
compile_text = compile_path.read_text()
|
157
|
+
|
158
|
+
assert 'umask 0022' in compile_text
|
159
|
+
compile_text = compile_text.replace(
|
160
|
+
'umask 0022', 'umask 0022\n\n' + self._get_checker()
|
161
|
+
)
|
162
|
+
|
163
|
+
flags = extension.flags_with_defaults()
|
164
|
+
if language in flags:
|
165
|
+
compile_text = compile_text.replace('{{rbxFlags}}', flags[language])
|
166
|
+
return compile_text
|
167
|
+
|
168
|
+
def name(self) -> str:
|
169
|
+
return 'boca'
|
170
|
+
|
171
|
+
def package(
|
172
|
+
self,
|
173
|
+
build_path: pathlib.Path,
|
174
|
+
into_path: pathlib.Path,
|
175
|
+
built_statements: List[BuiltStatement],
|
176
|
+
) -> pathlib.Path:
|
177
|
+
extension = get_extension_or_default('boca', BocaExtension)
|
178
|
+
|
179
|
+
# Prepare limits
|
180
|
+
limits_path = into_path / 'limits'
|
181
|
+
limits_path.mkdir(parents=True, exist_ok=True)
|
182
|
+
for language in extension.languages:
|
183
|
+
(limits_path / language).write_text(self._get_limits(language))
|
184
|
+
|
185
|
+
# Prepare compare
|
186
|
+
compare_path = into_path / 'compare'
|
187
|
+
compare_path.mkdir(parents=True, exist_ok=True)
|
188
|
+
for language in extension.languages:
|
189
|
+
(compare_path / language).write_text(self._get_compare())
|
190
|
+
|
191
|
+
# Prepare run
|
192
|
+
run_path = into_path / 'run'
|
193
|
+
run_path.mkdir(parents=True, exist_ok=True)
|
194
|
+
for language in extension.languages:
|
195
|
+
run_orig_path = (
|
196
|
+
get_default_app_path() / 'packagers' / 'boca' / 'run' / language
|
197
|
+
)
|
198
|
+
if not run_orig_path.is_file():
|
199
|
+
console.console.print(
|
200
|
+
f'[error]Run script for language [item]{language}[/item] not found.[/error]'
|
201
|
+
)
|
202
|
+
raise typer.Exit(1)
|
203
|
+
shutil.copyfile(run_orig_path, run_path / language)
|
204
|
+
|
205
|
+
# Prepare compile.
|
206
|
+
compile_path = into_path / 'compile'
|
207
|
+
compile_path.mkdir(parents=True, exist_ok=True)
|
208
|
+
for language in extension.languages:
|
209
|
+
(compile_path / language).write_text(self._get_compile(language))
|
210
|
+
|
211
|
+
# Prepare tests
|
212
|
+
tests_path = into_path / 'tests'
|
213
|
+
tests_path.mkdir(parents=True, exist_ok=True)
|
214
|
+
for language in extension.languages:
|
215
|
+
(tests_path / language).write_text('exit 0\n')
|
216
|
+
|
217
|
+
# Problem statement
|
218
|
+
description_path = into_path / 'description'
|
219
|
+
description_path.mkdir(parents=True, exist_ok=True)
|
220
|
+
(description_path / 'problem.info').write_text(self._get_problem_info())
|
221
|
+
shutil.copyfile(
|
222
|
+
self._get_main_built_statement(built_statements).path,
|
223
|
+
(description_path / self._get_problem_name()).with_suffix('.pdf'),
|
224
|
+
)
|
225
|
+
|
226
|
+
# Prepare IO
|
227
|
+
inputs_path = into_path / 'input'
|
228
|
+
inputs_path.mkdir(parents=True, exist_ok=True)
|
229
|
+
outputs_path = into_path / 'output'
|
230
|
+
outputs_path.mkdir(parents=True, exist_ok=True)
|
231
|
+
|
232
|
+
testcases = self.get_flattened_built_testcases()
|
233
|
+
for i, testcase in enumerate(testcases):
|
234
|
+
shutil.copyfile(testcase.inputPath, inputs_path / f'{i+1:03d}')
|
235
|
+
if testcase.outputPath is not None:
|
236
|
+
shutil.copyfile(testcase.outputPath, outputs_path / f'{i+1:03d}')
|
237
|
+
else:
|
238
|
+
(outputs_path / f'{i+1:03d}').touch()
|
239
|
+
|
240
|
+
# Zip all.
|
241
|
+
shutil.make_archive(
|
242
|
+
str(build_path / self._get_problem_name()), 'zip', into_path
|
243
|
+
)
|
244
|
+
|
245
|
+
return (build_path / self._get_problem_name()).with_suffix('.zip')
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import pathlib
|
2
|
+
import tempfile
|
3
|
+
from typing import Type
|
4
|
+
|
5
|
+
import typer
|
6
|
+
|
7
|
+
from rbx import annotations, console, utils
|
8
|
+
from rbx.box import environment, package
|
9
|
+
from rbx.box.contest import build_contest_statements, contest_package, contest_utils
|
10
|
+
from rbx.box.packaging.main import run_packager
|
11
|
+
from rbx.box.packaging.packager import (
|
12
|
+
BaseContestPackager,
|
13
|
+
BasePackager,
|
14
|
+
BuiltContestStatement,
|
15
|
+
BuiltProblemPackage,
|
16
|
+
)
|
17
|
+
from rbx.box.packaging.polygon.packager import PolygonContestPackager, PolygonPackager
|
18
|
+
|
19
|
+
app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
|
20
|
+
|
21
|
+
|
22
|
+
def run_contest_packager(
|
23
|
+
contest_packager_cls: Type[BaseContestPackager],
|
24
|
+
packager_cls: Type[BasePackager],
|
25
|
+
verification: environment.VerificationParam,
|
26
|
+
):
|
27
|
+
contest = contest_package.find_contest_package_or_die()
|
28
|
+
|
29
|
+
# Build problem-level packages.
|
30
|
+
built_packages = []
|
31
|
+
for problem in contest.problems:
|
32
|
+
console.console.print(
|
33
|
+
f'Processing problem [item]{problem.short_name}[/item]...'
|
34
|
+
)
|
35
|
+
with utils.new_cd(problem.get_path()):
|
36
|
+
contest_utils.clear_package_cache()
|
37
|
+
package_path = run_packager(packager_cls, verification=verification)
|
38
|
+
built_packages.append(
|
39
|
+
BuiltProblemPackage(
|
40
|
+
path=problem.get_path() / package_path,
|
41
|
+
package=package.find_problem_package_or_die(),
|
42
|
+
problem=problem,
|
43
|
+
)
|
44
|
+
)
|
45
|
+
|
46
|
+
# Build statements.
|
47
|
+
packager = contest_packager_cls()
|
48
|
+
statement_types = packager.statement_types()
|
49
|
+
built_statements = []
|
50
|
+
|
51
|
+
for statement_type in statement_types:
|
52
|
+
languages = packager.languages()
|
53
|
+
for language in languages:
|
54
|
+
statement = packager.get_statement_for_language(language)
|
55
|
+
statement_path = build_contest_statements.build_statement(
|
56
|
+
statement, contest, statement_type
|
57
|
+
)
|
58
|
+
built_statements.append(
|
59
|
+
BuiltContestStatement(statement, statement_path, statement_type)
|
60
|
+
)
|
61
|
+
|
62
|
+
console.console.print(f'Packaging contest for [item]{packager.name()}[/item]...')
|
63
|
+
|
64
|
+
# Build contest-level package.
|
65
|
+
with tempfile.TemporaryDirectory() as td:
|
66
|
+
packager.package(
|
67
|
+
built_packages, pathlib.Path('build'), pathlib.Path(td), built_statements
|
68
|
+
)
|
69
|
+
|
70
|
+
console.console.print(
|
71
|
+
f'[success]Contest packaged for [item]{packager.name()}[/item]![/success]'
|
72
|
+
)
|
73
|
+
|
74
|
+
|
75
|
+
@app.command('polygon', help='Build a contest package for Polygon.')
|
76
|
+
@contest_package.within_contest
|
77
|
+
def polygon(
|
78
|
+
verification: environment.VerificationParam,
|
79
|
+
):
|
80
|
+
run_contest_packager(
|
81
|
+
PolygonContestPackager, PolygonPackager, verification=verification
|
82
|
+
)
|