AlgoJudge documentation0.1

Languages

The eighteen toolchains a Runner ships with, why an id names a compiler as well as a standard, and the four images without which it judges nothing.

The Runner ships with eighteen toolchains in three families, and they share four images. The catalogue is data: adding a row is a table change, not a redesign.

An id has two levels, because it answers two questions

A participant reading a problem header wants the standard — "C++17". A participant choosing on the submit form needs the toolchain, because g++ and clang++ disagree about enough that a submission accepted by one is occasionally rejected by the other.

So the id carries both: cpp17-gcc, never cpp17. A standard is not a toolchain. cpp17 on its own resolves to nothing and is refused, because guessing which compiler somebody meant is exactly what two levels exist to prevent.

The catalogue

IdLabelFamilyImage
c89-gccC89 / ANSI C (GCC)CGCC
c89-clangC89 / ANSI C (Clang)CClang
c99-gccC99 (GCC)CGCC
c99-clangC99 (Clang)CClang
c11-gccC11 (GCC)CGCC
c11-clangC11 (Clang)CClang
c23-gccC23 (GCC)CGCC
c23-clangC23 (Clang)CClang
cpp11-gccC++11 (GCC)C++GCC
cpp11-clangC++11 (Clang)C++Clang
cpp17-gccC++17 (GCC)C++GCC
cpp17-clangC++17 (Clang)C++Clang
cpp20-gccC++20 (GCC)C++GCC
cpp20-clangC++20 (Clang)C++Clang
cpp23-gccC++23 (GCC)C++GCC
cpp23-clangC++23 (Clang)C++Clang
python3Python 3 (CPython)PythonCPython
pypy3Python 3 (PyPy)PythonPyPy

Every C and C++ standard is offered through both compilers, which is what a course teaching a particular standard may legitimately want to pin.

PyPy is a separate row rather than a footnote because the gap changes what a participant can do: a Python solution that times out on CPython frequently does not on PyPy.

What each family accepts, and how it is built

FamilyFile extensionsBuilt with
C.cthe compiler, -O2 -std=… and -static
C++.cpp, .cc, .cxx, .c++the compiler, -O2 -std=… and -static
Python.pypy_compile

Extensions are matched case-insensitively. A file the chosen toolchain does not accept is refused as a compilation error, not as an infrastructure failure: choosing C++ and uploading main.py is the participant's own mistake, the compiler would have said the same thing thirty seconds later, and a message naming the extensions is more use than a parse error in a language nobody thought they were writing.

-O2 is on every compiled row because a limit is stated against optimised code; judging a debug build would make every limit a different limit.

Python is not compiled, but it does have a build step. py_compile turns a syntax error into one failure a participant can read, rather than into every test failing with the same traceback.

Two ids that resolve but are not offered

cpp and python were the whole catalogue once, and a problem package's config.yml names the language of a checker, an interactor and a model solution using them. They still resolve — cpp to cpp20-gcc, python to python3 — so that packages written against the documented format keep building their own judging programs. A checker and an interactor are built by the same path, in the same image, with the same command as a submission. They are not offered on any form.

cpp resolves to C++20 specifically because that is the standard the single C++ entry carried before the catalogue existed, so a package judged yesterday is judged the same way today.

What is not here

Java and Rust are not in the catalogue and are not accepted. Java was considered and left out for a measurement reason: the JVM reserves address space rather than allocating it, so a memory limit would measure something different for it than for everything else.

Forwarding to an external judge offers six languages including Java and Pascal — by forwarding to a service that runs them, not by running them.

Four images, and a Runner without them judges nothing

Image keyVariable
GCCAJ_Sandbox__Image__Gcc
ClangAJ_Sandbox__Image__Clang
CPythonAJ_Sandbox__Image__Python
PyPyAJ_Sandbox__Image__Pypy

Eighteen toolchains share four images because every C and C++ standard is a -std flag rather than an image of its own.

The defaults, and the older AJ_Sandbox__Image__Cpp spelling that is still read, are on Configuration — stated once, so that the two pages cannot drift apart.

The language images are pulled separately from the Runner, and a Runner without them judges nothing

The Runner image contains no compiler. Every submission is built and run inside one of the four images above, and a Runner that cannot pull them starts, is approved, claims work, and fails every job it is given.

The defaults are the tags a development stack builds locally. A deployment names all four explicitly and pins the same version as the Runner — that pairing is what the release was tested as.

The compiler version is a capability the Runner reports, which is why the images are the project's own and pinned rather than a stock compiler image that moves. A tag that moves means two submissions to the same problem were judged by two different compilers.

Building an image of your own

AJ_Sandbox__Image__* will take any image, which is how you pin a compiler version we do not ship or add one to a fork. Four things are expected of it.

What, and why
/etc/algojudge-toolchainone line per compiler, whatever --version prints. The Runner reports it as a capability, and it is how two submissions to one problem can be shown to have met the same compiler
/out, owned by 65534:65534where a build puts what it made. Part of the image because the container is unprivileged and cannot create a directory at the root of its own filesystem
USER 65534:65534the safe default, and the one a step that is not measured runs as
/usr/local/bin/aj-shimthe measuring shim, below

The shim, and how to get one

A measured run is started as aj-shim <input-file> <program> [args...]. The shim becomes PID 1, opens the input file onto the submission's standard input, drops to 65534, forks, and reports the child's own processor time and peak memory — see Isolation for what that is worth and why it is not trusted on its own.

Copy ours rather than writing one. It is statically linked and depends on nothing in the image around it:

COPY --from=ghcr.io/algojudge/lang-gcc:0.1.0 /usr/local/bin/aj-shim /usr/local/bin/aj-shim

Pin the same version as the Runner, for the reason the callout above gives about tags that move.

An image without it still judges

Leave the shim out and nothing breaks: the report is absent, the reading from the cgroup stands on its own, and the container's own start is inside the measurement again — 33 to 74 ms of it, against limits that can be 100. The Runner says so once per image rather than once per submission.

What you must not do is ship a different program under that name. The Runner reads what the report says; a shim that reports less than the program spent is a submission that beats the time limit, and one that reports more is a correct solution refused.

What decides which toolchains a problem allows

Not the Runner. An assignment's configuration may list the languages it accepts, and the Runner refuses anything outside that list as a policy violation — a verdict rather than an infrastructure failure, because the participant chose it, their code may be perfect, and what they broke is a rule of the activity. The submission stays rejudgeable if a manager widens the set afterwards.

An empty list means the assignment said nothing, which allows anything this Runner can build. It is not a way of allowing none.

The list may name a family as well as a toolchain: cpp in an assignment's languages accepts every C++ row. That two-level lookup exists to close a silent failure — a rule keyed on the whole id alone would have found nothing for cpp17-gcc and reported no violations, which reads exactly like a clean submission.

On this page