Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ WORKDIR /var/www/html
RUN apk add --no-cache \
git \
curl \
wget \
ca-certificates \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
Expand All @@ -26,7 +28,69 @@ RUN apk add --no-cache \
linux-headers \
$PHPIZE_DEPS \
# Supervisor for queue workers
supervisor
supervisor \
# Compilers for autojudge -- this list must cover every language
# Language::getDefaultLanguages() marks is_active by default, or admins
# can select a language in the Contest Wizard that the judge then can't
# actually compile/run.
gcc \
g++ \
make \
python3 \
openjdk21-jdk \
nodejs \
npm \
dotnet8-sdk \
go \
rust \
ruby \
# AutoJudgeService::runCustomScript()/runCompareScript() invoke problem
# compile/run/compare scripts via `bash`, which Alpine doesn't ship by default
bash

# TypeScript (npx tsc, used by the "TypeScript (Node 24)" language)
RUN npm install -g typescript

# Kotlin has no Alpine package; JetBrains only ships it as a GitHub release
# zip. This is the same install method used by every other Kotlin judge
# container (it's pure JVM + a launcher script, so it's arch-independent
# given the JDK above).
ARG KOTLIN_VERSION=2.4.20
RUN wget -q "https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip" -O /tmp/kotlin.zip \
&& unzip -q /tmp/kotlin.zip -d /opt \
&& rm /tmp/kotlin.zip \
&& ln -s /opt/kotlinc/bin/kotlinc /usr/local/bin/kotlinc \
&& ln -s /opt/kotlinc/bin/kotlin /usr/local/bin/kotlin

# Free Pascal has no Alpine package either. Its official releases ship a
# pre-built compiler binary per architecture (no compilation needed); we
# only need the compiler + RTL units, not the full units-* extras or docs.
# The SourceForge download occasionally serves a truncated file under
# automated tools, hence the retry loop with byte-count verification.
ARG FPC_VERSION=3.2.2
RUN set -eu; \
arch="$(apk --print-arch)"; \
case "$arch" in \
x86_64) fpc_arch=x86_64-linux; fpc_bin=ppcx64 ;; \
aarch64) fpc_arch=aarch64-linux; fpc_bin=ppca64 ;; \
*) echo "Unsupported arch for FPC: $arch" >&2; exit 1 ;; \
esac; \
url="https://sourceforge.net/projects/freepascal/files/Linux/${FPC_VERSION}/fpc-${FPC_VERSION}.${fpc_arch}.tar/download"; \
tries=0; \
until wget -q -c "$url" -O /tmp/fpc.tar && tar -tf /tmp/fpc.tar >/dev/null 2>&1; do \
tries=$((tries + 1)); \
[ "$tries" -ge 6 ] && { echo "Failed to download a valid FPC tarball after $tries attempts" >&2; exit 1; }; \
rm -f /tmp/fpc.tar; \
done; \
mkdir -p /tmp/fpcx && tar -xf /tmp/fpc.tar -C /tmp/fpcx; \
cd "/tmp/fpcx/fpc-${FPC_VERSION}.${fpc_arch}"; \
tar -xf "binary.${fpc_arch}.tar" "base.${fpc_arch}.tar.gz"; \
mkdir -p "/opt/fpc/${FPC_VERSION}"; \
tar -xzf "base.${fpc_arch}.tar.gz" -C "/opt/fpc/${FPC_VERSION}"; \
install -m 755 "/opt/fpc/${FPC_VERSION}/lib/fpc/${FPC_VERSION}/${fpc_bin}" /usr/local/bin/fpc; \
printf -- '-Fu/opt/fpc/%s/lib/fpc/%s/units/%s/rtl\n-Fl/opt/fpc/%s/lib/fpc/%s/units/%s/rtl\n' \
"$FPC_VERSION" "$FPC_VERSION" "$fpc_arch" "$FPC_VERSION" "$FPC_VERSION" "$fpc_arch" > /etc/fpc.cfg; \
rm -rf /tmp/fpc.tar /tmp/fpcx

