diff --git a/dmoj/executors/C.py b/dmoj/executors/C.py index af4cb059f..afd1632d3 100644 --- a/dmoj/executors/C.py +++ b/dmoj/executors/C.py @@ -4,6 +4,7 @@ class Executor(GCCMixin, CExecutor): command = 'gcc' std = 'c99' + ext_priority = 1 test_program = """ #include diff --git a/dmoj/executors/C11.py b/dmoj/executors/C11.py index 43cdd20d5..14d57589e 100644 --- a/dmoj/executors/C11.py +++ b/dmoj/executors/C11.py @@ -5,6 +5,7 @@ class Executor(GCCMixin, CExecutor): command = 'gcc11' std = 'c11' command_paths = ['gcc'] + ext_priority = 2 test_program = """ #include diff --git a/dmoj/executors/C23.py b/dmoj/executors/C23.py index 60cdc7a2f..c53bcae3e 100644 --- a/dmoj/executors/C23.py +++ b/dmoj/executors/C23.py @@ -5,6 +5,7 @@ class Executor(GCCMixin, CExecutor): command = 'gcc23' std = 'c23' command_paths = ['gcc'] + ext_priority = 4 test_program = """ #include diff --git a/dmoj/executors/CLANG.py b/dmoj/executors/CLANG.py index e9b16c27e..2edabfc39 100644 --- a/dmoj/executors/CLANG.py +++ b/dmoj/executors/CLANG.py @@ -5,6 +5,7 @@ class Executor(ClangMixin, CExecutor): command = 'clang' std = 'c11' command_paths = [f'clang-{i}' for i in CLANG_VERSIONS] + ['clang'] + ext_priority = 3 test_program = """ #include diff --git a/dmoj/executors/CLPP14.py b/dmoj/executors/CLPP14.py index 1ab5c7fdf..6d29d734c 100644 --- a/dmoj/executors/CLPP14.py +++ b/dmoj/executors/CLPP14.py @@ -5,6 +5,7 @@ class Executor(ClangMixin, CPPExecutor): command = 'clang++' std = 'c++14' command_paths = [f'clang++-{i}' for i in CLANG_VERSIONS] + ['clang++'] + ext_priority = 4 test_program = """ #include diff --git a/dmoj/executors/CLPP17.py b/dmoj/executors/CLPP17.py index a25ef8d8f..462590ad8 100644 --- a/dmoj/executors/CLPP17.py +++ b/dmoj/executors/CLPP17.py @@ -5,6 +5,7 @@ class Executor(ClangMixin, CPPExecutor): command = 'clang++' std = 'c++17' command_paths = [f'clang++-{i}' for i in CLANG_VERSIONS] + ['clang++'] + ext_priority = 6 test_program = """ #include diff --git a/dmoj/executors/CLPP20.py b/dmoj/executors/CLPP20.py index cc5109bb2..82759f5be 100644 --- a/dmoj/executors/CLPP20.py +++ b/dmoj/executors/CLPP20.py @@ -5,6 +5,7 @@ class Executor(ClangMixin, CPPExecutor): command = 'clang++' std = 'c++20' command_paths = [f'clang++-{i}' for i in CLANG_VERSIONS] + ['clang++'] + ext_priority = 8 test_program = """ #include diff --git a/dmoj/executors/CLPP23.py b/dmoj/executors/CLPP23.py index 143d732a3..0599ce653 100644 --- a/dmoj/executors/CLPP23.py +++ b/dmoj/executors/CLPP23.py @@ -5,6 +5,7 @@ class Executor(ClangMixin, CPPExecutor): command = 'clang++' std = 'c++23' command_paths = [f'clang++-{i}' for i in CLANG_VERSIONS] + ['clang++'] + ext_priority = 10 test_program = """ #include diff --git a/dmoj/executors/CPP03.py b/dmoj/executors/CPP03.py index d945aa703..96db73554 100644 --- a/dmoj/executors/CPP03.py +++ b/dmoj/executors/CPP03.py @@ -4,6 +4,7 @@ class Executor(GCCMixin, CPPExecutor): command = 'g++' std = 'c++03' + ext_priority = 1 test_program = """ #include diff --git a/dmoj/executors/CPP11.py b/dmoj/executors/CPP11.py index 5ccbe8b97..14c281797 100644 --- a/dmoj/executors/CPP11.py +++ b/dmoj/executors/CPP11.py @@ -5,6 +5,7 @@ class Executor(GCCMixin, CPPExecutor): command = 'g++11' command_paths = ['g++-5', 'g++-4.9', 'g++-4.8', 'g++'] std = 'c++11' + ext_priority = 2 test_program = """ #include diff --git a/dmoj/executors/CPP14.py b/dmoj/executors/CPP14.py index 45a7ad818..27c09e701 100644 --- a/dmoj/executors/CPP14.py +++ b/dmoj/executors/CPP14.py @@ -5,6 +5,7 @@ class Executor(GCCMixin, CPPExecutor): command = 'g++14' command_paths = ['g++-5', 'g++'] std = 'c++14' + ext_priority = 3 test_program = """ #include diff --git a/dmoj/executors/CPP17.py b/dmoj/executors/CPP17.py index f190ecd7b..ee3f5f88d 100644 --- a/dmoj/executors/CPP17.py +++ b/dmoj/executors/CPP17.py @@ -5,6 +5,7 @@ class Executor(GCCMixin, CPPExecutor): command = 'g++17' command_paths = ['g++-7', 'g++'] std = 'c++17' + ext_priority = 5 test_program = """ #include diff --git a/dmoj/executors/CPP20.py b/dmoj/executors/CPP20.py index fb4729adb..4d92e2e1d 100644 --- a/dmoj/executors/CPP20.py +++ b/dmoj/executors/CPP20.py @@ -5,6 +5,7 @@ class Executor(GCCMixin, CPPExecutor): command = 'g++20' command_paths = ['g++-11', 'g++'] std = 'c++20' + ext_priority = 7 test_program = """ #include diff --git a/dmoj/executors/CPP23.py b/dmoj/executors/CPP23.py index d6e904545..fb9c6a5f8 100644 --- a/dmoj/executors/CPP23.py +++ b/dmoj/executors/CPP23.py @@ -5,6 +5,7 @@ class Executor(GCCMixin, CPPExecutor): command = 'g++23' command_paths = ['g++-13', 'g++'] std = 'c++23' + ext_priority = 9 test_program = """ #include diff --git a/dmoj/executors/GAS32.py b/dmoj/executors/GAS32.py index 3db690f8f..c1fab15ae 100644 --- a/dmoj/executors/GAS32.py +++ b/dmoj/executors/GAS32.py @@ -3,6 +3,7 @@ class Executor(PlatformX86Mixin, GASExecutor): as_name = 'as_x86' + ext_priority = 1 test_program = r""".intel_syntax noprefix diff --git a/dmoj/executors/GAS64.py b/dmoj/executors/GAS64.py index 1e22bf008..91336aefe 100644 --- a/dmoj/executors/GAS64.py +++ b/dmoj/executors/GAS64.py @@ -3,6 +3,7 @@ class Executor(PlatformX64Mixin, GASExecutor): as_name = 'as_x64' + ext_priority = 2 test_program = r""".intel_syntax noprefix diff --git a/dmoj/executors/JAVA.py b/dmoj/executors/JAVA.py index b859bc6b1..10efbd312 100644 --- a/dmoj/executors/JAVA.py +++ b/dmoj/executors/JAVA.py @@ -5,6 +5,7 @@ class Executor(JavacExecutor): compiler = 'javac' vm = 'java' jvm_regex = r'(?:java-|openjdk)(?:9|[1-9][0-9]+)' + ext_priority = 2 test_program = """\ import java.io.IOException; diff --git a/dmoj/executors/JAVA8.py b/dmoj/executors/JAVA8.py index ee816d8d3..ffa9e5a6a 100644 --- a/dmoj/executors/JAVA8.py +++ b/dmoj/executors/JAVA8.py @@ -7,6 +7,7 @@ class Executor(JavacExecutor): compiler = 'javac8' vm = 'java8' jvm_regex = r'java-8-|jdk-8-|openjdk8|oracle-java8' + ext_priority = 1 test_program = """\ import java.io.IOException; diff --git a/dmoj/executors/NASM.py b/dmoj/executors/NASM.py index af606deed..149fea830 100644 --- a/dmoj/executors/NASM.py +++ b/dmoj/executors/NASM.py @@ -3,6 +3,7 @@ class Executor(PlatformX86Mixin, NASMExecutor): nasm_format = 'elf32' + ext_priority = 1 test_program = """\ section .text diff --git a/dmoj/executors/NASM64.py b/dmoj/executors/NASM64.py index 883c8daa7..ec4424e66 100644 --- a/dmoj/executors/NASM64.py +++ b/dmoj/executors/NASM64.py @@ -3,6 +3,7 @@ class Executor(PlatformX64Mixin, NASMExecutor): nasm_format = 'elf64' + ext_priority = 2 test_program = """\ section .text diff --git a/dmoj/executors/PY2.py b/dmoj/executors/PY2.py index d19d78cf3..b67fc5eab 100644 --- a/dmoj/executors/PY2.py +++ b/dmoj/executors/PY2.py @@ -5,6 +5,7 @@ class Executor(PythonExecutor): command = 'python' command_paths = ['python2.7', 'python2', 'python'] pygments_traceback_lexer = 'py2tb' + ext_priority = 1 test_program = """ import sys if sys.version_info.major == 2: diff --git a/dmoj/executors/PY3.py b/dmoj/executors/PY3.py index 1fcfb552e..3e87fdd2c 100644 --- a/dmoj/executors/PY3.py +++ b/dmoj/executors/PY3.py @@ -5,6 +5,7 @@ class Executor(PythonExecutor): command = 'python3' command_paths = [f'python{i}' for i in ['3.6', '3.5', '3.4', '3.3', '3.2', '3.1', '3']] pygments_traceback_lexer = 'py3tb' + ext_priority = 3 test_program = """ import sys if sys.version_info.major == 3: diff --git a/dmoj/executors/PYPY.py b/dmoj/executors/PYPY.py index e93c6e687..64777ff93 100644 --- a/dmoj/executors/PYPY.py +++ b/dmoj/executors/PYPY.py @@ -7,6 +7,7 @@ class Executor(PythonExecutor): command = 'pypy' pygments_traceback_lexer = 'py2tb' + ext_priority = 2 test_program = """ import sys if sys.version_info.major == 2: diff --git a/dmoj/executors/PYPY3.py b/dmoj/executors/PYPY3.py index 5d59732bc..7cecfcd5e 100644 --- a/dmoj/executors/PYPY3.py +++ b/dmoj/executors/PYPY3.py @@ -4,6 +4,7 @@ class Executor(PYPYExecutor): command = 'pypy3' pygments_traceback_lexer = 'py3tb' + ext_priority = 4 test_program = """ import sys if sys.version_info.major == 3: diff --git a/dmoj/executors/__init__.py b/dmoj/executors/__init__.py index b0971d3bd..cee8d6ac2 100644 --- a/dmoj/executors/__init__.py +++ b/dmoj/executors/__init__.py @@ -22,8 +22,8 @@ def by_ext(ext: str) -> Any: if name.lower() == ext: return executor - for executor in sorted(executors.values(), key=lambda executor: executor.Executor.name): - if executor.Executor.ext == ext: + for executor in sorted(executors.values(), key=lambda executor: executor.Executor.ext_priority, reverse=True): + if ext in executor.Executor.get_valid_exts(): return executor raise KeyError('no executor for extension "%s"' % ext) diff --git a/dmoj/executors/base_executor.py b/dmoj/executors/base_executor.py index 25816e804..0c955b705 100644 --- a/dmoj/executors/base_executor.py +++ b/dmoj/executors/base_executor.py @@ -6,7 +6,7 @@ import sys import tempfile import traceback -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union +from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, Union from dmoj.cptbox import IsolateTracer, TracedPopen, syscalls from dmoj.cptbox.filesystem_policies import ExactDir, ExactFile, FilesystemAccessRule, RecursiveDir @@ -120,6 +120,7 @@ class BaseExecutor(metaclass=ExecutorMeta): test_memory = env.selftest_memory_limit version_regex = re.compile(r'.*?(\d+(?:\.\d+)+)', re.DOTALL) source_filename_format = '{problem_id}.{ext}' + ext_priority = 0 # When there are multiple versions of a similar runtime, bigger is newer address_grace = 65536 data_grace = 0 @@ -483,3 +484,12 @@ def get_find_first_mapping(cls) -> Optional[Dict[str, List[str]]]: @classmethod def autoconfig(cls) -> AutoConfigOutput: return cls.autoconfig_find_first(cls.get_find_first_mapping()) + + @classmethod + def get_valid_exts(cls) -> Sequence[str]: + # This exists because sometimes we want to recognize more extensions than just `ext` for auxiliary files + return (cls.ext,) + + @classmethod + def supports_multifile(cls) -> bool: + return False diff --git a/dmoj/executors/c_like_executor.py b/dmoj/executors/c_like_executor.py index e19ec963b..2e780df2f 100644 --- a/dmoj/executors/c_like_executor.py +++ b/dmoj/executors/c_like_executor.py @@ -1,7 +1,7 @@ import os import re from collections import deque -from typing import Dict, List, Optional, Type +from typing import Dict, List, Optional, Sequence, Type from dmoj.cptbox import TracedPopen from dmoj.executors.base_executor import AutoConfigOutput, AutoConfigResult, VersionFlags @@ -139,6 +139,10 @@ def initialize(cls) -> bool: cls.has_color = versions is not None and versions[0][1] is not None and versions[0][1] > (4, 9) return res + @classmethod + def supports_multifile(cls) -> bool: + return True + class GCCMixin(CLikeExecutor): arch: str = 'gcc_target_arch' @@ -161,6 +165,11 @@ def get_flags(self) -> List[str]: def get_version_flags(cls, command: str) -> List[VersionFlags]: return ['--version'] + def create_files(self, problem_id: str, source_code: bytes, *args, **kwargs) -> None: + super().create_files(problem_id, source_code, *args, **kwargs) + # Clang/++ will complain if you pass header files, so we strip them out + self.source_paths = [source for source in self.source_paths if source.partition('.')[-1] not in ('h', 'hpp')] + class CExecutor(CLikeExecutor): ext: str = 'c' @@ -170,3 +179,8 @@ class CExecutor(CLikeExecutor): class CPPExecutor(CLikeExecutor): ext: str = 'cpp' is_signature_gradable = True + + @classmethod + def get_valid_exts(cls) -> Sequence[str]: + # Recognize both `cpp` and `cc` as valid C++ extensions for auxiliary files + return 'cpp', 'cc' diff --git a/dmoj/problem.py b/dmoj/problem.py index fdc8c257a..ba1a94279 100644 --- a/dmoj/problem.py +++ b/dmoj/problem.py @@ -388,7 +388,7 @@ def _run_generator(self, gen: Union[str, ConfigNode], args: Optional[Iterable[st time_limit = env.generator_time_limit memory_limit = env.generator_memory_limit compiler_time_limit = env.generator_compiler_time_limit - lang = None # Default to C/C++ + lang = None # Default to autodetection base = get_problem_root(self.problem.id) assert base is not None diff --git a/dmoj/utils/helper_files.py b/dmoj/utils/helper_files.py index 88be4bfbe..715ce4bd0 100644 --- a/dmoj/utils/helper_files.py +++ b/dmoj/utils/helper_files.py @@ -35,22 +35,6 @@ def compile_with_auxiliary_files( with open(filename, 'rb') as f: sources[os.path.basename(filename)] = f.read() - def find_runtime(*languages): - for grader in languages: - if grader in executors.executors: - return grader - return None - - use_cpp = any(map(lambda name: os.path.splitext(name)[1] in ['.cpp', '.cc'], filenames)) - use_c = any(map(lambda name: os.path.splitext(name)[1] in ['.c'], filenames)) - if not lang: - if use_cpp: - lang = find_runtime('CPP20', 'CPP17', 'CPP14', 'CPP11', 'CPP03') - elif use_c: - lang = find_runtime('C11', 'C') - - # TODO: remove above code once `from_filename` is smart enough to - # prioritize newer versions of runtimes if not lang: for filename in filenames: try: @@ -69,15 +53,15 @@ def find_runtime(*languages): kwargs['compiler_time_limit'] = compiler_time_limit if hasattr(executor, 'flags'): - kwargs['flags'] = flags + list(executor.flags) + kwargs['flags'] = flags + executor.flags - # Optimize the common case. - if use_cpp or use_c: + # Check if the executor supports multifile compilation + if executor.supports_multifile(): # Some auxiliary files (like those using testlib.h) take an extremely long time to compile, so we cache them. executor = executor('_aux_file', None, aux_sources=sources, cached=True, unbuffered=unbuffered, **kwargs) else: if len(sources) > 1: - raise InternalError('non-C/C++ auxiliary programs cannot be multi-file') + raise InternalError(f'{lang} auxiliary programs cannot be multi-file') executor = executor('_aux_file', list(sources.values())[0], cached=True, unbuffered=unbuffered, **kwargs) return executor diff --git a/testsuite/bridged_interactor/interactor.cpp b/testsuite/bridged_interactor/interactor.cpp index 904e424fe..0425e3210 100644 --- a/testsuite/bridged_interactor/interactor.cpp +++ b/testsuite/bridged_interactor/interactor.cpp @@ -11,17 +11,18 @@ int main(int argc, char *argv[]) { int N, guesses = 0; long long guess; fscanf(input_file, "%d", &N); - while (guess != N) { + while (true) { read(&guess); + guesses++; if (guess == N) { puts("OK"); + break; } else if (guess > N) { puts("FLOATS"); } else { puts("SINKS"); } fflush(stdout); - guesses++; } if (guesses <= 31) return 0; // AC diff --git a/testsuite/bridged_interactor_noflush/interactor.cpp b/testsuite/bridged_interactor_noflush/interactor.cpp index 497e86892..761474008 100644 --- a/testsuite/bridged_interactor_noflush/interactor.cpp +++ b/testsuite/bridged_interactor_noflush/interactor.cpp @@ -11,16 +11,17 @@ int main(int argc, char *argv[]) { int N, guesses = 0; long long guess; fscanf(input_file, "%d", &N); - while (guess != N) { + while (true) { read(&guess); + guesses++; if (guess == N) { puts("OK"); + break; } else if (guess > N) { puts("FLOATS"); } else { puts("SINKS"); } - guesses++; } if (guesses <= 31) return 0; // AC diff --git a/testsuite/siggrade/tests/clang_signature_grading/sigtest.c b/testsuite/siggrade/tests/clang_signature_grading/sigtest.c new file mode 100644 index 000000000..7daeddf6d --- /dev/null +++ b/testsuite/siggrade/tests/clang_signature_grading/sigtest.c @@ -0,0 +1 @@ +bool is_valid(int n) { return n == 1; } diff --git a/testsuite/siggrade/tests/clang_signature_grading/test.yml b/testsuite/siggrade/tests/clang_signature_grading/test.yml new file mode 100644 index 000000000..2581334e4 --- /dev/null +++ b/testsuite/siggrade/tests/clang_signature_grading/test.yml @@ -0,0 +1,5 @@ +language: CLANG +time: 2 +memory: 65536 +source: sigtest.c +expect: AC