rbx.cp 0.13.8__py3-none-any.whl → 0.16.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.
Files changed (77) hide show
  1. rbx/__version__.py +1 -0
  2. rbx/box/cli.py +74 -70
  3. rbx/box/code.py +3 -0
  4. rbx/box/contest/build_contest_statements.py +65 -23
  5. rbx/box/contest/contest_package.py +8 -1
  6. rbx/box/contest/main.py +9 -3
  7. rbx/box/contest/schema.py +17 -13
  8. rbx/box/contest/statements.py +12 -8
  9. rbx/box/dump_schemas.py +2 -1
  10. rbx/box/environment.py +1 -1
  11. rbx/box/fields.py +22 -4
  12. rbx/box/generators.py +32 -13
  13. rbx/box/git_utils.py +29 -1
  14. rbx/box/limits_info.py +161 -0
  15. rbx/box/package.py +18 -1
  16. rbx/box/packaging/boca/boca_language_utils.py +26 -0
  17. rbx/box/packaging/boca/boca_outcome_utils.py +10 -0
  18. rbx/box/packaging/boca/packager.py +7 -5
  19. rbx/box/packaging/contest_main.py +20 -12
  20. rbx/box/packaging/packager.py +24 -14
  21. rbx/box/packaging/polygon/packager.py +7 -3
  22. rbx/box/packaging/polygon/upload.py +2 -1
  23. rbx/box/presets/__init__.py +143 -78
  24. rbx/box/presets/fetch.py +10 -2
  25. rbx/box/presets/schema.py +16 -1
  26. rbx/box/remote.py +3 -3
  27. rbx/box/sanitizers/issue_stack.py +124 -0
  28. rbx/box/schema.py +87 -27
  29. rbx/box/solutions.py +74 -117
  30. rbx/box/statements/build_statements.py +12 -1
  31. rbx/box/statements/builders.py +5 -3
  32. rbx/box/statements/latex_jinja.py +73 -23
  33. rbx/box/statements/schema.py +7 -9
  34. rbx/box/stressing/generator_parser.py +3 -1
  35. rbx/box/tasks.py +10 -10
  36. rbx/box/testcase_extractors.py +8 -0
  37. rbx/box/testing/testing_preset.py +129 -2
  38. rbx/box/testing/testing_shared.py +3 -1
  39. rbx/box/timing.py +305 -0
  40. rbx/box/tooling/boca/debug_utils.py +88 -0
  41. rbx/box/tooling/boca/manual_scrape.py +20 -0
  42. rbx/box/tooling/boca/scraper.py +660 -57
  43. rbx/box/unit.py +0 -2
  44. rbx/box/validators.py +0 -4
  45. rbx/grading/judge/cacher.py +36 -0
  46. rbx/grading/judge/program.py +12 -2
  47. rbx/grading/judge/sandbox.py +1 -1
  48. rbx/grading/judge/sandboxes/stupid_sandbox.py +2 -1
  49. rbx/grading/judge/storage.py +36 -3
  50. rbx/grading/limits.py +4 -0
  51. rbx/grading/steps.py +3 -2
  52. rbx/resources/presets/default/contest/contest.rbx.yml +7 -1
  53. rbx/resources/presets/default/contest/statement/info.rbx.tex +46 -0
  54. rbx/resources/presets/default/preset.rbx.yml +1 -0
  55. rbx/resources/presets/default/problem/.gitignore +1 -0
  56. rbx/resources/presets/default/problem/problem.rbx.yml +19 -3
  57. rbx/resources/presets/default/problem/rbx.h +52 -5
  58. rbx/resources/presets/default/problem/statement/statement.rbx.tex +6 -2
  59. rbx/resources/presets/default/problem/testlib.h +6299 -0
  60. rbx/resources/presets/default/problem/validator.cpp +4 -3
  61. rbx/resources/presets/default/shared/contest_template.rbx.tex +8 -4
  62. rbx/resources/presets/default/shared/icpc.sty +18 -3
  63. rbx/resources/presets/default/shared/problem_template.rbx.tex +4 -1
  64. rbx/testing_utils.py +17 -1
  65. rbx/utils.py +45 -0
  66. {rbx_cp-0.13.8.dist-info → rbx_cp-0.16.0.dist-info}/METADATA +5 -2
  67. {rbx_cp-0.13.8.dist-info → rbx_cp-0.16.0.dist-info}/RECORD +71 -67
  68. {rbx_cp-0.13.8.dist-info → rbx_cp-0.16.0.dist-info}/entry_points.txt +0 -1
  69. rbx/providers/__init__.py +0 -43
  70. rbx/providers/codeforces.py +0 -73
  71. rbx/providers/provider.py +0 -26
  72. rbx/submitors/__init__.py +0 -18
  73. rbx/submitors/codeforces.py +0 -121
  74. rbx/submitors/submitor.py +0 -25
  75. /rbx/resources/presets/default/problem/sols/{wa.cpp → wa-overflow.cpp} +0 -0
  76. {rbx_cp-0.13.8.dist-info → rbx_cp-0.16.0.dist-info}/LICENSE +0 -0
  77. {rbx_cp-0.13.8.dist-info → rbx_cp-0.16.0.dist-info}/WHEEL +0 -0