# Configure and install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
Expand Down
58 changes: 57 additions & 1 deletion Dockerfile.dev
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ WORKDIR /var/www/html
RUN apk add --no-cache \
git \
curl \
wget \
ca-certificates \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
Expand All @@ -24,15 +26,69 @@ RUN apk add --no-cache \
postgresql-dev \
linux-headers \
$PHPIZE_DEPS \
# Compilers for autojudge
# Compilers for autojudge -- this list must cover every language
# Language::getDefaultLanguages() marks is_active by default, or admins
# can select a language in the Contest Wizard that the judge then can't
# actually compile/run.
gcc \
g++ \
make \
python3 \
openjdk21-jdk \
nodejs \
npm \
dotnet8-sdk \
go \
rust \
ruby \
# AutoJudgeService::runCustomScript()/runCompareScript() invoke problem
# compile/run/compare scripts via `bash`, which Alpine doesn't ship by default
bash

# TypeScript (npx tsc, used by the "TypeScript (Node 24)" language)
RUN npm install -g typescript

# Kotlin has no Alpine package; JetBrains only ships it as a GitHub release
# zip. This is the same install method used by every other Kotlin judge
# container (it's pure JVM + a launcher script, so it's arch-independent
# given the JDK above).
ARG KOTLIN_VERSION=2.4.20
RUN wget -q "https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip" -O /tmp/kotlin.zip \
&& unzip -q /tmp/kotlin.zip -d /opt \
&& rm /tmp/kotlin.zip \
&& ln -s /opt/kotlinc/bin/kotlinc /usr/local/bin/kotlinc \
&& ln -s /opt/kotlinc/bin/kotlin /usr/local/bin/kotlin

# Free Pascal has no Alpine package either. Its official releases ship a
# pre-built compiler binary per architecture (no compilation needed); we
# only need the compiler + RTL units, not the full units-* extras or docs.
# The SourceForge download occasionally serves a truncated file under
# automated tools, hence the retry loop with byte-count verification.
ARG FPC_VERSION=3.2.2
RUN set -eu; \
arch="$(apk --print-arch)"; \
case "$arch" in \
x86_64) fpc_arch=x86_64-linux; fpc_bin=ppcx64 ;; \
aarch64) fpc_arch=aarch64-linux; fpc_bin=ppca64 ;; \
*) echo "Unsupported arch for FPC: $arch" >&2; exit 1 ;; \
esac; \
url="https://sourceforge.net/projects/freepascal/files/Linux/${FPC_VERSION}/fpc-${FPC_VERSION}.${fpc_arch}.tar/download"; \
tries=0; \
until wget -q -c "$url" -O /tmp/fpc.tar && tar -tf /tmp/fpc.tar >/dev/null 2>&1; do \
tries=$((tries + 1)); \
[ "$tries" -ge 6 ] && { echo "Failed to download a valid FPC tarball after $tries attempts" >&2; exit 1; }; \
rm -f /tmp/fpc.tar; \
done; \
mkdir -p /tmp/fpcx && tar -xf /tmp/fpc.tar -C /tmp/fpcx; \
cd "/tmp/fpcx/fpc-${FPC_VERSION}.${fpc_arch}"; \
tar -xf "binary.${fpc_arch}.tar" "base.${fpc_arch}.tar.gz"; \
mkdir -p "/opt/fpc/${FPC_VERSION}"; \
tar -xzf "base.${fpc_arch}.tar.gz" -C "/opt/fpc/${FPC_VERSION}"; \
install -m 755 "/opt/fpc/${FPC_VERSION}/lib/fpc/${FPC_VERSION}/${fpc_bin}" /usr/local/bin/fpc; \
printf -- '-Fu/opt/fpc/%s/lib/fpc/%s/units/%s/rtl\n-Fl/opt/fpc/%s/lib/fpc/%s/units/%s/rtl\n' \
"$FPC_VERSION" "$FPC_VERSION" "$fpc_arch" "$FPC_VERSION" "$FPC_VERSION" "$fpc_arch" > /etc/fpc.cfg; \
rm -rf /tmp/fpc.tar /tmp/fpcx

# Configure and install PHP extensions
RUN docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) \
Expand Down
105 changes: 55 additions & 50 deletions Dockerfile.judge
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ LABEL description="MicroHelium Auto-Judge Worker"

WORKDIR /var/www/html

