競技プログラミング好きの allegrogiken です。本業としてはバックエンドやインフラ周りを担当することが多いのですが、競技プログラミングの知見を活かせる機会は少なくて悲しいですね。

ところで、2023年の1月から8月にかけて AtCoder では言語アップデートの活動が行われていました。言語アップデート、それは環境構築です。 これなら本業の知見を AtCoder 側に活かすことができてハッピーです。環境構築は気持ちいいゾイ!

AtCoderの言語アップデートとは

AtCoderで使用できる言語環境を更新するための活動で、不定期に行われます。
公式情報として こちらのPost をご覧ください!前回の言語アップデートは2020年1月〜だったので、今回は実に3年ぶりですね!

言語環境の提供はコミュニティ的になっており、誰でも参加することができます。このためか、AtCoder は選べる言語がかなり多い状態がキープされている印象がありますね。

なお、今回の言語アップデートは完了しており、2023/08/12 開催の ABC314 から新しい環境でのコンテストが開催されています。

言語アップデートに協力する方法

言語アップデートは 1枚のGoogle Spreadsheet の上でほぼ全ての活動が行われています。このシートに対して、期間内に下記のような情報を記載することで言語環境の候補を立てることができます!

  • 使いたい言語のバージョン・ライブラリの一覧(と、そのライセンス)
  • 言語環境をインストールするためのスクリプト
  • コードをビルド、実行するためのコマンド列など

日常的に環境構築を行なっているエンジニアなら、そこまで難しいものではないはず、です!

D言語の実行環境を提供してみた

私はAtCoder上ではD言語というものを使って参加しているのですが、この言語はお世辞にもメジャーな言語とは言えません。誰かが提供しなければ言語環境が消えることもあり得るので、今回は私も積極的に提案を進めてみました。

AtCoderの実行環境っぽい仮想環境を作る

言語環境セットアップスクリプトを書く前に、それを動かすための基盤を整備してみようと思います。
具体的には、仮想環境を作ります。仮想環境なら何度でもクリーンな状態でやり直しができて検証がやりやすいです。

OSの種類・バージョンなどの環境情報は公式からある程度提供されており、AtCoder 上での動作に近い仮想環境を作ることができそうです。今回は Docker でトライしてみました。

FROM ubuntu:22.10

ARG TARGET
ENV TARGET $TARGET

RUN apt update; apt install -y wget sudo

ENV USERNAME=runner
RUN adduser --disabled-password --gecos "" "${USERNAME}" && \
    echo "%${USERNAME} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
USER ${USERNAME}

WORKDIR /judge
ADD ${TARGET}.sh /tmp/setup.sh
RUN bash /tmp/setup.sh && sudo rm /tmp/setup.sh

ベースOS・実行ユーザ・ワーキングディレクトリといった設定は公式から提供されている前提条件に従っています。非root ユーザで sudo できる必要があるので、コンテナでもそれが再現できるようにしています。

ビルド時の引数 TARGET には「環境名」を渡せるようにしています。それと同名のセットアップ用スクリプトを配置してビルドを行うと、言語環境ごとのイメージができる・・ という設計を狙いました。

D言語のセットアップスクリプトを作る

続いて言語環境のセットアップです。D言語にはいくつかのコンパイラがありますが、今回は「LDC」というコンパイラ向けのセットアップスクリプトを書きます。
下のように ldc.sh を書いて、docker build --build-arg TARGET=ldc . でコンテナイメージができるはず。

sudo apt update
sudo apt install -y --no-install-recommends curl build-essential ca-certificates libxml2

D_COMPLILER="ldc-1.32.2"

wget https://dlang.org/install.sh -O /tmp/install.sh
chmod +x /tmp/install.sh
/tmp/install.sh install ${D_COMPLILER} 

sudo ln -s $(/tmp/install.sh get-path ${D_COMPLILER}) /usr/local/bin/ldc2
sudo ln -s $(/tmp/install.sh get-path --dmd ${D_COMPLILER}) /usr/local/bin/ldmd2
sudo ln -s $(/tmp/install.sh get-path --dub ${D_COMPLILER}) /usr/local/bin/dub

dub init -n
dub add mir
dub build  --build=release-nobounds
rm judge source/app.d

