rbx.cp 0.5.16__tar.gz → 0.5.18__tar.gz

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 (169) hide show
  1. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/PKG-INFO +2 -1
  2. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/pyproject.toml +2 -1
  3. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/builder.py +2 -2
  4. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/cd.py +12 -2
  5. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/checkers.py +38 -4
  6. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/code.py +128 -18
  7. rbx_cp-0.5.18/rbx/box/compile.py +82 -0
  8. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/contest/build_contest_statements.py +7 -6
  9. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/contest/contest_package.py +15 -2
  10. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/contest/contest_utils.py +0 -9
  11. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/contest/main.py +35 -24
  12. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/contest/statements.py +4 -5
  13. rbx_cp-0.5.18/rbx/box/deferred.py +26 -0
  14. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/extensions.py +0 -8
  15. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/generators.py +6 -4
  16. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/main.py +195 -19
  17. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/package.py +24 -2
  18. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/packaging/contest_main.py +5 -5
  19. rbx_cp-0.5.18/rbx/box/sanitizers/warning_stack.py +90 -0
  20. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/schema.py +15 -1
  21. rbx_cp-0.5.18/rbx/box/setter_config.py +132 -0
  22. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/solutions.py +237 -149
  23. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/solutions_test.py +2 -1
  24. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/stresses.py +5 -3
  25. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/validators.py +5 -5
  26. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/config.py +3 -0
  27. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/caching.py +4 -0
  28. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/sandboxes/stupid_sandbox.py +2 -0
  29. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/sandboxes/timeit.py +3 -3
  30. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/steps.py +143 -15
  31. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/steps_with_caching.py +2 -0
  32. rbx_cp-0.5.18/rbx/resources/default_setter_config.mac.yml +31 -0
  33. rbx_cp-0.5.18/rbx/resources/default_setter_config.yml +29 -0
  34. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/utils.py +8 -1
  35. rbx_cp-0.5.16/rbx/box/compile.py +0 -50
  36. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/LICENSE +0 -0
  37. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/README.md +0 -0
  38. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/__init__.py +0 -0
  39. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/annotations.py +0 -0
  40. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/autoenum.py +0 -0
  41. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/__init__.py +0 -0
  42. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/conftest.py +0 -0
  43. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/contest/__init__.py +0 -0
  44. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/contest/schema.py +0 -0
  45. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/creation.py +0 -0
  46. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/download.py +0 -0
  47. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/environment.py +0 -0
  48. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/generators_test.py +0 -0
  49. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/packaging/boca/extension.py +0 -0
  50. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/packaging/boca/packager.py +0 -0
  51. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/packaging/main.py +0 -0
  52. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/packaging/packager.py +0 -0
  53. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/packaging/polygon/packager.py +0 -0
  54. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/packaging/polygon/test.py +0 -0
  55. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/packaging/polygon/xml_schema.py +0 -0
  56. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/presets/__init__.py +0 -0
  57. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/presets/fetch.py +0 -0
  58. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/presets/lock_schema.py +0 -0
  59. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/presets/schema.py +0 -0
  60. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/statements/__init__.py +0 -0
  61. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/statements/build_statements.py +0 -0
  62. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/statements/builders.py +0 -0
  63. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/statements/joiners.py +0 -0
  64. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/statements/latex.py +0 -0
  65. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/statements/latex_jinja.py +0 -0
  66. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/statements/schema.py +0 -0
  67. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/stressing/__init__.py +0 -0
  68. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/stressing/finder_parser.py +0 -0
  69. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/stressing/generator_parser.py +0 -0
  70. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/testcases.py +0 -0
  71. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/ui/__init__.py +0 -0
  72. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/ui/captured_log.py +0 -0
  73. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/ui/css/app.tcss +0 -0
  74. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/ui/main.py +0 -0
  75. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/ui/run.py +0 -0
  76. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/box/validators_test.py +0 -0
  77. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/checker.py +0 -0
  78. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/clone.py +0 -0
  79. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/conftest.py +0 -0
  80. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/console.py +0 -0
  81. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/create.py +0 -0
  82. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/edit.py +0 -0
  83. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/__init__.py +0 -0
  84. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/conftest.py +0 -0
  85. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/__init__.py +0 -0
  86. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/cacher.py +0 -0
  87. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/digester.py +0 -0
  88. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/sandbox.py +0 -0
  89. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/sandboxes/__init__.py +0 -0
  90. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/sandboxes/isolate.py +0 -0
  91. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/storage.py +0 -0
  92. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/test.py +0 -0
  93. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/judge/testiso.py +0 -0
  94. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading/steps_with_caching_run_test.py +0 -0
  95. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/grading_utils.py +0 -0
  96. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/hydration.py +0 -0
  97. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/main.py +0 -0
  98. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/metadata.py +0 -0
  99. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/providers/__init__.py +0 -0
  100. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/providers/codeforces.py +0 -0
  101. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/providers/provider.py +0 -0
  102. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/checkers/boilerplate.cpp +0 -0
  103. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/default_config.json +0 -0
  104. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/envs/default.rbx.yml +0 -0
  105. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/envs/isolate.rbx.yml +0 -0
  106. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/checker.sh +0 -0
  107. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/compare +0 -0
  108. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/compile/c +0 -0
  109. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/compile/cc +0 -0
  110. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/compile/cpp +0 -0
  111. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/compile/java +0 -0
  112. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/compile/kt +0 -0
  113. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/compile/pas +0 -0
  114. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/compile/py2 +0 -0
  115. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/compile/py3 +0 -0
  116. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/run/c +0 -0
  117. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/run/cc +0 -0
  118. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/run/cpp +0 -0
  119. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/run/java +0 -0
  120. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/run/kt +0 -0
  121. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/run/py2 +0 -0
  122. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/packagers/boca/run/py3 +0 -0
  123. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/contest/contest.rbx.yml +0 -0
  124. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/contest/statement/contest.rbx.tex +0 -0
  125. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/contest/statement/olymp.sty +0 -0
  126. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -0
  127. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/preset.rbx.yml +0 -0
  128. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/.gitignore +0 -0
  129. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/gen.cpp +0 -0
  130. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/problem.rbx.yml +0 -0
  131. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/random.py +0 -0
  132. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/random.txt +0 -0
  133. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/sols/main.cpp +0 -0
  134. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/sols/slow.cpp +0 -0
  135. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/sols/wa.cpp +0 -0
  136. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/statement/olymp.sty +0 -0
  137. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  138. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/statement/statement.rbx.tex +0 -0
  139. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -0
  140. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/tests/samples/000.in +0 -0
  141. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/tests/samples/001.in +0 -0
  142. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/validator.cpp +0 -0
  143. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/presets/default/problem/wcmp.cpp +0 -0
  144. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/resources/templates/template.cpp +0 -0
  145. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/run.py +0 -0
  146. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/schema.py +0 -0
  147. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/submit.py +0 -0
  148. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/submitors/__init__.py +0 -0
  149. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/submitors/codeforces.py +0 -0
  150. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/submitors/submitor.py +0 -0
  151. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/test.py +0 -0
  152. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testcase.py +0 -0
  153. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testcase_rendering.py +0 -0
  154. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/gen1.cpp +0 -0
  155. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/gen2.cpp +0 -0
  156. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/genScript.py +0 -0
  157. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/hard-tle.sol.cpp +0 -0
  158. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/ole.cpp +0 -0
  159. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/problem.rbx.yml +0 -0
  160. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/re.sol.cpp +0 -0
  161. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/sol.cpp +0 -0
  162. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/tests/1.in +0 -0
  163. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/tle-and-incorrect.sol.cpp +0 -0
  164. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/tle.sol.cpp +0 -0
  165. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/validator.cpp +0 -0
  166. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/box1/wa.sol.cpp +0 -0
  167. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/caching/executable.py +0 -0
  168. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testdata/compatible +0 -0
  169. {rbx_cp-0.5.16 → rbx_cp-0.5.18}/rbx/testing_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rbx.cp