# Install system dependencies for compilation
# Install system dependencies for compilation -- this list must cover every
# language Language::getDefaultLanguages() marks is_active (see
# Dockerfile/Dockerfile.dev, which install the identical set for the
# queue-worker judging path; this image is the standalone autojudge:start
# worker's, and must not silently drift out of sync with it).
RUN apk add --no-cache \
git \
curl \
wget \
ca-certificates \
libpng-dev \
libjpeg-turbo-dev \
freetype-dev \
Expand All @@ -33,15 +38,20 @@ RUN apk add --no-cache \
libc-dev \
libstdc++ \
# Java (for Java, Kotlin)
openjdk17-jdk \
openjdk21-jdk \
# Python 3
python3 \
py3-pip \
# Node.js / TypeScript -- a single real runtime, not nvm: see
# Dockerfile.dev's comment on why multiple Node majors aren't installed
nodejs \
npm \
# .NET / C#
dotnet8-sdk \
# Go
go \
# Rust
rust \
cargo \
# Ruby
ruby \
# Process management
Expand All @@ -50,53 +60,48 @@ RUN apk add --no-cache \
coreutils \
# Memory limiting
util-linux \
# Bash for scripts and nvm
bash \
# Required for nvm
shadow

# Install nvm (Node Version Manager) and multiple Node.js versions
ENV NVM_DIR=/opt/nvm
ENV NODE_VERSION_DEFAULT=20
ENV NODE_VERSIONS="18 20 22"

RUN mkdir -p $NVM_DIR && \
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash && \
. $NVM_DIR/nvm.sh && \
# Install multiple Node.js versions
for version in $NODE_VERSIONS; do \
nvm install $version; \
done && \
# Set default version
nvm alias default $NODE_VERSION_DEFAULT && \
nvm use default && \
# Install TypeScript globally for default version
npm install -g typescript && \
# Create wrapper scripts for node and npm
ln -sf $NVM_DIR/versions/node/v$(nvm version default | tr -d 'v')/bin/node /usr/local/bin/node && \
ln -sf $NVM_DIR/versions/node/v$(nvm version default | tr -d 'v')/bin/npm /usr/local/bin/npm && \
ln -sf $NVM_DIR/versions/node/v$(nvm version default | tr -d 'v')/bin/npx /usr/local/bin/npx && \
ln -sf $NVM_DIR/versions/node/v$(nvm version default | tr -d 'v')/bin/tsc /usr/local/bin/tsc

# Create nvm helper script for runtime version switching
RUN echo '#!/bin/bash\n\
source $NVM_DIR/nvm.sh\n\
nvm use $1 > /dev/null 2>&1\n\
shift\n\
exec node "$@"' > /usr/local/bin/node-version && \
chmod +x /usr/local/bin/node-version

# nvm environment setup
ENV PATH=$NVM_DIR/versions/node/v20/bin:$PATH

# Install Kotlin compiler
ENV KOTLIN_VERSION=1.9.22
RUN cd /tmp && \
wget -q "https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip" && \
unzip -q kotlin-compiler-${KOTLIN_VERSION}.zip -d /opt && \
rm kotlin-compiler-${KOTLIN_VERSION}.zip && \
ln -s /opt/kotlinc/bin/kotlin /usr/local/bin/kotlin && \
ln -s /opt/kotlinc/bin/kotlinc /usr/local/bin/kotlinc
# Bash for scripts
bash

RUN npm install -g typescript

# Kotlin has no Alpine package; JetBrains only ships it as a GitHub release
# zip (pure JVM + a launcher script, arch-independent given the JDK above).
ARG KOTLIN_VERSION=2.4.20
RUN wget -q "https://github.com/JetBrains/kotlin/releases/download/v${KOTLIN_VERSION}/kotlin-compiler-${KOTLIN_VERSION}.zip" -O /tmp/kotlin.zip \
&& unzip -q /tmp/kotlin.zip -d /opt \
&& rm /tmp/kotlin.zip \
&& ln -s /opt/kotlinc/bin/kotlinc /usr/local/bin/kotlinc \
&& ln -s /opt/kotlinc/bin/kotlin /usr/local/bin/kotlin