rbx/box/generators.py CHANGED
@@ -378,7 +378,8 @@ async def generate_testcases(
378
378
 
379
379
 
380
380
  async def generate_output_for_testcase(
381
- main_solution_digest: str,
381
+ model_solution: CodeItem,
382
+ model_solution_digest: str,
382
383
  testcase: Testcase,
383
384
  interactor_digest: Optional[str] = None,
384
385
  capture_pipes: Optional[bool] = None,
@@ -387,13 +388,9 @@ async def generate_output_for_testcase(
387
388
  testcase.inputPath.parent.mkdir(parents=True, exist_ok=True)
388
389
  testcase.outputPath.parent.mkdir(parents=True, exist_ok=True)
389
390
 
390
- main_solution = package.get_main_solution()
391
- if main_solution is None:
392
- return
393
-
394
391
  eval: Evaluation = await run_solution_on_testcase(
395
- main_solution,
396
- main_solution_digest,
392
+ model_solution,
393
+ model_solution_digest,
397
394
  None,
398
395
  testcase,
399
396
  interactor_digest=interactor_digest,
@@ -439,7 +436,7 @@ async def generate_outputs_for_testcases(
439
436
  needs_output = _needs_output(generation_entries)
440
437
 
441
438
  main_solution = package.get_main_solution()
442
- solution_digest: Optional[str] = None
439
+ solution_digest_map = {}
443
440
 
444
441
  pkg = package.find_problem_package_or_die()
445
442
 
@@ -452,11 +449,30 @@ async def generate_outputs_for_testcases(
452
449
  if progress:
453
450
  progress.update('Compiling main solution...')
454
451
  try:
455
- solution_digest = compile_item(main_solution)
452
+ solution_digest_map[main_solution.path] = compile_item(main_solution)
456
453
  except:
457
454
  console.console.print('[error]Failed compiling main solution.[/error]')
458
455
  raise
459
456
 
457
+ for entry in generation_entries:
458
+ if (
459
+ entry.model_solution is not None
460
+ and entry.model_solution.path not in solution_digest_map
461
+ ):
462
+ if progress:
463
+ progress.update(
464
+ f'Compiling model solution [item]{entry.model_solution.path}[/item]...'
465
+ )
466
+ try:
467
+ solution_digest_map[entry.model_solution.path] = compile_item(
468
+ entry.model_solution
469
+ )
470
+ except:
471
+ console.console.print(
472
+ f'[error]Failed compiling model solution [item]{entry.model_solution.path}[/item].[/error]'
473
+ )
474
+ raise
475
+
460
476
  gen_runs_dir = package.get_problem_runs_dir() / '.gen'
461
477
  shutil.rmtree(str(gen_runs_dir), ignore_errors=True)
462
478
  gen_runs_dir.mkdir(parents=True, exist_ok=True)
@@ -476,15 +492,17 @@ async def generate_outputs_for_testcases(
476
492
  continue
477
493
 
478
494
  assert needs_output
495
+ model_solution = entry.model_solution or main_solution
479
496
  if (
480
- main_solution is None or solution_digest is None
497
+ model_solution is None or model_solution.path not in solution_digest_map
481
498
  ) and not tc.outputPath.is_file():
482
499
  console.console.print(
483
- '[error]No main solution found to generate outputs for testcases.[/error]',
500
+ '[error]No main/model solution found to generate outputs for testcases.[/error]',
484
501
  )
485
502
  raise typer.Exit(1)
486
503
 
487
- assert solution_digest is not None
504
+ assert model_solution is not None
505
+ model_solution_digest = solution_digest_map[model_solution.path]
488
506
  capture_pipes = None
489
507
  if (
490
508
  pkg.type == TaskType.COMMUNICATION
@@ -497,7 +515,8 @@ async def generate_outputs_for_testcases(
497
515
  )
498
516
 
499
517
  await generate_output_for_testcase(
500
- solution_digest,
518
+ model_solution,
519
+ model_solution_digest,
501
520
  tc,
502
521
  interactor_digest=interactor_digest,
503
522
  capture_pipes=capture_pipes,
rbx/box/git_utils.py CHANGED
@@ -1,7 +1,9 @@
1
1
  import pathlib
2
- from typing import Optional
2
+ import subprocess
3
+ from typing import List, Optional
3
4
 
4
5
  import git
6
+ import semver
5
7
 
6
8
 
7
9
  def get_repo_or_nil(
@@ -26,3 +28,29 @@ def get_any_remote(repo: git.Repo) -> Optional[git.Remote]:
26
28
  if remote.exists():
27
29
  return remote
28
30
  return None
31
+
32
+
33
+ def _parse_tag_from_ref(ref: str) -> str:
34
+ return ref.split('/')[-1].split('^{}')[0]
35
+
36
+
37
+ def ls_remote_tags(uri: str) -> List[str]:
38
+ completed_process = subprocess.run(
39
+ ['git', 'ls-remote', '--tags', uri],
40
+ check=True,
41
+ capture_output=True,
42
+ text=True,
43
+ )
44
+ return [
45
+ _parse_tag_from_ref(line.split('\t')[1])
46
+ for line in completed_process.stdout.split('\n')
47
+ if line
48
+ ]
49
+
50
+
51
+ def latest_remote_tag(uri: str) -> str:
52
+ tags = ls_remote_tags(uri)
53
+ valid_tags = [tag for tag in tags if semver.Version.is_valid(tag)]
54
+ if not valid_tags:
55
+ raise ValueError(f'No valid tags found for {uri}')
56
+ return sorted(valid_tags, key=semver.VersionInfo.parse)[-1]
rbx/box/limits_info.py ADDED
@@ -0,0 +1,161 @@
1
+ import contextvars
2
+ import pathlib
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from rbx import console, utils
8
+ from rbx.box import package
9
+ from rbx.box.environment import VerificationLevel
10
+ from rbx.box.schema import LimitsProfile
11
+ from rbx.grading.limits import Limits
12
+
13
+ profile_var = contextvars.ContextVar[Optional[str]]('profile', default=None)
14
+
15
+
16
+ def get_active_profile() -> Optional[str]:
17
+ return profile_var.get()
18
+
19
+
20
+ class use_profile:
21
+ def __init__(self, profile: Optional[str]):
22
+ self.profile = profile
23
+ self.token = None
24
+
25
+ def __enter__(self):
26
+ self.token = profile_var.set(self.profile)
27
+
28
+ def __exit__(self, exc_type, exc_value, traceback):
29
+ if self.token is not None:
30
+ profile_var.reset(self.token)
31
+
32
+
33
+ def _expand_limits_profile(
34
+ limits_profile: LimitsProfile, root: pathlib.Path
35
+ ) -> LimitsProfile:
36
+ pkg = package.find_problem_package_or_die(root=root)
37
+ res = LimitsProfile(
38
+ timeLimit=pkg.timeLimit,
39
+ memoryLimit=pkg.memoryLimit,
40
+ outputLimit=pkg.outputLimit,
41
+ )
42
+ for language, modifier in pkg.modifiers.items():
43
+ res.modifiers[language] = modifier.model_copy(deep=True)
44
+
45
+ if limits_profile.inheritFromPackage:
46
+ return res
47
+
48
+ time_is_overridden = limits_profile.timeLimit is not None
49
+ memory_is_overridden = limits_profile.memoryLimit is not None
50
+ output_is_overridden = limits_profile.outputLimit is not None
51
+ if time_is_overridden:
52
+ res.timeLimit = limits_profile.timeLimit
53
+ if memory_is_overridden:
54
+ res.memoryLimit = limits_profile.memoryLimit
55
+ if output_is_overridden:
56
+ res.outputLimit = limits_profile.outputLimit
57
+
58
+ for modifier in res.modifiers.values():
59
+ # Clean up modifiers coming from the package that are not overridden
60
+ # by the base limits profile.
61
+ if time_is_overridden:
62
+ modifier.time = None
63
+ if memory_is_overridden:
64
+ modifier.memory = None
65
+
66
+ for language, modifier in limits_profile.modifiers.items():
67
+ if modifier.time is not None:
68
+ res.modifiers[language].time = modifier.time
69
+ if modifier.timeMultiplier is not None:
70
+ res.modifiers[language].timeMultiplier = modifier.timeMultiplier
71
+ if modifier.memory is not None:
72
+ res.modifiers[language].memory = modifier.memory
73
+ return res
74
+
75
+
76
+ def _get_limits_from_profile(
77
+ language: Optional[str],
78
+ limits_profile: LimitsProfile,
79
+ source_profile: Optional[str],
80
+ verification: VerificationLevel,
81
+ root: pathlib.Path,
82
+ ) -> Limits:
83
+ limits_profile = _expand_limits_profile(limits_profile, root=root)
84
+ return Limits(
85
+ time=limits_profile.timelimit_for_language(language),
86
+ memory=limits_profile.memorylimit_for_language(language),
87
+ output=limits_profile.outputLimit,
88
+ isDoubleTL=verification.value >= VerificationLevel.FULL.value,
89
+ profile=source_profile,
90
+ )
91
+
92
+
93
+ def get_saved_limits_profile(
94
+ profile: str = 'local', root: pathlib.Path = pathlib.Path()
95
+ ) -> Optional[LimitsProfile]:
96
+ limits_path = package.get_limits_file(profile, root=root)
97
+ if not limits_path.exists():
98
+ return None
99
+ return utils.model_from_yaml(LimitsProfile, limits_path.read_text())
100
+
101
+
102
+ def get_package_limits_profile(root: pathlib.Path = pathlib.Path()) -> LimitsProfile:
103
+ profile = LimitsProfile(inheritFromPackage=True)
104
+ return _expand_limits_profile(profile, root=root)
105
+
106
+
107
+ def get_package_limits(
108
+ verification: VerificationLevel = VerificationLevel.NONE,
109
+ root: pathlib.Path = pathlib.Path(),
110
+ ) -> Limits:
111
+ return _get_limits_from_profile(
112
+ language=None,
113
+ limits_profile=get_package_limits_profile(root=root),
114
+ source_profile=None,
115
+ verification=verification,
116
+ root=root,
117
+ )
118
+
119
+
120
+ def get_limits_profile(
121
+ profile: Optional[str] = None,
122
+ fallback_to_package_profile: bool = True,
123
+ root: pathlib.Path = pathlib.Path(),
124
+ ) -> LimitsProfile:
125
+ if profile is None:
126
+ return get_package_limits_profile(root=root)
127
+ saved_profile = get_saved_limits_profile(profile, root=root)
128
+ if saved_profile is None:
129
+ if fallback_to_package_profile:
130
+ return get_package_limits_profile(root=root)
131
+ console.console.print(
132
+ f'[error]Limits profile [item]{profile}[/item] not found.[/error]'
133
+ )
134
+ raise typer.Exit(1)
135
+ return _expand_limits_profile(saved_profile, root=root)
136
+
137
+
138
+ def get_limits(
139
+ language: Optional[str] = None,
140
+ profile: Optional[str] = None,
141
+ fallback_to_package_profile: bool = True,
142
+ verification: VerificationLevel = VerificationLevel.NONE,
143
+ root: pathlib.Path = pathlib.Path(),
144
+ ) -> Limits:
145
+ source_profile = None
146
+ limits_profile = LimitsProfile(inheritFromPackage=True)
147
+ if profile is not None:
148
+ specified_limits_profile = get_saved_limits_profile(profile, root=root)
149
+ if specified_limits_profile is not None:
150
+ limits_profile = specified_limits_profile
151
+ source_profile = profile
152
+ elif not fallback_to_package_profile:
153
+ console.console.print(
154
+ f'[error]Limits profile [item]{profile}[/item] not found.[/error]'
155
+ )
156
+ raise typer.Exit(1)
157
+
158
+ res = _get_limits_from_profile(
159
+ language, limits_profile, source_profile, verification, root=root
160
+ )
161
+ return res
rbx/box/package.py CHANGED
@@ -12,6 +12,7 @@ from rbx import console, utils
12
12
  from rbx.box import cd, global_package
13
13
  from rbx.box.environment import get_sandbox_type
14
14
  from rbx.box.global_package import get_cache_fingerprint
15
+ from rbx.box.sanitizers import issue_stack
15
16
  from rbx.box.schema import (
16
17
  CodeItem,
17
18
  ExpectedOutcome,
@@ -80,7 +81,13 @@ def within_problem(func):
80
81
  @functools.wraps(func)
81
82
  def wrapper(*args, **kwargs):
82
83
  with cd.new_package_cd(find_problem()):
83
- return func(*args, **kwargs)
84
+ issue_level_token = issue_stack.issue_level_var.set(
85
+ issue_stack.IssueLevel.DETAILED
86
+ )
87
+ ret = func(*args, **kwargs)
88
+ issue_stack.print_current_report()
89
+ issue_stack.issue_level_var.reset(issue_level_token)
90
+ return ret
84
91
 
85
92
  return wrapper
86
93
 
@@ -147,6 +154,16 @@ def get_problem_iruns_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
147
154
  return iruns_dir
148
155
 
149
156
 
157
+ def get_limits_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
158
+ limits_dir = root / '.limits'
159
+ limits_dir.mkdir(parents=True, exist_ok=True)
160
+ return limits_dir
161
+
162
+
163
+ def get_limits_file(profile: str, root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
164
+ return get_limits_dir(root) / f'{profile}.yml'
165
+
166
+
150
167
  def get_problem_preprocessed_path(
151
168
  item: pathlib.Path, root: pathlib.Path = pathlib.Path()
152
169
  ) -> pathlib.Path:
@@ -0,0 +1,26 @@
1
+ import typing
2
+
3
+ from rbx.box.environment import get_environment, get_language
4
+ from rbx.box.packaging.boca.extension import BocaLanguage, BocaLanguageExtension
5
+
6
+
7
+ def get_rbx_language_from_boca_language(boca_language: BocaLanguage) -> str:
8
+ for language in get_environment().languages:
9
+ language_extension = language.get_extension_or_default(
10
+ language.name, BocaLanguageExtension
11
+ )
12
+ if language_extension.bocaLanguage == boca_language:
13
+ return language.name
14
+ return boca_language
15
+
16
+
17
+ def get_boca_language_from_rbx_language(rbx_language: str) -> BocaLanguage:
18
+ language = get_language(rbx_language)
19
+ language_extension = language.get_extension_or_default(
20
+ 'boca', BocaLanguageExtension
21
+ )
22
+ if language_extension.bocaLanguage:
23
+ return typing.cast(BocaLanguage, language_extension.bocaLanguage)
24
+ if rbx_language.lower() in typing.get_args(BocaLanguage):
25
+ return typing.cast(BocaLanguage, rbx_language.lower())
26
+ raise ValueError(f'No Boca language found for Rbx language {rbx_language}')
@@ -0,0 +1,10 @@
1
+ from rbx.box.schema import ExpectedOutcome
2
+
3
+
4
+ def simplify_rbx_expected_outcome(outcome: ExpectedOutcome) -> ExpectedOutcome:
5
+ if outcome in [
6
+ ExpectedOutcome.OUTPUT_LIMIT_EXCEEDED,
7
+ ExpectedOutcome.MEMORY_LIMIT_EXCEEDED,
8
+ ]:
9
+ return ExpectedOutcome.RUNTIME_ERROR
10
+ return outcome
@@ -6,7 +6,7 @@ from typing import List
6
6
  import typer
7
7
 
8
8
  from rbx import console
9
- from rbx.box import header, naming, package
9
+ from rbx.box import header, limits_info, naming, package
10
10
  from rbx.box.environment import get_extension_or_default
11
11
  from rbx.box.packaging.boca.extension import BocaExtension, BocaLanguage
12
12
  from rbx.box.packaging.packager import BasePackager, BuiltStatement
@@ -71,12 +71,14 @@ class BocaPackager(BasePackager):
71
71
  )
72
72
 
73
73
  def _get_pkg_timelimit(self, language: BocaLanguage) -> int:
74
- pkg = package.find_problem_package_or_die()
75
- return pkg.timelimit_for_language(language)
74
+ limits = limits_info.get_limits(language, profile='boca')
75
+ assert limits.time is not None
76
+ return limits.time
76
77
 
77
78
  def _get_pkg_memorylimit(self, language: BocaLanguage) -> int:
78
- pkg = package.find_problem_package_or_die()
79
- return pkg.memorylimit_for_language(language)
79
+ limits = limits_info.get_limits(language, profile='boca')
80
+ assert limits.memory is not None
81
+ return limits.memory
80
82
 
81
83
  def _get_number_of_runs(self, language: BocaLanguage) -> int:
82
84
  pkg = package.find_problem_package_or_die()
@@ -6,7 +6,7 @@ import syncer
6
6
  import typer
7
7
 
8
8
  from rbx import annotations, console
9
- from rbx.box import cd, environment, package
9
+ from rbx.box import cd, environment, limits_info, package
10
10
  from rbx.box.contest import build_contest_statements, contest_package
11
11
  from rbx.box.packaging.packager import (
12
12
  BaseContestPackager,
@@ -28,6 +28,11 @@ async def run_contest_packager(
28
28
  ):
29
29
  contest = contest_package.find_contest_package_or_die()
30
30
 
31
+ if limits_info.get_saved_limits_profile(contest_packager_cls.name()) is not None:
32
+ console.console.print(
33
+ f'[warning]Using saved limits profile for [item]{contest_packager_cls.name()}[/item].[/warning]'
34
+ )
35
+
31
36
  # Build problem-level packages.
32
37
  built_packages = []
33
38
  for problem in contest.problems:
@@ -52,21 +57,24 @@ async def run_contest_packager(
52
57
  statement_types = packager.statement_types()
53
58
  built_statements = []
54
59
 
55
- for statement_type in statement_types:
56
- languages = packager.languages()
57
- for language in languages:
58
- statement = packager.get_statement_for_language(language)
59
- statement_path = build_contest_statements.build_statement(
60
- statement, contest, statement_type
61
- )
62
- built_statements.append(
63
- BuiltContestStatement(statement, statement_path, statement_type)
64
- )
60
+ with limits_info.use_profile(contest_packager_cls.name()):
61
+ for statement_type in statement_types:
62
+ languages = packager.languages()
63
+ for language in languages:
64
+ statement = packager.get_statement_for_language(language)
65
+ statement_path = build_contest_statements.build_statement(
66
+ statement, contest, statement_type
67
+ )
68
+ built_statements.append(
69
+ BuiltContestStatement(statement, statement_path, statement_type)
70
+ )
65
71
 
66
72
  console.console.print(f'Packaging contest for [item]{packager.name()}[/item]...')
67
73
 
68
74
  # Build contest-level package.
69
- with tempfile.TemporaryDirectory() as td:
75
+ with tempfile.TemporaryDirectory() as td, limits_info.use_profile(
76
+ contest_packager_cls.name()
77
+ ):
70
78
  result_path = packager.package(
71
79
  built_packages, pathlib.Path('build'), pathlib.Path(td), built_statements
72
80
  )
@@ -8,7 +8,7 @@ from typing import List, Tuple, Type
8
8
  import typer
9
9
 
10
10
  from rbx import console
11
- from rbx.box import environment, header, naming, package
11
+ from rbx.box import environment, header, limits_info, naming, package
12
12
  from rbx.box.contest import contest_package
13
13
  from rbx.box.contest.schema import ContestProblem, ContestStatement
14
14
  from rbx.box.formatting import href
@@ -103,8 +103,9 @@ class BasePackager(ABC):
103
103
 
104
104
 
105
105
  class BaseContestPackager(ABC):
106
+ @classmethod
106
107
  @abstractmethod
107
- def name(self) -> str:
108
+ def name(cls) -> str:
108
109
  pass
109
110
 
110
111
  @abstractmethod
@@ -180,11 +181,17 @@ async def run_packager(
180
181
 
181
182
  header.generate_header()
182
183
 
183
- if not await builder.verify(verification=verification):
184
+ if limits_info.get_saved_limits_profile(packager_cls.name()) is not None:
184
185
  console.console.print(
185
- '[error]Build or verification failed, check the report.[/error]'
186
+ f'[warning]Using saved limits profile for [item]{packager_cls.name()}[/item].[/warning]'
186
187
  )
187
- raise typer.Exit(1)
188
+
189
+ with limits_info.use_profile(packager_cls.name()):
190
+ if not await builder.verify(verification=verification):
191
+ console.console.print(
192
+ '[error]Build or verification failed, check the report.[/error]'
193
+ )
194
+ raise typer.Exit(1)
188
195
 
189
196
  pkg = package.find_problem_package_or_die()
190
197
 
@@ -199,18 +206,21 @@ async def run_packager(
199
206
  statement_types = packager.statement_types()
200
207
  built_statements = []
201
208
 
202
- for statement_type in statement_types:
203
- languages = packager.languages()
204
- for language in languages:
205
- statement = packager.get_statement_for_language(language)
206
- statement_path = build_statement(statement, pkg, statement_type)
207
- built_statements.append(
208
- BuiltStatement(statement, statement_path, statement_type)
209
- )
209
+ with limits_info.use_profile(packager_cls.name()):
210
+ for statement_type in statement_types:
211
+ languages = packager.languages()
212
+ for language in languages:
213
+ statement = packager.get_statement_for_language(language)
214
+ statement_path = build_statement(statement, pkg, statement_type)
215
+ built_statements.append(
216
+ BuiltStatement(statement, statement_path, statement_type)
217
+ )
210
218
 
211
219
  console.console.print(f'Packaging problem for [item]{packager.name()}[/item]...')
212
220
 
213
- with tempfile.TemporaryDirectory() as td:
221
+ with tempfile.TemporaryDirectory() as td, limits_info.use_profile(
222
+ packager_cls.name()
223
+ ):
214
224
  result_path = packager.package(
215
225
  package.get_build_path(), pathlib.Path(td), built_statements
216
226
  )
@@ -5,7 +5,7 @@ from typing import List, Optional
5
5
  import typer
6
6
 
7
7
  from rbx import console, utils
8
- from rbx.box import header, package
8
+ from rbx.box import header, limits_info, package
9
9
  from rbx.box.lang import code_to_lang, code_to_langs, is_valid_lang_code
10
10
  from rbx.box.packaging.packager import (
11
11
  BaseContestPackager,
@@ -123,10 +123,14 @@ class PolygonPackager(BasePackager):
123
123
 
124
124
  testcases = self.get_flattened_built_testcases()
125
125
 
126
+ limits = limits_info.get_limits(None, profile='polygon')
127
+ assert limits.time is not None
128
+ assert limits.memory is not None
129
+
126
130
  return polygon_schema.Testset(
127
131
  name='tests',
128
- timelimit=pkg.timelimit_for_language(None),
129
- memorylimit=pkg.memorylimit_for_language(None) * 1024 * 1024,
132
+ timelimit=limits.time,
133
+ memorylimit=limits.memory * 1024 * 1024,
130
134
  size=len(testcases),
131
135
  inputPattern='tests/%03d',
132
136
  answerPattern='tests/%03d.a',
@@ -8,7 +8,7 @@ import rich.progress
8
8
  import typer
9
9
 
10
10
  from rbx import console
11
- from rbx.box import header, package
11
+ from rbx.box import header, limits_info, package
12
12
  from rbx.box.generators import get_all_built_testcases
13
13
  from rbx.box.lang import code_to_langs, is_valid_lang_code
14
14
  from rbx.box.packaging.polygon import polygon_api as api
@@ -250,6 +250,7 @@ def _get_statement_blocks(statement: Statement) -> StatementBlocks:
250
250
  pkg = package.find_problem_package_or_die()
251
251
  # TODO: pull this from a library, too hacky at the moment
252
252
  builder_problem = StatementBuilderProblem(
253
+ limits=limits_info.get_limits_profile(profile='polygon'),
253
254
  package=pkg,
254
255
  statement=statement,
255
256
  vars={