3
- Version: 0.5.16
3
+ Version: 0.5.18
4
4
  Summary:
5
5
  Author: Roberto Sales
6
6
  Requires-Python: >=3.9,<4.0
@@ -27,6 +27,7 @@ Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
27
27
  Requires-Dist: questionary (>=2.1.0,<3.0.0)
28
28
  Requires-Dist: requests (>=2.32.3,<3.0.0)
29
29
  Requires-Dist: rich (>=13.9.4,<14.0.0)
30
+ Requires-Dist: ruyaml (>=0.91.0,<0.92.0)
30
31
  Requires-Dist: textual (>=0.79.1,<0.80.0)
31
32
  Requires-Dist: typer (>=0.15.1,<0.16.0)
32
33
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "rbx.cp"
3
- version = "0.5.16"
3
+ version = "0.5.18"
4
4
  description = ""
5
5
  packages = [
6
6
  {include = "rbx"}
@@ -30,6 +30,7 @@ pyte = "^0.8.2"
30
30
  questionary = "^2.1.0"
31
31
  lark = "^1.2.2"
32
32
  chardet = "^5.2.0"
33
+ ruyaml = "^0.91.0"
33
34
 
34
35
  [tool.poetry.scripts]
35
36
  rbc = "rbx.main:app"
@@ -76,7 +76,7 @@ def build(
76
76
  return True
77
77
 
78
78
 
79
- def verify(verification: environment.VerificationParam) -> bool:
79
+ async def verify(verification: environment.VerificationParam) -> bool:
80
80
  if not build(verification=verification):
81
81
  return False
82
82
 
@@ -100,7 +100,7 @@ def verify(verification: environment.VerificationParam) -> bool:
100
100
 
101
101
  console.console.print()
102
102
  console.console.rule('[status]Run report[/status]', style='status')
103
- return print_run_report(
103
+ return await print_run_report(
104
104
  solution_result,
105
105
  console.console,
106
106
  verification,
@@ -1,10 +1,13 @@
1
+ import contextlib
1
2
  import functools
2
3
  import pathlib
3
4
  from typing import Optional
4
5
 
5
6
  import typer
6
7
 
7
- from rbx import console, utils
8
+ from rbx import console
9
+ from rbx.box.sanitizers import warning_stack
10
+ from rbx.utils import new_cd
8
11
 
9
12
 
10
13
  def find_package(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
@@ -30,7 +33,14 @@ def within_closest_package(func):
30
33
  console.console.print('[error]No rbx package found.[/error]')
31
34
  raise typer.Exit(1)
32
35
  # Get deepest package.
33
- with utils.new_cd(package):
36
+ with new_package_cd(package):
34
37
  return func(*args, **kwargs)
35
38
 
36
39
  return wrapper
40
+
41
+
42
+ @contextlib.contextmanager
43
+ def new_package_cd(x: pathlib.Path):
44
+ with new_cd(x):
45
+ yield
46
+ warning_stack.print_warning_stack_report()
@@ -5,7 +5,7 @@ import typer
5
5
 
6
6
  from rbx import console
7
7
  from rbx.box import package
8
- from rbx.box.code import compile_item, run_item
8
+ from rbx.box.code import SanitizationLevel, compile_item, run_item
9
9
  from rbx.box.schema import Testcase
10
10
  from rbx.grading.judge.sandbox import SandboxBase
11
11
  from rbx.grading.steps import (
@@ -23,7 +23,7 @@ def compile_checker() -> str:
23
23
  checker = package.get_checker()
24
24
 
25
25
  try:
26
- digest = compile_item(checker)
26
+ digest = compile_item(checker, sanitized=SanitizationLevel.PREFER)
27
27
  except Exception as e:
28
28
  console.console.print('[error]Failed compiling checker.[/error]')
29
29
  raise typer.Exit(1) from e
@@ -33,11 +33,21 @@ def compile_checker() -> str:
33
33
  def _check_pre_output(run_log: Optional[RunLog]) -> CheckerResult:
34
34
  pkg = package.find_problem_package_or_die()
35
35
 
36
+ is_sanitized = (
37
+ run_log is not None
38
+ and run_log.metadata is not None
39
+ and run_log.metadata.is_sanitized
40
+ )
41
+
36
42
  if run_log is None:
37
43
  return CheckerResult(outcome=Outcome.INTERNAL_ERROR)
38
44
 
39
45
  timelimit = pkg.timelimit_for_language(run_log.get_run_language())
40
- if run_log.time is not None and run_log.time * 1000 > timelimit * 2:
46
+ if (
47
+ run_log.time is not None
48
+ and run_log.time * 1000 > timelimit * 2
49
+ and not is_sanitized
50
+ ):
41
51
  return CheckerResult(outcome=Outcome.TIME_LIMIT_EXCEEDED)
42
52
 
43
53
  if run_log.exitstatus in [SandboxBase.EXIT_SIGNAL, SandboxBase.EXIT_NONZERO_RETURN]:
@@ -58,11 +68,17 @@ def _convert_tle(result: CheckerResult, run_log: Optional[RunLog]) -> CheckerRes
58
68
  # This already is a TLE outcome.
59
69
  return result
60
70
  pkg = package.find_problem_package_or_die()
71
+ is_sanitized = (
72
+ run_log is not None
73
+ and run_log.metadata is not None
74
+ and run_log.metadata.is_sanitized
75
+ )
61
76
  if (
62
77
  run_log is not None
63
78
  and run_log.time is not None
64
79
  and run_log.time * 1000
65
80
  >= pkg.timelimit_for_language(run_log.get_run_language())
81
+ and not is_sanitized
66
82
  ):
67
83
  # Soft TLE.
68
84
  result.no_tle_outcome = result.outcome
@@ -75,7 +91,7 @@ def check_with_no_output(run_log: Optional[RunLog]) -> CheckerResult:
75
91
  return _convert_tle(result, run_log)
76
92
 
77
93
 
78
- def check(
94
+ def _check(
79
95
  checker_digest: str,
80
96
  run_log: Optional[RunLog],
81
97
  testcase: Testcase,
@@ -157,3 +173,21 @@ def check(
157
173
  if skip_run_log:
158
174
  return result
159
175
  return _convert_tle(result, run_log)
176
+
177
+
178
+ def _check_sanitizer_warnings(run_log: Optional[RunLog]) -> bool:
179
+ if run_log is None:
180
+ return False
181
+ return run_log.warnings
182
+
183
+
184
+ def check(
185
+ checker_digest: str,
186
+ run_log: Optional[RunLog],
187
+ testcase: Testcase,
188
+ program_output: pathlib.Path,
189
+ skip_run_log: bool = False,
190
+ ) -> CheckerResult:
191
+ result = _check(checker_digest, run_log, testcase, program_output, skip_run_log)
192
+ result.sanitizer_warnings = _check_sanitizer_warnings(run_log)
193
+ return result
@@ -1,17 +1,19 @@
1
1
  import pathlib
2
2
  import shlex
3
- import sys
3
+ from enum import Enum
4
4
  from pathlib import PosixPath
5
5
  from typing import List, Optional
6
6
 
7
+ import rich
8
+ import rich.text
7
9
  import typer
8
10
 
9
- from rbx.box import download, package
11
+ from rbx import console
12
+ from rbx.box import download, package, setter_config
10
13
  from rbx.box.environment import (
11
14
  ExecutionConfig,
12
15
  get_compilation_config,
13
16
  get_execution_config,
14
- get_extension_or_default,
15
17
  get_file_mapping,
16
18
  get_language,
17
19
  get_mapped_command,
@@ -19,7 +21,7 @@ from rbx.box.environment import (
19
21
  get_sandbox_params_from_config,
20
22
  merge_execution_configs,
21
23
  )
22
- from rbx.box.extensions import MacExtension
24
+ from rbx.box.sanitizers import warning_stack
23
25
  from rbx.box.schema import CodeItem
24
26
  from rbx.grading import steps_with_caching
25
27
  from rbx.grading.steps import (
@@ -31,17 +33,25 @@ from rbx.grading.steps import (
31
33
  GradingFileOutput,
32
34
  RunLog,
33
35
  RunLogMetadata,
36
+ is_cxx_command,
34
37
  )
35
38
 
36
39
 
37
- def normalize_for_macos(commands: List[str]) -> List[str]:
38
- def normalize(command: str) -> str:
39
- extension = get_extension_or_default('mac', MacExtension)
40
- if extension.gpp_alternative is None:
41
- return command
42
- return command.replace('g++', extension.gpp_alternative)
40
+ class SanitizationLevel(Enum):
41
+ NONE = 0
42
+ PREFER = 1
43
+ FORCE = 2
43
44
 
44
- return [normalize(command) for command in commands]
45
+ def should_sanitize(self) -> bool:
46
+ cfg = setter_config.get_setter_config()
47
+ if cfg.sanitizers.enabled:
48
+ return self.value >= SanitizationLevel.PREFER.value
49
+ return self.value >= SanitizationLevel.FORCE.value
50
+
51
+
52
+ def substitute_commands(commands: List[str], sanitized: bool = False) -> List[str]:
53
+ cfg = setter_config.get_setter_config()
54
+ return [cfg.substitute_command(command, sanitized) for command in commands]
45
55
 
46
56
 
47
57
  def get_extension(code: CodeItem) -> str:
@@ -55,8 +65,46 @@ def find_language_name(code: CodeItem) -> str:
55
65
  return get_language(get_extension(code)).name
56
66
 
57
67
 
68
+ def is_executable_sanitized(executable: DigestOrSource) -> bool:
69
+ if executable.digest is None:
70
+ return False
71
+ storage = package.get_cache_storage()
72
+ return storage.exists(f'{executable.digest.value}.san')
73
+
74
+
75
+ def add_sanitizer_flags_to_command(command: str) -> str:
76
+ if is_cxx_command(command):
77
+ return command + ' -fsanitize=address,undefined -fno-omit-frame-pointer'
78
+ return command
79
+
80
+
81
+ def add_sanitizer_flags(commands: List[str]) -> List[str]:
82
+ return [add_sanitizer_flags_to_command(command) for command in commands]
83
+
84
+
85
+ def add_warning_flags_to_command(command: str) -> str:
86
+ if is_cxx_command(command):
87
+ return (
88
+ command
89
+ + ' -Wall -Wshadow -Wno-unused-result -Wno-sign-compare -Wno-char-subscripts'
90
+ )
91
+ return command
92
+
93
+
94
+ def add_warning_flags(commands: List[str], force_warnings: bool) -> List[str]:
95
+ cfg = setter_config.get_setter_config()
96
+ if cfg.warnings.enabled or force_warnings:
97
+ return [add_warning_flags_to_command(command) for command in commands]
98
+ return commands
99
+
100
+
58
101
  # Compile code item and return its digest in the storage.
59
- def compile_item(code: CodeItem) -> str:
102
+ def compile_item(
103
+ code: CodeItem,
104
+ sanitized: SanitizationLevel = SanitizationLevel.PREFER,
105
+ force_warnings: bool = False,
106
+ verbose: bool = False,
107
+ ) -> str:
60
108
  generator_path = PosixPath(code.path)
61
109
  language = find_language_name(code)
62
110
  compilation_options = get_compilation_config(language)
@@ -69,10 +117,20 @@ def compile_item(code: CodeItem) -> str:
69
117
  # Language is not compiled.
70
118
  return sandbox.file_cacher.put_file_from_path(generator_path)
71
119
 
72
- # Compile the generator
73
120
  commands = get_mapped_commands(compilation_options.commands, file_mapping)
74
- if sys.platform == 'darwin':
75
- commands = normalize_for_macos(commands)
121
+ commands = add_warning_flags(commands, force_warnings)
122
+ commands = substitute_commands(commands, sanitized=sanitized.should_sanitize())
123
+
124
+ if sanitized.should_sanitize():
125
+ commands = add_sanitizer_flags(commands)
126
+
127
+ # Remove any memory constraints for a sanitized executable.
128
+ # Sanitizers are known to be memory-hungry.
129
+ sandbox_params.address_space = None
130
+
131
+ # Reset timeout configs since sanitizers are known to be time-hungry.
132
+ sandbox_params.timeout = None
133
+ sandbox_params.wallclock_timeout = None
76
134
 
77
135
  compiled_digest = DigestHolder()
78
136
 
@@ -104,6 +162,33 @@ def compile_item(code: CodeItem) -> str:
104
162
  raise typer.Exit(1)
105
163
 
106
164
  assert compiled_digest.value is not None
165
+
166
+ if verbose and artifacts.logs is not None and artifacts.logs.preprocess is not None:
167
+ for log in artifacts.logs.preprocess:
168
+ console.console.print(f'[status]Command:[/status] {log.get_command()}')
169
+ console.console.print(f'[status]Summary:[/status] {log.get_summary()}')
170
+ console.console.print(rich.text.Text.from_ansi(log.log), style='default')
171
+
172
+ # Write compiler warnings.
173
+ cfg = setter_config.get_setter_config()
174
+ if (
175
+ (cfg.warnings.enabled or force_warnings)
176
+ and artifacts.logs is not None
177
+ and artifacts.logs.preprocess is not None
178
+ ):
179
+ any_warning = any(log.warnings for log in artifacts.logs.preprocess)
180
+ if any_warning:
181
+ warning_stack.get_warning_stack().add_warning(code)
182
+
183
+ # Create sentinel to indicate this executable is sanitized.
184
+ storage = package.get_cache_storage()
185
+ if sanitized.should_sanitize():
186
+ pf = storage.create_file(f'{compiled_digest.value}.san')
187
+ if pf is not None:
188
+ storage.commit_file(pf)
189
+ elif storage.exists(f'{compiled_digest.value}.san'):
190
+ storage.delete(f'{compiled_digest.value}.san')
191
+
107
192
  return compiled_digest.value
108
193
 
109
194
 
@@ -127,14 +212,29 @@ def run_item(
127
212
  sandbox = package.get_singleton_sandbox()
128
213
  sandbox_params = get_sandbox_params_from_config(execution_options.sandbox)
129
214
 
215
+ # Sanitization parameters.
216
+ sanitized = False
217
+ if is_executable_sanitized(executable):
218
+ # Remove any memory constraints for a sanitized executable.
219
+ # Sanitizers are known to be memory-hungry.
220
+ sandbox_params.address_space = None
221
+
222
+ # Reset timeout configs since sanitizers are known to be time-hungry.
223
+ sandbox_params.timeout = None
224
+ sandbox_params.wallclock_timeout = None
225
+ sanitized = True
226
+
130
227
  sandbox_params.set_stdall(
131
228
  stdin=PosixPath(file_mapping.input) if stdin is not None else None,
132
229
  stdout=PosixPath(file_mapping.output) if stdout is not None else None,
133
- stderr=PosixPath(file_mapping.error) if stderr is not None else None,
230
+ stderr=PosixPath(file_mapping.error)
231
+ if stderr is not None or sanitized
232
+ else None,
134
233
  )
135
234
 
136
235
  assert execution_options.command
137
236
  command = get_mapped_command(execution_options.command, file_mapping)
237
+ command = substitute_commands([command], sanitized=sanitized)[0]
138
238
 
139
239
  if extra_args is not None:
140
240
  splitted_command = shlex.split(command)
@@ -175,11 +275,21 @@ def run_item(
175
275
  if outputs:
176
276
  artifacts.outputs.extend(outputs)
177
277
 
178
- return steps_with_caching.run(
278
+ run_log = steps_with_caching.run(
179
279
  command,
180
280
  params=sandbox_params,
181
281
  sandbox=sandbox,
182
282
  artifacts=artifacts,
183
283
  dependency_cache=dependency_cache,
184
- metadata=RunLogMetadata(language=code.language),
284
+ metadata=RunLogMetadata(language=code.language, is_sanitized=sanitized),
185
285
  )
286
+
287
+ # Find sanitizer logs.
288
+ if run_log is not None and run_log.warnings:
289
+ assert sandbox_params.stderr_file is not None
290
+ stderr_output = artifacts.get_output_file_for_src(sandbox_params.stderr_file)
291
+ if stderr_output is not None:
292
+ warning_stack.get_warning_stack().add_sanitizer_warning(
293
+ package.get_cache_storage(), code, stderr_output
294
+ )
295
+ return run_log
@@ -0,0 +1,82 @@
1
+ import pathlib
2
+
3
+ import typer
4
+
5
+ from rbx import annotations, console
6
+ from rbx.box import code, package
7
+ from rbx.box.code import SanitizationLevel
8
+ from rbx.box.sanitizers import warning_stack
9
+ from rbx.box.schema import CodeItem
10
+
11
+ app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
12
+
13
+
14
+ def _compile_out():
15
+ return package.get_build_path() / 'exe'
16
+
17
+
18
+ def _compile(item: CodeItem, sanitized: SanitizationLevel, warnings: bool):
19
+ console.console.print(f'Compiling [item]{item.path}[/item]...')
20
+ digest = code.compile_item(item, sanitized, force_warnings=warnings, verbose=True)
21
+ cacher = package.get_file_cacher()
22
+ out_path = _compile_out()
23
+ cacher.get_file_to_path(digest, out_path)
24
+ out_path.chmod(0o755)
25
+
26
+ # Clear the warning stack as we don't really worry about it when running
27
+ # `rbx compile`.
28
+ warning_stack.get_warning_stack().clear()
29
+
30
+ console.console.print(
31
+ f'[success]Compiled file written at [item]{out_path}[/item][/success]'
32
+ )
33
+
34
+
35
+ def any(path: str, sanitized: bool = False, warnings: bool = False):
36
+ pkg = package.find_problem_package_or_die()
37
+
38
+ solution = package.get_solution_or_nil(path)
39
+ if solution is not None:
40
+ _compile(
41
+ solution,
42
+ sanitized=SanitizationLevel.FORCE if sanitized else SanitizationLevel.NONE,
43
+ warnings=warnings,
44
+ )
45
+ return
46
+
47
+ for generator in pkg.generators:
48
+ if generator.path == pathlib.Path(path) or generator.name == path:
49
+ _compile(
50
+ generator,
51
+ sanitized=SanitizationLevel.FORCE
52
+ if sanitized
53
+ else SanitizationLevel.PREFER,
54
+ warnings=warnings,
55
+ )
56
+ return
57
+
58
+ if pkg.checker is not None and pkg.checker.path == pathlib.Path(path):
59
+ _compile(
60
+ pkg.checker,
61
+ sanitized=SanitizationLevel.FORCE
62
+ if sanitized
63
+ else SanitizationLevel.PREFER,
64
+ warnings=warnings,
65
+ )
66
+ return
67
+
68
+ if pkg.validator is not None and pkg.validator.path == pathlib.Path(path):
69
+ _compile(
70
+ pkg.validator,
71
+ sanitized=SanitizationLevel.FORCE
72
+ if sanitized
73
+ else SanitizationLevel.PREFER,
74
+ warnings=warnings,
75
+ )
76
+ return
77
+
78
+ _compile(
79
+ CodeItem(path=pathlib.Path(path)),
80
+ sanitized=SanitizationLevel.FORCE if sanitized else SanitizationLevel.NONE,
81
+ warnings=warnings,
82
+ )
@@ -6,8 +6,8 @@ from typing import List, Optional, Tuple
6
6
 
7
7
  import typer
8
8
 
9
- from rbx import console, testing_utils, utils
10
- from rbx.box.contest import contest_utils
9
+ from rbx import console, testing_utils
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
13
13
  from rbx.box.schema import Package, Testcase
@@ -58,8 +58,8 @@ class ExtractedProblem:
58
58
 
59
59
 
60
60
  def _get_samples(problem: ContestProblem) -> List[Testcase]:
61
- with utils.new_cd(problem.get_path()):
62
- contest_utils.clear_package_cache()
61
+ with cd.new_package_cd(problem.get_path()):
62
+ package.clear_package_cache()
63
63
  return get_samples()
64
64
 
65
65
 
@@ -156,8 +156,8 @@ def _build_problem_statements(
156
156
  console.console.print(
157
157
  f'Building statement for problem {extracted_problem.problem.short_name}...'
158
158
  )
159
- with utils.new_cd(extracted_problem.problem.get_path()):
160
- contest_utils.clear_package_cache()
159
+ with cd.new_package_cd(extracted_problem.problem.get_path()):
160
+ package.clear_package_cache()
161
161
  # TODO: respect steps override
162
162
  content, _ = build_statements.build_statement_bytes(
163
163
  extracted_problem.statement,
@@ -282,6 +282,7 @@ def build_statement_rooted(
282
282
 
283
283
  if joiner is None:
284
284
  return last_content, last_output
285
+ assert statement.joiner is not None
285
286
 
286
287
  # Join statements.
287
288
  console.console.print('Joining statements...')
@@ -1,11 +1,13 @@
1
1
  import functools
2
2
  import pathlib
3
- from typing import List, Optional
3
+ from typing import List, Optional, Tuple
4
4
 
5
+ import ruyaml
5
6
  import typer
6
7
  from pydantic import ValidationError
7
8
 
8
9
  from rbx import console, utils
10
+ from rbx.box import cd
9
11
  from rbx.box.contest.schema import Contest
10
12
  from rbx.box.package import find_problem_package_or_die, warn_preset_deactivated
11
13
  from rbx.box.schema import Package
@@ -58,7 +60,7 @@ def find_contest(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
58
60
  def within_contest(func):
59
61
  @functools.wraps(func)
60
62
  def wrapper(*args, **kwargs):
61
- with utils.new_cd(find_contest()):
63
+ with cd.new_package_cd(find_contest()):
62
64
  return func(*args, **kwargs)
63
65
 
64
66
  return wrapper
@@ -80,3 +82,14 @@ def get_problems(contest: Contest) -> List[Package]:
80
82
  for problem in contest.problems:
81
83
  problems.append(find_problem_package_or_die(problem.get_path()))
82
84
  return problems
85
+
86
+
87
+ def get_ruyaml() -> Tuple[ruyaml.YAML, ruyaml.Any]:
88
+ contest_yaml_path = find_contest_yaml()
89
+ if contest_yaml_path is None:
90
+ console.console.print(
91
+ f'Contest not found in {pathlib.Path().absolute()}', style='error'
92
+ )
93
+ raise typer.Exit(1)
94
+ res = ruyaml.YAML()
95
+ return res, res.load(contest_yaml_path.read_text())
@@ -2,15 +2,6 @@ from rbx.box import environment, package
2
2
  from rbx.box.contest import contest_package
3
3
 
4
4
 
5
- def clear_package_cache():
6
- pkgs = [package]
7
-
8
- for pkg in pkgs:
9
- for fn in pkg.__dict__.values():
10
- if hasattr(fn, 'cache_clear'):
11
- fn.cache_clear()
12
-
13
-
14
5
  def clear_all_caches():
15
6
  pkgs = [package, environment, contest_package]
16
7
 
@@ -7,13 +7,12 @@ import rich.prompt
7
7
  import typer
8
8
 
9
9
  from rbx import annotations, console, utils
10
- from rbx.box import creation, presets
11
- from rbx.box.contest import contest_utils, statements
10
+ from rbx.box import cd, creation, presets
11
+ from rbx.box.contest import contest_package, contest_utils, statements
12
12
  from rbx.box.contest.contest_package import (
13
13
  find_contest,
14
14
  find_contest_package_or_die,
15
15
  find_contest_yaml,
16
- save_contest,
17
16
  within_contest,
18
17
  )
19
18
  from rbx.box.contest.schema import ContestProblem
@@ -111,7 +110,7 @@ def create(
111
110
  if local:
112
111
  shutil.copytree(str(preset_path), str(dest_path / '.local.rbx'))
113
112
 
114
- with utils.new_cd(dest_path):
113
+ with cd.new_package_cd(dest_path):
115
114
  contest_utils.clear_all_caches()
116
115
  presets.generate_lock(preset if not local else presets.LOCAL)
117
116
 
@@ -145,16 +144,19 @@ def add(path: str, short_name: str, preset: Optional[str] = None):
145
144
  creation.create(name, preset=preset, path=pathlib.Path(path))
146
145
 
147
146
  contest = find_contest_package_or_die()
148
- # Reassign mutable object before saving.
149
- contest.problems = sorted(
150
- [
151
- *contest.problems,
152
- ContestProblem(short_name=short_name, path=pathlib.Path(path)),
153
- ],
154
- key=lambda p: p.short_name,
147
+
148
+ ru, contest = contest_package.get_ruyaml()
149
+
150
+ contest['problems'].append(
151
+ {
152
+ 'short_name': short_name,
153
+ 'path': path,
154
+ }
155
155
  )
156
+ dest = find_contest_yaml()
157
+ assert dest is not None
158
+ utils.save_ruyaml(dest, ru, contest)
156
159
 
157
- save_contest(contest)
158
160
  console.console.print(
159
161
  f'Problem [item]{name} ({short_name})[/item] added to contest at [item]{path}[/item].'
160
162
  )
@@ -165,25 +167,34 @@ def add(path: str, short_name: str, preset: Optional[str] = None):
165
167
  def remove(path_or_short_name: str):
166
168
  contest = find_contest_package_or_die()
167
169
 
168
- kept_problems = []
169
- removed_problems = []
170
- for problem in contest.problems:
170
+ removed_problem_idx = None
171
+ removed_problem = None
172
+ for i, problem in enumerate(contest.problems):
171
173
  if (
172
174
  problem.path == pathlib.Path(path_or_short_name)
173
175
  or problem.short_name == path_or_short_name
174
176
  ):
175
- removed_problems.append(problem)
176
- else:
177
- kept_problems.append(problem)
177
+ removed_problem_idx = i
178
+ removed_problem = problem
179
+ break
178
180
 
179
- contest.problems = kept_problems
180
- save_contest(contest)
181
-
182
- for problem in removed_problems:
183
- shutil.rmtree(str(problem.path), ignore_errors=True)
181
+ if removed_problem_idx is None or removed_problem is None:
184
182
  console.console.print(
185
- f'Problem [item]{problem.short_name}[/item] removed from contest at [item]{problem.path}[/item].'
183
+ f'[error]Problem [item]{path_or_short_name}[/item] not found in contest.[/error]'
186
184
  )
185
+ raise typer.Exit(1)
186
+
187
+ ru, contest = contest_package.get_ruyaml()
188
+
189
+ del contest['problems'][removed_problem_idx]
190
+ dest = find_contest_yaml()
191
+ assert dest is not None
192
+ utils.save_ruyaml(dest, ru, contest)
193
+
194
+ shutil.rmtree(str(removed_problem.path), ignore_errors=True)
195
+ console.console.print(
196
+ f'Problem [item]{removed_problem.short_name}[/item] removed from contest at [item]{removed_problem.path}[/item].'
197
+ )
187
198
 
188
199
 
189
200
  @app.command(