# Free Pascal has no Alpine package either; installed from the official
# per-architecture SourceForge binary tarball. See Dockerfile.dev for the
# detailed rationale (retry loop: the SourceForge download occasionally
# serves a truncated file under automated tools).
ARG FPC_VERSION=3.2.2
RUN set -eu; \
arch="$(apk --print-arch)"; \
case "$arch" in \
x86_64) fpc_arch=x86_64-linux; fpc_bin=ppcx64 ;; \
aarch64) fpc_arch=aarch64-linux; fpc_bin=ppca64 ;; \
*) echo "Unsupported arch for FPC: $arch" >&2; exit 1 ;; \
esac; \
url="https://sourceforge.net/projects/freepascal/files/Linux/${FPC_VERSION}/fpc-${FPC_VERSION}.${fpc_arch}.tar/download"; \
tries=0; \
until wget -q -c "$url" -O /tmp/fpc.tar && tar -tf /tmp/fpc.tar >/dev/null 2>&1; do \
tries=$((tries + 1)); \
[ "$tries" -ge 6 ] && { echo "Failed to download a valid FPC tarball after $tries attempts" >&2; exit 1; }; \
rm -f /tmp/fpc.tar; \
done; \
mkdir -p /tmp/fpcx && tar -xf /tmp/fpc.tar -C /tmp/fpcx; \
cd "/tmp/fpcx/fpc-${FPC_VERSION}.${fpc_arch}"; \
tar -xf "binary.${fpc_arch}.tar" "base.${fpc_arch}.tar.gz"; \
mkdir -p "/opt/fpc/${FPC_VERSION}"; \
tar -xzf "base.${fpc_arch}.tar.gz" -C "/opt/fpc/${FPC_VERSION}"; \
install -m 755 "/opt/fpc/${FPC_VERSION}/lib/fpc/${FPC_VERSION}/${fpc_bin}" /usr/local/bin/fpc; \
printf -- '-Fu/opt/fpc/%s/lib/fpc/%s/units/%s/rtl\n-Fl/opt/fpc/%s/lib/fpc/%s/units/%s/rtl\n' \
"$FPC_VERSION" "$FPC_VERSION" "$fpc_arch" "$FPC_VERSION" "$FPC_VERSION" "$fpc_arch" > /etc/fpc.cfg; \
rm -rf /tmp/fpc.tar /tmp/fpcx

# Set Go environment
ENV GOPATH=/go
Expand Down
30 changes: 22 additions & 8 deletions app/Models/Language.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,37 @@ public static function getDefaultLanguages(): array

// Python
['name' => 'Python 3.12', 'extension' => 'py3', 'file_ext' => 'py', 'compile_command' => 'python3 -m py_compile {source}', 'run_command' => 'python3 {source}', 'is_active' => true, 'category' => 'interpreted'],
['name' => 'Python 3 (PyPy)', 'extension' => 'pypy3', 'file_ext' => 'py', 'compile_command' => 'pypy3 -m py_compile {source}', 'run_command' => 'pypy3 {source}', 'is_active' => true, 'category' => 'interpreted'],
// PyPy has no musl/Alpine build (upstream only ships glibc
// binaries), so it's left inactive rather than silently failing
// every submission; CPython 3.12 above covers the language.
['name' => 'Python 3 (PyPy)', 'extension' => 'pypy3', 'file_ext' => 'py', 'compile_command' => 'pypy3 -m py_compile {source}', 'run_command' => 'pypy3 {source}', 'is_active' => false, 'category' => 'interpreted'],
['name' => 'Python 2.7', 'extension' => 'py2', 'file_ext' => 'py', 'compile_command' => 'python2 -m py_compile {source}', 'run_command' => 'python2 {source}', 'is_active' => false, 'category' => 'interpreted'],

// JavaScript / Node.js (using NVM)
['name' => 'JavaScript (Node 24)', 'extension' => 'js_node24', 'file_ext' => 'js', 'compile_command' => 'node --check {source}', 'run_command' => 'source ~/.nvm/nvm.sh && nvm use 24 && node {source}', 'is_active' => true, 'category' => 'interpreted'],
['name' => 'JavaScript (Node 22)', 'extension' => 'js_node22', 'file_ext' => 'js', 'compile_command' => 'node --check {source}', 'run_command' => 'source ~/.nvm/nvm.sh && nvm use 22 && node {source}', 'is_active' => true, 'category' => 'interpreted'],
['name' => 'JavaScript (Node 20 LTS)', 'extension' => 'js_node20', 'file_ext' => 'js', 'compile_command' => 'node --check {source}', 'run_command' => 'source ~/.nvm/nvm.sh && nvm use 20 && node {source}', 'is_active' => true, 'category' => 'interpreted'],
['name' => 'TypeScript (Node 22)', 'extension' => 'ts', 'file_ext' => 'ts', 'compile_command' => 'npx tsc --strict --outFile {output}.js {source}', 'run_command' => 'source ~/.nvm/nvm.sh && nvm use 22 && node {executable}.js', 'is_active' => true, 'category' => 'compiled'],
// JavaScript / Node.js -- the container only installs a single
// Node runtime (the current LTS, via Alpine's `nodejs` package),
// so only one Node entry is real; the others are kept in the
// catalog for a future multi-version (nvm-based) setup but left
// inactive since selecting them today would silently fail.
['name' => 'JavaScript (Node 24 LTS)', 'extension' => 'js_node24', 'file_ext' => 'js', 'compile_command' => 'node --check {source}', 'run_command' => 'node {source}', 'is_active' => true, 'category' => 'interpreted'],
['name' => 'JavaScript (Node 22)', 'extension' => 'js_node22', 'file_ext' => 'js', 'compile_command' => 'node --check {source}', 'run_command' => 'node {source}', 'is_active' => false, 'category' => 'interpreted'],
['name' => 'JavaScript (Node 20 LTS)', 'extension' => 'js_node20', 'file_ext' => 'js', 'compile_command' => 'node --check {source}', 'run_command' => 'node {source}', 'is_active' => false, 'category' => 'interpreted'],
['name' => 'TypeScript (Node 24)', 'extension' => 'ts', 'file_ext' => 'ts', 'compile_command' => 'npx tsc --strict --module commonjs {source}', 'run_command' => 'node {executable}.js', 'is_active' => true, 'category' => 'compiled'],