このスクリプトについては詳解しませんが、私を含めて何人かの手によって完成したものです。下のようなポイントを意識できて良い出来になったのではないかと思っています。

  • 言語バージョンの更新が楽にできる
  • ライブラリの追加が楽にできる

ネットワーク環境を再現し、ビルド&実行をテストする

ここまでで「言語環境のセットアップ」ができています。あとはD言語のソースコードをコンテナに与えて、ビルド・実行を確認したいです。docker run でビルドと実行を確認できるように整えていきます。

まずは提出用のプログラムが必要です。適当なサンプルを app.d として別で作成しておき、実行時にマウントします。それをビルド・実行するコマンドを docker run に渡せば良いでしょう。

ところで、一般的なコンテストサイトにおいて提出されたソースコードをビルド・実行する際にはオフライン環境になるはずです。そのため、ビルド時に依存ライブラリの確認でインターネットにアクセスしてしまう・・ みたいなことがあるとエラーになってしまいます。そのような事故を事前に防ぐため、ネットワーク環境についてもできるだけ再現しておきましょう。

ここまでの要件をまとめて、docker コマンドを錬成するのは少々骨が折れます。下のような docker-compose.yml ファイルを作成して楽にやっていきましょう。

version: '3'

networks:
  no_out:
    name: no_out
    internal: true

services:
  ldc:
    build:
      context: .
      args:
        - TARGET=ldc
    volumes:
      - ./app.d:/judge/source/app.d
    networks:
      - no_out
    command: sh -c "dub build --skip-registry=all --nodeps --build=release-nobounds && ./judge"

ここまでの成果物を揃えた状態にすれば、 docker compose run ldc で確認ができます。やってみましょう。

% docker compose run ldc

[+] Creating 1/0
 ✔ Network no_out  Created                                                                                                                                                                                    0.0s 
Performing "release-nobounds" build using /home/runner/dlang/ldc-1.32.2/bin/ldc2 for aarch64, arm_hardfloat.
mir-core 1.6.0: building configuration "library"...
mir-algorithm 3.21.0: building configuration "default"...
mir-random 2.2.19: building configuration "extended"...
judge ~master: building configuration "application"...
Linking...

Hello, D!
[[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 6]]

いい感じですね!

言語アップデートの提案シートに転記する

テストがいい感じだったので、これをもとにAtCoderの言語アップデート情報として提供していきましょう。言語アップデートの提案シート の各セルに、下の要領で記載をしていきます。

  • Install Command
    • セットアップスクリプト全体をそのまま転記
  • Compilation Command
    • docker-compose.yml における command の前半
  • Execution Command
    • docker-compose.yml における command の後半
  • Filename
    • docker-compose.yml における volumes のコンテナ側

言語アップデート専用のコンテストでテストする

記載した内容にライセンス的な問題がなければ、そのうちテスト用コンテストで試せるようになります。そこで問題なく動作確認ができればOKです。

問題が確認された場合は修正できますが、反映はリアルタイムではありません。一定のスパンでの反映となります。そのため修正の回数・期間はなるべく小さく済ませられる方が良いと思います。

感想と今後の展望について

難しかったです。初版を提供してから、1回目はビルド時のエラーで動いてくれませんでした。2回目でなんとか正常に動く環境になりました。また「こういったソースコードの場合にビルドが終わらない」みたいな言語固有の問題もありました。これは他の方に SpreadSheet 上で修正提案をいただけて感謝の極みでした。

「仮装環境でAtCoderを再現する」ことをやりましたが、この辺はベースイメージみたいな物が AtCoder から提供されるとやりやすくなるので嬉しいと思います。もし公式の方が見ていたらご検討ください!

今回のアップデートで、D言語では簡単にライブラリを導入できるようにしたつもりなので、次回のアップデートでは ac-library のD言語版を導入したいですね。今後も継続的にやっていきたいと思います。

なお、今回記載した成果物は全て こちらの GItHubリポジトリ にまとめていますので、興味のある方はそちらも参考にしていただけると幸いです。 Dockerfile だけ見ると、どんな言語でも使える汎用的な作りになっているはずではあります。

環境構築に自信がある方、ぜひ次回のアップデートでやってみましょう!