Skip to content

Commit 78ebf3e

Browse files
authored
Add new command: compiler (kyuridenamida#231)
* コンパイラーを追加 * コンパイラ向けに追加 * インタラクティブはこのバージョンになさそうなので、コメントアウト * compiler向けの修正 * 追加 * metadataを修正 * testerを修正 * configを修正 * READMEを追記 * compilerのテスト追加 * compilerのテストを追加 * testerを修正 * READMEを修正 * 整形 * venvを削除 * configを変更しないようにした * configのdefaultの順番を変更
1 parent 660cfc3 commit 78ebf3e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+883
-62
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ atcoder_tools.egg-info/
1111
build/
1212
dist/
1313
check_autopep8
14+
venv/

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ optional arguments:
224224
- `skip_existing_problems=false` ディレクトリが既に存在する問題の処理をスキップする
225225
- `in_example_format="in_{}.txt"` テストケース(input)のフォーマットを`in_1.txt, in_2.txt, ...`とする
226226
- `out_example_format="out_{}.txt"` テストケース(output)のフォーマットを`out_1.txt, out_2.txt, ...`とする
227+
- `compile_before_testing` テスト前にコンパイルを実行するか否かをTrue/Falseで指定。何も指定しないとFalseとなります。
228+
- `compile_only_when_diff_detected` テスト前のコンパイルの際、元のソースに変更があった場合のみ実行するかをTrue/Falseで指定。何も指定しないとFalseとなります。
229+
227230

228231
```toml
229232
[codestyle]

atcodertools/atcoder_tools.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from atcodertools.tools.tester import main as tester_main
1010
from atcodertools.tools.submit import main as submit_main
1111
from atcodertools.tools.codegen import main as codegen_main
12+
from atcodertools.tools.compiler import main as compiler_main
1213
from atcodertools.release_management.version import __version__
1314
from colorama import Fore, Style
1415

@@ -38,7 +39,7 @@ def notify_if_latest_version_found():
3839
def main():
3940
notify_if_latest_version_found()
4041

41-
if len(sys.argv) < 2 or sys.argv[1] not in ("gen", "test", "submit", "codegen", "version"):
42+
if len(sys.argv) < 2 or sys.argv[1] not in ("gen", "test", "submit", "codegen", "compile", "version"):
4243
print("Usage:")
4344
print("{} gen -- to generate workspace".format(sys.argv[0]))
4445
print("{} test -- to test codes in your workspace".format(sys.argv[0]))
@@ -63,5 +64,8 @@ def main():
6364
if sys.argv[1] == "codegen":
6465
codegen_main(prog, args)
6566

67+
if sys.argv[1] == "compile":
68+
compiler_main(prog, args)
69+
6670
if sys.argv[1] == "version":
6771
print(__version__)

atcodertools/common/language.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from atcodertools.codegen.code_generators import cpp, java, rust, python, nim, d, cs, swift
55
from atcodertools.codegen.models.code_gen_args import CodeGenArgs
66
from atcodertools.tools.templates import get_default_template_path
7+
import platform
78

89

910
class LanguageNotFoundError(Exception):
@@ -25,7 +26,10 @@ def __init__(self,
2526
submission_lang_pattern: Pattern[str],
2627
default_code_generator: Callable[[CodeGenArgs], str],
2728
default_template_path: str,
28-
default_code_style=None
29+
default_code_style=None,
30+
compile_command=None,
31+
test_command=None,
32+
exec_filename=None
2933
):
3034
self.name = name
3135
self.display_name = display_name
@@ -34,11 +38,37 @@ def __init__(self,
3438
self.default_code_generator = default_code_generator
3539
self.default_template_path = default_template_path
3640
self.default_code_style = default_code_style
41+
self.compile_command = compile_command
42+
self.test_command = test_command
43+
self.code_filename = "{filename}." + extension
44+
if platform.system() == "Windows":
45+
self.exec_filename = exec_filename.replace(
46+
"{exec_extension}", ".exe")
47+
else:
48+
self.exec_filename = exec_filename.replace("{exec_extension}", "")
3749

3850
def source_code_name(self, name_without_extension: str) -> str:
3951
# put extension to the name
4052
return "{}.{}".format(name_without_extension, self.extension)
4153

54+
def get_compile_command(self, filename: str):
55+
return self.compile_command.format(filename=filename)
56+
57+
def get_code_filename(self, filename: str):
58+
return self.code_filename.format(filename=filename)
59+
60+
def get_exec_filename(self, filename: str):
61+
return self.exec_filename.format(filename=filename, capitalized_filename=filename.capitalize())
62+
63+
def get_test_command(self, filename: str, cwd: str = '.'):
64+
exec_filename = cwd + '/'
65+
if platform.system() == "Windows":
66+
exec_filename += filename + ".exe"
67+
else:
68+
exec_filename += filename
69+
capitalized_filename = filename.capitalize()
70+
return self.test_command.format(filename=filename, exec_filename=exec_filename, capitalized_filename=capitalized_filename)
71+
4272
@classmethod
4373
def from_name(cls, name: str):
4474
for lang in ALL_LANGUAGES:
@@ -56,6 +86,9 @@ def from_name(cls, name: str):
5686
".*C\\+\\+ \\(GCC 9.*|.*C\\+\\+14 \\(GCC 5.*"),
5787
default_code_generator=cpp.main,
5888
default_template_path=get_default_template_path('cpp'),
89+
compile_command="g++ {filename}.cpp -o {filename} -std=c++14",
90+
test_command="{exec_filename}",
91+
exec_filename="{filename}{exec_extension}"
5992
)
6093

6194
JAVA = Language(
@@ -65,6 +98,9 @@ def from_name(cls, name: str):
6598
submission_lang_pattern=re.compile(".*Java8.*|.*Java \\(OpenJDK 11.*"),
6699
default_code_generator=java.main,
67100
default_template_path=get_default_template_path('java'),
101+
compile_command="javac {filename}.java",
102+
test_command="java {capitalized_filename}",
103+
exec_filename="{capitalized_filename}.class"
68104
)
69105

70106
RUST = Language(
@@ -74,6 +110,9 @@ def from_name(cls, name: str):
74110
submission_lang_pattern=re.compile(".*Rust \\(1.*"),
75111
default_code_generator=rust.main,
76112
default_template_path=get_default_template_path('rs'),
113+
compile_command="rustc {filename}.rs -o {filename}",
114+
test_command="{exec_filename}",
115+
exec_filename="{filename}{exec_extension}"
77116
)
78117

79118
PYTHON = Language(
@@ -83,6 +122,9 @@ def from_name(cls, name: str):
83122
submission_lang_pattern=re.compile(".*Python3.*|^Python$"),
84123
default_code_generator=python.main,
85124
default_template_path=get_default_template_path('py'),
125+
compile_command="python3 -mpy_compile {filename}.py",
126+
test_command="python3 {filename}.py",
127+
exec_filename="{filename}.pyc"
86128
)
87129

88130
DLANG = Language(
@@ -92,6 +134,9 @@ def from_name(cls, name: str):
92134
submission_lang_pattern=re.compile(".*D \\(DMD.*"),
93135
default_code_generator=d.main,
94136
default_template_path=get_default_template_path('d'),
137+
compile_command="dmd {filename}.d -of={filename}",
138+
test_command="{exec_filename}",
139+
exec_filename="{filename}{exec_extension}"
95140
)
96141

97142
NIM = Language(
@@ -101,7 +146,10 @@ def from_name(cls, name: str):
101146
submission_lang_pattern=re.compile(".*Nim \\(1.*"),
102147
default_code_generator=nim.main,
103148
default_template_path=get_default_template_path('nim'),
104-
default_code_style=CodeStyle(indent_width=2)
149+
default_code_style=CodeStyle(indent_width=2),
150+
compile_command="nim cpp -o:{filename} {filename}.nim",
151+
test_command="{exec_filename}",
152+
exec_filename="{filename}{exec_extension}"
105153
)
106154

107155
CSHARP = Language(
@@ -111,6 +159,9 @@ def from_name(cls, name: str):
111159
submission_lang_pattern=re.compile(".*C# \\(Mono.*"),
112160
default_code_generator=cs.main,
113161
default_template_path=get_default_template_path('cs'),
162+
compile_command="mcs {filename}.cs -o {filename}",
163+
test_command="{exec_filename}",
164+
exec_filename="{filename}{exec_extension}"
114165
)
115166

116167
SWIFT = Language(
@@ -120,6 +171,9 @@ def from_name(cls, name: str):
120171
submission_lang_pattern=re.compile("^Swift"),
121172
default_code_generator=swift.main,
122173
default_template_path=get_default_template_path('swift'),
174+
compile_command="swiftc {filename}.swift -o {filename}",
175+
test_command="{exec_filename}",
176+
exec_filename="{filename}{exec_extension}"
123177
)
124178

125179

atcodertools/config/etc_config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ def __init__(self,
77
skip_existing_problems: bool = False,
88
in_example_format: str = "in_{}.txt",
99
out_example_format: str = "out_{}.txt",
10+
compile_before_testing: bool = False,
11+
compile_only_when_diff_detected: bool = False
1012
):
1113
self.download_without_login = download_without_login
1214
self.parallel_download = parallel_download
1315
self.save_no_session_cache = save_no_session_cache
1416
self.skip_existing_problems = skip_existing_problems
1517
self.in_example_format = in_example_format
1618
self.out_example_format = out_example_format
19+
self.compile_before_testing = compile_before_testing
20+
self.compile_only_when_diff_detected = compile_only_when_diff_detected

atcodertools/executils/run_command.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,12 @@ def run_command(exec_cmd: str, current_working_dir: str) -> str:
99
stderr=subprocess.STDOUT,
1010
cwd=current_working_dir)
1111
return proc.stdout.decode(locale.getpreferredencoding())
12+
13+
14+
def run_command_with_returncode(exec_cmd: str, current_working_dir: str):
15+
proc = subprocess.run(exec_cmd,
16+
shell=True,
17+
stdout=subprocess.PIPE,
18+
stderr=subprocess.STDOUT,
19+
cwd=current_working_dir)
20+
return proc.returncode, proc.stdout.decode(locale.getpreferredencoding())

atcodertools/executils/run_program.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def run_program(exec_file: str, input_file: str, timeout_sec: int, args=None, cu
3636
try:
3737
elapsed_sec = -time.time()
3838
proc = subprocess.run(
39-
[exec_file] + args, stdin=open(input_file, 'r'), universal_newlines=True, timeout=timeout_sec,
39+
exec_file.split(" ") + args, stdin=open(input_file, 'r'), universal_newlines=True, timeout=timeout_sec,
4040
stdout=subprocess.PIPE,
4141
stderr=subprocess.PIPE,
4242
cwd=current_working_dir

atcodertools/tools/compiler.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/python3
2+
import argparse
3+
4+
from atcodertools.executils.run_command import run_command_with_returncode
5+
from atcodertools.tools.models.metadata import Metadata
6+
from atcodertools.common.language import Language
7+
import os
8+
import pathlib
9+
10+
11+
class BadStatusCodeException(Exception):
12+
pass
13+
14+
15+
def _compile(code_filename: str, exec_filename: str, compile_command: str, cwd: str, force_compile: bool) -> None:
16+
if not force_compile:
17+
code_path = pathlib.Path(os.path.join(cwd, code_filename))
18+
exec_path_name = os.path.join(cwd, exec_filename)
19+
20+
if os.path.exists(exec_path_name) and code_path.stat().st_mtime < pathlib.Path(exec_path_name).stat().st_mtime:
21+
print("No need to compile")
22+
return
23+
24+
print("Compiling... (command: `{}`)".format(compile_command))
25+
code, stdout = run_command_with_returncode(compile_command, cwd)
26+
print(stdout)
27+
if code != 0:
28+
raise BadStatusCodeException
29+
30+
31+
def compile_main_and_judge_programs(lang: Language, cwd="./", force_compile=False, compile_command=None) -> None:
32+
print("[Main Program]")
33+
if compile_command is None:
34+
compile_command = lang.get_compile_command('main')
35+
code_filename = lang.get_code_filename('main')
36+
exec_filename = lang.get_exec_filename('main')
37+
38+
try:
39+
_compile(code_filename, exec_filename,
40+
compile_command, cwd, force_compile)
41+
except BadStatusCodeException as e:
42+
raise e
43+
44+
45+
def main(prog, args):
46+
parser = argparse.ArgumentParser(
47+
prog=prog,
48+
usage="Compile your program in the current directory (no argument)",
49+
formatter_class=argparse.RawTextHelpFormatter)
50+
parser.add_argument('--compile-command',
51+
help='set compile command'
52+
' [Default]: None',
53+
type=str,
54+
default=None)
55+
56+
parser.add_argument('--compile-only-when-diff-detected',
57+
help='compile only when diff detected [true, false]'
58+
' [Default]: true',
59+
type=bool,
60+
default=False)
61+
62+
args = parser.parse_args(args)
63+
64+
metadata = Metadata.load_from("./metadata.json")
65+
force_compile = not args.compile_only_when_diff_detected
66+
compile_main_and_judge_programs(metadata.lang, force_compile=force_compile,
67+
compile_command=args.compile_command)

atcodertools/tools/models/metadata.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from atcodertools.client.models.problem import Problem
44
from atcodertools.common.judgetype import NormalJudge, DecimalJudge, Judge
5-
from atcodertools.common.language import Language
5+
from atcodertools.common.language import Language, CPP
66

77

88
class Metadata:
@@ -57,3 +57,17 @@ def save_to(self, filename):
5757
with open(filename, 'w') as f:
5858
json.dump(self.to_dict(), f, indent=1, sort_keys=True)
5959
f.write('\n')
60+
61+
62+
DEFAULT_IN_EXAMPLE_PATTERN = 'in_*.txt'
63+
DEFAULT_OUT_EXAMPLE_PATTERN = "out_*.txt"
64+
65+
66+
DEFAULT_METADATA = Metadata(
67+
problem=None,
68+
code_filename=None,
69+
sample_in_pattern=DEFAULT_IN_EXAMPLE_PATTERN,
70+
sample_out_pattern=DEFAULT_OUT_EXAMPLE_PATTERN,
71+
lang=CPP,
72+
judge_method=NormalJudge()
73+
)

0 commit comments

Comments
 (0)