// JVM Languages
['name' => 'Kotlin (1.9)', 'extension' => 'kt', 'file_ext' => 'kt', 'compile_command' => 'kotlinc {source} -include-runtime -d {output}.jar', 'run_command' => 'java -jar {executable}.jar', 'is_active' => true, 'category' => 'compiled'],
['name' => 'Kotlin (2.4)', 'extension' => 'kt', 'file_ext' => 'kt', 'compile_command' => 'kotlinc {source} -include-runtime -d {output}.jar', 'run_command' => 'java -jar {executable}.jar', 'is_active' => true, 'category' => 'compiled'],
['name' => 'Scala 3', 'extension' => 'scala', 'file_ext' => 'scala', 'compile_command' => 'scalac {source}', 'run_command' => 'scala {classname}', 'is_active' => false, 'category' => 'compiled'],
['name' => 'Groovy 4', 'extension' => 'groovy', 'file_ext' => 'groovy', 'compile_command' => 'groovyc {source}', 'run_command' => 'groovy {source}', 'is_active' => false, 'category' => 'interpreted'],
['name' => 'Clojure', 'extension' => 'clj', 'file_ext' => 'clj', 'compile_command' => 'clojure -M --main clojure.main --eval "(compile \'main)"', 'run_command' => 'clojure {source}', 'is_active' => false, 'category' => 'interpreted'],

// .NET Languages
['name' => 'C# (.NET 8)', 'extension' => 'cs_dotnet', 'file_ext' => 'cs', 'compile_command' => 'dotnet build', 'run_command' => 'dotnet run', 'is_active' => true, 'category' => 'compiled'],
// `dotnet build`/`dotnet run` need a project, not a bare .cs
// file, so C# uses wrapper scripts that scaffold a throwaway
// console project around the submitted source before building
// it (see resources/judge-runtime/csharp/). {judge_runtime} is
// resolved by AutoJudgeService, not here -- this array must stay
// callable from contexts where the app isn't booted yet (e.g.
// PHPUnit data providers), where base_path() isn't available.
['name' => 'C# (.NET 8)', 'extension' => 'cs_dotnet', 'file_ext' => 'cs', 'compile_command' => 'bash {judge_runtime}/csharp/compile.sh {source} {output}', 'run_command' => 'bash {judge_runtime}/csharp/run.sh {executable}', 'is_active' => true, 'category' => 'compiled'],
['name' => 'C# (Mono)', 'extension' => 'cs_mono', 'file_ext' => 'cs', 'compile_command' => 'mcs -out:{output}.exe {source}', 'run_command' => 'mono {executable}.exe', 'is_active' => false, 'category' => 'compiled'],
['name' => 'F# (.NET 8)', 'extension' => 'fs_dotnet', 'file_ext' => 'fs', 'compile_command' => 'dotnet build', 'run_command' => 'dotnet run', 'is_active' => false, 'category' => 'compiled'],
['name' => 'Visual Basic (.NET 8)', 'extension' => 'vb', 'file_ext' => 'vb', 'compile_command' => 'dotnet build', 'run_command' => 'dotnet run', 'is_active' => false, 'category' => 'compiled'],
Expand Down
Loading