rbx.cp 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. rbx/box/cd.py +2 -2
  2. rbx/box/cli.py +87 -33
  3. rbx/box/code.py +133 -84
  4. rbx/box/contest/build_contest_statements.py +2 -2
  5. rbx/box/contest/contest_package.py +1 -1
  6. rbx/box/contest/main.py +29 -2
  7. rbx/box/environment.py +140 -80
  8. rbx/box/formatting.py +2 -1
  9. rbx/box/global_package.py +74 -0
  10. rbx/box/package.py +11 -24
  11. rbx/box/packaging/__init__.py +0 -0
  12. rbx/box/packaging/boca/__init__.py +0 -0
  13. rbx/box/packaging/polygon/packager.py +3 -3
  14. rbx/box/presets/__init__.py +369 -53
  15. rbx/box/presets/lock_schema.py +42 -2
  16. rbx/box/presets/schema.py +4 -0
  17. rbx/box/remote.py +21 -2
  18. rbx/box/retries.py +3 -2
  19. rbx/box/sanitizers/warning_stack.py +5 -5
  20. rbx/box/solutions.py +37 -25
  21. rbx/box/statements/build_statements.py +6 -6
  22. rbx/box/statements/builders.py +1 -1
  23. rbx/box/stats.py +10 -0
  24. rbx/box/stresses.py +47 -66
  25. rbx/box/stressing/finder_parser.py +11 -16
  26. rbx/box/tasks.py +33 -22
  27. rbx/box/testcase_utils.py +3 -3
  28. rbx/box/tooling/boca/scraper.py +1 -1
  29. rbx/grading/caching.py +98 -47
  30. rbx/grading/debug_context.py +31 -0
  31. rbx/grading/grading_context.py +96 -0
  32. rbx/grading/judge/cacher.py +93 -21
  33. rbx/grading/judge/sandbox.py +8 -4
  34. rbx/grading/judge/sandboxes/isolate.py +3 -2
  35. rbx/grading/judge/sandboxes/stupid_sandbox.py +3 -2
  36. rbx/grading/judge/sandboxes/timeit.py +1 -1
  37. rbx/grading/judge/storage.py +170 -35
  38. rbx/grading/profiling.py +126 -0
  39. rbx/grading/steps.py +46 -17
  40. rbx/grading/steps_with_caching.py +52 -26
  41. rbx/resources/envs/default.rbx.yml +2 -3
  42. rbx/resources/envs/isolate.rbx.yml +2 -3
  43. rbx/resources/presets/default/contest/.gitignore +6 -0
  44. rbx/resources/presets/default/contest/contest.rbx.yml +14 -1
  45. rbx/resources/presets/default/contest/statement/contest.rbx.tex +24 -86
  46. rbx/resources/presets/default/contest/statement/instructions.tex +40 -0
  47. rbx/resources/presets/default/contest/statement/logo.png +0 -0
  48. rbx/resources/presets/default/env.rbx.yml +67 -0
  49. rbx/resources/presets/default/preset.rbx.yml +6 -2
  50. rbx/resources/presets/default/problem/.gitignore +1 -1
  51. rbx/resources/presets/default/problem/problem.rbx.yml +12 -8
  52. rbx/resources/presets/default/shared/contest_template.rbx.tex +57 -0
  53. rbx/resources/presets/default/shared/icpc.sty +322 -0
  54. rbx/resources/presets/default/shared/problem_template.rbx.tex +57 -0
  55. rbx/submitors/codeforces.py +3 -2
  56. rbx/test.py +1 -1
  57. rbx/utils.py +6 -1
  58. {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/METADATA +4 -1
  59. {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/RECORD +67 -58
  60. rbx/resources/presets/default/contest/statement/olymp.sty +0 -250
  61. rbx/resources/presets/default/contest/statement/template.rbx.tex +0 -42
  62. rbx/resources/presets/default/problem/statement/olymp.sty +0 -250
  63. rbx/resources/presets/default/problem/statement/template.rbx.tex +0 -89
  64. /rbx/resources/presets/default/problem/{gen.cpp → gens/gen.cpp} +0 -0
  65. /rbx/resources/presets/default/problem/{tests → manual_tests}/samples/000.in +0 -0
  66. /rbx/resources/presets/default/problem/{tests → manual_tests}/samples/001.in +0 -0
  67. /rbx/resources/presets/default/problem/{random.py → testplan/random.py} +0 -0
  68. /rbx/resources/presets/default/problem/{random.txt → testplan/random.txt} +0 -0
  69. {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/LICENSE +0 -0
  70. {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/WHEEL +0 -0
  71. {rbx_cp-0.7.0.dist-info → rbx_cp-0.9.0.dist-info}/entry_points.txt +0 -0
rbx/box/environment.py CHANGED
@@ -40,102 +40,149 @@ VerificationParam = Annotated[
40
40
  class FileMapping(BaseModel):
41
41
  model_config = ConfigDict(extra='forbid')
42
42
 
43
- # Path where to copy the stdin file to before running the program,
44
- # relative to the sandbox root.
45
- input: str = 'stdin'
43
+ input: str = Field(
44
+ default='stdin',
45
+ description="""Path where to copy the stdin file to before running the program,
46
+ relative to the sandbox root.""",
47
+ )
46
48
 
47
- # Path where to output the stdout file after running the program,
48
- # relative to the sandbox root.
49
- output: str = 'stdout'
49
+ output: str = Field(
50
+ default='stdout',
51
+ description="""Path where to output the stdout file after running the program,
52
+ relative to the sandbox root.""",
53
+ )
50
54
 
51
- # Path where to output the stderr file after running the program,
52
- # relative to the sandbox root.
53
- error: str = 'stderr'
55
+ error: str = Field(
56
+ default='stderr',
57
+ description="""Path where to output the stderr file after running the program,
58
+ relative to the sandbox root.""",
59
+ )
54
60
 
55
- # Path where to copy the compilable file to before compiling the program,
56
- # relative to the sandbox root.
57
- compilable: str = 'compilable'
61
+ compilable: str = Field(
62
+ default='compilable',
63
+ description="""Path where to copy the compilable file to before compiling the program,
64
+ relative to the sandbox root.""",
65
+ )
58
66
 
59
- # Path to where to output the executable file after compiling the program,
60
- # relative to the sandbox root.
61
- executable: str = 'executable'
67
+ executable: str = Field(
68
+ default='executable',
69
+ description="""Path to where to output the executable file after compiling the program,
70
+ relative to the sandbox root.""",
71
+ )
62
72
 
63
73
 
64
74
  class EnvironmentSandbox(BaseModel):
65
75
  model_config = ConfigDict(extra='forbid')
66
76
 
67
- # Max. number of process to allow to run concurrently for the program.
68
- maxProcesses: Optional[int] = 1
77
+ maxProcesses: Optional[int] = Field(
78
+ default=1,
79
+ description="""Max. number of process to allow to run concurrently for the program.""",
80
+ )
69
81
 
70
- # Time limit in milliseconds to allow the program to run.
71
- timeLimit: Optional[int] = None
82
+ timeLimit: Optional[int] = Field(
83
+ default=None,
84
+ description="""Time limit in milliseconds to allow the program to run.""",
85
+ )
72
86
 
73
- # Wall time limit in milliseconds to allow the program to run.
74
- wallTimeLimit: Optional[int] = None
87
+ wallTimeLimit: Optional[int] = Field(
88
+ default=None,
89
+ description="""Wall time limit in milliseconds to allow the program to run.""",
90
+ )
75
91
 
76
- # Memory limit in MiB.
77
- memoryLimit: Optional[int] = None
92
+ memoryLimit: Optional[int] = Field(
93
+ default=None,
94
+ description="""Memory limit in MiB.""",
95
+ )
78
96
 
79
- # File size limit in KiB
80
- fileSizeLimit: Optional[int] = None
97
+ fileSizeLimit: Optional[int] = Field(
98
+ default=None,
99
+ description="""File size limit in KiB""",
100
+ )
81
101
 
82
- # Stack limit in MiB.
83
- stackLimit: Optional[int] = None
102
+ stackLimit: Optional[int] = Field(
103
+ default=None,
104
+ description="""Stack limit in MiB.""",
105
+ )
84
106
 
85
- # Whether to preserve env. variables coming from the host.
86
- preserveEnv: Optional[bool] = False
107
+ preserveEnv: Optional[bool] = Field(
108
+ default=False,
109
+ description="""Whether to preserve env. variables coming from the host.""",
110
+ )
87
111
 
88
- # Directories in the host that should be read-only exposed to the sandbox.
89
- mirrorDirs: Optional[List[str]] = []
112
+ mirrorDirs: Optional[List[str]] = Field(
113
+ default=[],
114
+ description="""Directories in the host that should be read-only exposed to the sandbox.""",
115
+ )
90
116
 
91
117
 
92
118
  class CompilationConfig(BaseModel):
93
- # Commands to compile the program.
94
- commands: Optional[List[str]] = []
119
+ commands: Optional[List[str]] = Field(
120
+ default=[],
121
+ description="""Commands to compile the program.""",
122
+ )
95
123
 
96
- # Sandbox configuration to use when compiling for this language.
97
- sandbox: Optional[EnvironmentSandbox] = None
124
+ sandbox: Optional[EnvironmentSandbox] = Field(
125
+ default=None,
126
+ description="""Sandbox configuration to use when compiling for this language.""",
127
+ )
98
128
 
99
129
 
100
130
  class ExecutionConfig(BaseModel):
101
131
  model_config = ConfigDict(extra='forbid')
102
132
 
103
- # Command to run the program.
104
- command: Optional[str] = None
133
+ command: Optional[str] = Field(
134
+ default=None,
135
+ description="""Command to run the program.""",
136
+ )
105
137
 
106
- # Sandbox configuration to use when executing for this language.
107
- sandbox: Optional[EnvironmentSandbox] = None
138
+ sandbox: Optional[EnvironmentSandbox] = Field(
139
+ default=None,
140
+ description="""Sandbox configuration to use when executing for this language.""",
141
+ )
108
142
 
109
- # Original limits of the problem.
110
- problemLimits: Limits = Field(default_factory=Limits)
143
+ problemLimits: Limits = Field(
144
+ default_factory=Limits,
145
+ description="""Original limits of the problem.""",
146
+ )
111
147
 
112
148
 
113
149
  class EnvironmentLanguage(BaseModel):
114
150
  model_config = ConfigDict(extra='forbid')
115
151
 
116
- # Identifier of this language within this environment.
117
- name: str
152
+ name: str = Field(
153
+ description="""Identifier of this language within this environment."""
154
+ )
118
155
 
119
- # Readable name for this language.
120
- readable_name: Optional[str] = None
156
+ readableName: Optional[str] = Field(
157
+ default=None,
158
+ description="""Readable name for this language.""",
159
+ )
121
160
 
122
- # File extension supported by this language. If there's only one language
123
- # that supports a certain file extension in the environment, the tool
124
- # will automatically identify the language based on such extension.
125
- extension: str
161
+ extension: str = Field(
162
+ description="""File extension supported by this language. If there's only one language
163
+ that supports a certain file extension in the environment, the tool
164
+ will automatically identify the language based on such extension."""
165
+ )
126
166
 
127
- # Compilation config to use when compiling programs for this language.
128
- compilation: Optional[CompilationConfig] = None
167
+ compilation: Optional[CompilationConfig] = Field(
168
+ default=None,
169
+ description="""Compilation config to use when compiling programs for this language.""",
170
+ )
129
171
 
130
- # Execution config to use when running programs for this language.
131
- execution: ExecutionConfig
172
+ execution: ExecutionConfig = Field(
173
+ description="""Execution config to use when running programs for this language."""
174
+ )
132
175
 
133
- # Mapping for files within the sandbox. If not specified, the default mapping
134
- # for the environment will be used.
135
- fileMapping: Optional[FileMapping] = None
176
+ fileMapping: Optional[FileMapping] = Field(
177
+ default=None,
178
+ description="""Mapping for files within the sandbox. If not specified, the default mapping
179
+ for the environment will be used.""",
180
+ )
136
181
 
137
- # Exntensions to apply for this language.
138
- extensions: Optional[LanguageExtensions] = None
182
+ extensions: Optional[LanguageExtensions] = Field(
183
+ default=None,
184
+ description="""Extensions to apply for this language.""",
185
+ )
139
186
 
140
187
  def get_extension(self, name: str, _: Type[T]) -> Optional[T]:
141
188
  if self.extensions is None:
@@ -151,39 +198,52 @@ class EnvironmentLanguage(BaseModel):
151
198
  class TimingConfig(BaseModel):
152
199
  model_config = ConfigDict(extra='forbid')
153
200
 
154
- # Formula to use to calculate the time limit for the environment.
155
- formula: str = 'step_up(max(fastest * 3, slowest * 1.5), 100)'
201
+ formula: str = Field(
202
+ default='step_up(max(fastest * 3, slowest * 1.5), 100)',
203
+ description="""Formula to use to calculate the time limit for the environment.""",
204
+ )
156
205
 
157
206
 
158
207
  class Environment(BaseModel):
159
208
  model_config = ConfigDict(extra='forbid')
160
209
 
161
- # Default mapping for files within the sandbox. Fields in the mapping can be
162
- # individually overridden in the language configuration.
163
- defaultFileMapping: Optional[FileMapping] = None
164
-
165
- # Default compilation configuration to use when compiling programs. Fields in
166
- # the compilation config can be individually overridden in the language configuration.
167
- defaultCompilation: Optional[CompilationConfig] = None
210
+ defaultFileMapping: Optional[FileMapping] = Field(
211
+ default=None,
212
+ description="""Default mapping for files within the sandbox. Fields in the mapping can be
213
+ individually overridden in the language configuration.""",
214
+ )
168
215
 
169
- # Default execution configuration to use when running programs. Fields in the
170
- # execution config can be individually overridden in the language configuration.
171
- defaultExecution: Optional[ExecutionConfig] = None
216
+ defaultCompilation: Optional[CompilationConfig] = Field(
217
+ default=None,
218
+ description="""Default compilation configuration to use when compiling programs. Fields in
219
+ the compilation config can be individually overridden in the language configuration.""",
220
+ )
172
221
 
173
- # Configuration for each language supported in this environment.
174
- languages: List[EnvironmentLanguage] = []
222
+ defaultExecution: Optional[ExecutionConfig] = Field(
223
+ default=None,
224
+ description="""Default execution configuration to use when running programs. Fields in the
225
+ execution config can be individually overridden in the language configuration.""",
226
+ )
175
227
 
176
- # Identifier of the sandbox used by this environment (e.g. "stupid", "isolate")
177
- sandbox: str = 'stupid'
228
+ languages: List[EnvironmentLanguage] = Field(
229
+ default=[],
230
+ description="""Configuration for each language supported in this environment.""",
231
+ )
178
232
 
179
- # Identifier of the preset that should be used when creating new problems.
180
- preset: str = 'default'
233
+ sandbox: str = Field(
234
+ default='stupid',
235
+ description="""Identifier of the sandbox used by this environment (e.g. "stupid", "isolate")""",
236
+ )
181
237
 
182
- # Timing configuration for the environment.
183
- timing: TimingConfig = Field(default_factory=TimingConfig)
238
+ timing: TimingConfig = Field(
239
+ default_factory=TimingConfig,
240
+ description="""Timing configuration for the environment.""",
241
+ )
184
242
 
185
- # Extensions to be added to the environment.
186
- extensions: Optional[Extensions] = None
243
+ extensions: Optional[Extensions] = Field(
244
+ default=None,
245
+ description="""Extensions to be added to the environment.""",
246
+ )
187
247
 
188
248
 
189
249
  def get_app_environment_path(env: str) -> pathlib.Path:
rbx/box/formatting.py CHANGED
@@ -2,6 +2,7 @@ import os
2
2
  import pathlib
3
3
  from typing import Any, Optional
4
4
 
5
+ from rbx import utils
5
6
  from rbx.box import setter_config
6
7
 
7
8
 
@@ -23,7 +24,7 @@ def href(url: os.PathLike[str], text: Optional[str] = None, style: str = 'item')
23
24
  return f'[{style}]{text}[/{style}]'
24
25
 
25
26
  if isinstance(url, pathlib.Path):
26
- url = url.resolve()
27
+ url = utils.abspath(url)
27
28
 
28
29
  url_str = str(url)
29
30
  if pathlib.Path(url_str).exists():
@@ -0,0 +1,74 @@
1
+ import functools
2
+ import pathlib
3
+ import shutil
4
+
5
+ from rbx.config import get_app_path
6
+ from rbx.grading.caching import DependencyCache
7
+ from rbx.grading.judge.cacher import FileCacher
8
+ from rbx.grading.judge.sandbox import SandboxBase
9
+ from rbx.grading.judge.sandboxes.stupid_sandbox import StupidSandbox
10
+ from rbx.grading.judge.storage import FilesystemStorage, Storage
11
+
12
+ CACHE_STEP_VERSION = 3
13
+
14
+
15
+ def get_cache_fingerprint() -> str:
16
+ return f'{CACHE_STEP_VERSION}'
17
+
18
+
19
+ @functools.cache
20
+ def is_cache_valid(cache_dir: pathlib.Path) -> bool:
21
+ if not cache_dir.is_dir():
22
+ return True
23
+ fingerprint_file = cache_dir / 'fingerprint'
24
+ if not fingerprint_file.is_file():
25
+ return False
26
+ fingerprint = fingerprint_file.read_text()
27
+ if fingerprint.strip() != get_cache_fingerprint():
28
+ return False
29
+ return True
30
+
31
+
32
+ @functools.cache
33
+ def get_global_cache_dir() -> pathlib.Path:
34
+ cache_dir = get_app_path() / '.box'
35
+ cache_dir.mkdir(parents=True, exist_ok=True)
36
+ fingerprint_file = cache_dir / 'fingerprint'
37
+ if not fingerprint_file.is_file():
38
+ fingerprint_file.write_text(get_cache_fingerprint())
39
+ return cache_dir
40
+
41
+
42
+ def is_global_cache_valid() -> bool:
43
+ return is_cache_valid(get_global_cache_dir())
44
+
45
+
46
+ @functools.cache
47
+ def get_global_storage_dir() -> pathlib.Path:
48
+ storage_dir = get_global_cache_dir() / '.storage'
49
+ storage_dir.mkdir(parents=True, exist_ok=True)
50
+ return storage_dir
51
+
52
+
53
+ @functools.cache
54
+ def get_global_cache_storage() -> Storage:
55
+ return FilesystemStorage(get_global_storage_dir())
56
+
57
+
58
+ @functools.cache
59
+ def get_global_file_cacher() -> FileCacher:
60
+ return FileCacher(get_global_cache_storage())
61
+
62
+
63
+ @functools.cache
64
+ def get_global_dependency_cache() -> DependencyCache:
65
+ return DependencyCache(get_global_cache_dir(), get_global_file_cacher())
66
+
67
+
68
+ @functools.cache
69
+ def get_global_sandbox() -> SandboxBase:
70
+ return StupidSandbox(get_global_file_cacher())
71
+
72
+
73
+ def clear_global_cache():
74
+ shutil.rmtree(get_global_cache_dir(), ignore_errors=True)
rbx/box/package.py CHANGED
@@ -11,8 +11,9 @@ import typer
11
11
  from pydantic import ValidationError
12
12
 
13
13
  from rbx import console, utils
14
- from rbx.box import cd
14
+ from rbx.box import cd, global_package
15
15
  from rbx.box.environment import get_sandbox_type
16
+ from rbx.box.global_package import get_cache_fingerprint
16
17
  from rbx.box.schema import (
17
18
  CodeItem,
18
19
  ExpectedOutcome,
@@ -34,12 +35,11 @@ YAML_NAME = 'problem.rbx.yml'
34
35
  _DEFAULT_CHECKER = 'wcmp.cpp'
35
36
  _NOOP_CHECKER = 'noop.cpp'
36
37
  TEMP_DIR = None
37
- CACHE_STEP_VERSION = 1
38
38
 
39
39
 
40
40
  @functools.cache
41
41
  def find_problem_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
42
- root = root.resolve()
42
+ root = utils.abspath(root)
43
43
  problem_yaml_path = root / YAML_NAME
44
44
  while root != pathlib.PosixPath('/') and not problem_yaml_path.is_file():
45
45
  root = root.parent
@@ -107,17 +107,13 @@ def get_ruyaml(root: pathlib.Path = pathlib.Path()) -> Tuple[ruyaml.YAML, ruyaml
107
107
  return res, res.load(problem_yaml_path.read_text())
108
108
 
109
109
 
110
- def _get_fingerprint() -> str:
111
- return f'{CACHE_STEP_VERSION}'
112
-
113
-
114
110
  @functools.cache
115
111
  def get_problem_cache_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
116
112
  cache_dir = find_problem(root) / '.box'
117
113
  cache_dir.mkdir(parents=True, exist_ok=True)
118
114
  fingerprint_file = cache_dir / 'fingerprint'
119
115
  if not fingerprint_file.is_file():
120
- fingerprint_file.write_text(_get_fingerprint())
116
+ fingerprint_file.write_text(get_cache_fingerprint())
121
117
  return cache_dir
122
118
 
123
119
 
@@ -156,8 +152,8 @@ def get_problem_iruns_dir(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
156
152
  def get_problem_preprocessed_path(
157
153
  item: pathlib.Path, root: pathlib.Path = pathlib.Path()
158
154
  ) -> pathlib.Path:
159
- root_resolved = root.resolve()
160
- item_resolved = item.resolve()
155
+ root_resolved = utils.abspath(root)
156
+ item_resolved = utils.abspath(item)
161
157
 
162
158
  if not item_resolved.is_relative_to(root_resolved):
163
159
  final_path = pathlib.Path('remote') / item_resolved.name
@@ -170,12 +166,12 @@ def get_problem_preprocessed_path(
170
166
 
171
167
  @functools.cache
172
168
  def get_cache_storage(root: pathlib.Path = pathlib.Path()) -> Storage:
173
- return FilesystemStorage(get_problem_storage_dir(root))
169
+ return FilesystemStorage(get_problem_storage_dir(root), compress=False)
174
170
 
175
171
 
176
172
  @functools.cache
177
173
  def get_dependency_cache(root: pathlib.Path = pathlib.Path()) -> DependencyCache:
178
- return DependencyCache(get_problem_cache_dir(root), get_cache_storage(root))
174
+ return DependencyCache(get_problem_cache_dir(root), get_file_cacher(root))
179
175
 
180
176
 
181
177
  @functools.cache
@@ -364,11 +360,11 @@ def get_test_groups_by_name(
364
360
  # Return each compilation file and to where it should be moved inside
365
361
  # the sandbox.
366
362
  def get_compilation_files(code: CodeItem) -> List[Tuple[pathlib.Path, pathlib.Path]]:
367
- code_dir = code.path.parent.resolve()
363
+ code_dir = utils.abspath(code.path.parent)
368
364
 
369
365
  res = []
370
366
  for compilation_file in code.compilationFiles or []:
371
- compilation_file_path = pathlib.Path(compilation_file).resolve()
367
+ compilation_file_path = utils.abspath(pathlib.Path(compilation_file))
372
368
  if not compilation_file_path.is_file():
373
369
  console.console.print(
374
370
  f'[error]Compilation file [item]{compilation_file}[/item] for '
@@ -427,16 +423,7 @@ def get_merged_capture_path(root: pathlib.Path = pathlib.Path()) -> pathlib.Path
427
423
  @functools.cache
428
424
  def is_cache_valid(root: pathlib.Path = pathlib.Path()):
429
425
  cache_dir = find_problem(root) / '.box'
430
- if not cache_dir.is_dir():
431
- return True
432
-
433
- fingerprint_file = cache_dir / 'fingerprint'
434
- if not fingerprint_file.is_file():
435
- return False
436
- fingerprint = fingerprint_file.read_text()
437
- if fingerprint.strip() != _get_fingerprint():
438
- return False
439
- return True
426
+ return global_package.is_cache_valid(cache_dir)
440
427
 
441
428
 
442
429
  def clear_package_cache():
File without changes
File without changes
@@ -4,7 +4,7 @@ from typing import List, Optional
4
4
 
5
5
  import typer
6
6
 
7
- from rbx import console
7
+ from rbx import console, utils
8
8
  from rbx.box import header, package
9
9
  from rbx.box.lang import code_to_langs, is_valid_lang_code
10
10
  from rbx.box.packaging.packager import (
@@ -129,7 +129,7 @@ class PolygonPackager(BasePackager):
129
129
  shutil.copyfile(built_statement.path, final_path)
130
130
 
131
131
  return polygon_schema.Statement(
132
- path=str(final_path.resolve().relative_to(into_path.resolve())),
132
+ path=str(utils.abspath(final_path).relative_to(utils.abspath(into_path))),
133
133
  language=language,
134
134
  type=self._statement_application_type(built_statement), # type: ignore
135
135
  )
@@ -238,7 +238,7 @@ class PolygonContestPackager(BaseContestPackager):
238
238
  shutil.copyfile(built_statement.path, final_path)
239
239
 
240
240
  return polygon_schema.Statement(
241
- path=str(final_path.resolve().relative_to(into_path.resolve())),
241
+ path=str(utils.abspath(final_path).relative_to(utils.abspath(into_path))),
242
242
  language=language,
243
243
  type='application/pdf', # type: ignore
244
244
  )