💭

VSCodeのdevcontainerでAtCoder環境を構築

2025/01/20に公開

せっかくだからいろいろな言語でAtCoderをやってみたい、と思っている皆さんには、ちょっと参考になることもあるかと。

環境の前提

  • Windows
  • WSL2
  • VSCode

ディレクトリ構造

冒頭から、ディレクトリ構造を。

Workspaces
        ├─ C++
        │   ├─ .devcontainer
        │   │   ├─ Dockerfile
        │   │
        │   ├─ .vscode
        │   │   ├─ tasks.json
        │   │   ├─ cpp.code-snippets
        │   │
        │   ├─ C++.code-workspace
        │   ├─.Contest
        │   │   ├─ A.cpp
        │   │
        │   ├─ test
        │   │   ├─ sample-1.in
        │   │   ├─ sample-1.out
        │   │
        │   ├─ a.out
        │   ├─ .Library
        │       ├─ DP
        │
        ├─ Python
        ├─ Nim

Solved_Code
        ├─ ABC
            ├─ 123
                ├─ A.cpp
                ├─ B.py
                ├─ C.nim
  • 環境部分は複数言語用意することを念頭に、提出コード集とはわけてみた
    ※そもそも提出コードを保存する意味あるのか?という話もあるが、単なる思い出だけでなく、検索できるのは一定の意味があるかと

  • 環境部分は言語毎の以下のセット

    • Dockerfile含む.devcontainerディレクトリ
    • tasks.jsonやら、snippetsやらを含む.vscodeディレクトリ
    • .code-workspaceファイル

    それに加えて

    • 解答中のプログラムを一時的に入れておく.Contestディレクトリ(.をつけて上にあげるといい感じ)
    • テストを落とすtsstディレクトリ(なんでoj-prepareを使わないんだよ、という話ですが、ぽつぽつ1問ずつ解くこともあるじゃないですか、ということです)
    • コンパイル後の実行ファイル
    • お手製のライブラリを入れる.Library
  • 提出コード集の方は、コンテストカテゴリー別にしつつ、言語はごちゃ混ぜ

  • Windowsだし、個人作業ならOneDriveは非常に快適なので、gitはお好みで

Dockerfile

基本はubuntunに、2023年の言語アップデート時の提案シート通りにinstall commandを書いていくだけ。
みんな大好きonline-judge-toolsと、verification-helperも入れておきます。
(online-judge-toolsについては、このDockerfileを使ったVSCodeのdevcontainer内でsubmitしても、結果表示のブラウザが立ち上がらなかったので、submit.pyのshow resultのところのutils.webbrowser_register_explorer_exe()の行をコメントアウトして使っています。)

Dockerfile_AtCoder-C++
FROM ubuntu:22.04

# g++-12 gdb
RUN apt update && apt install -y g++-12 gdb build-essential 

# GMP
RUN apt install -y libgmp3-dev

WORKDIR /tmp

# ac-library
RUN apt install -y wget &&\
    wget https://github.com/atcoder/ac-library/releases/download/v1.5.1/ac-library.zip -O ac-library.zip &&\
    mkdir /opt/ac-library &&\
    apt-get install -y unzip &&\
    unzip /tmp/ac-library.zip -d /opt/ac-library

# boost
RUN wget https://archives.boost.io/release/1.82.0/source/boost_1_82_0.tar.gz -O boost_1_82_0.tar.gz &&\
    tar xf boost_1_82_0.tar.gz
WORKDIR /tmp/boost_1_82_0
RUN ./bootstrap.sh --with-toolset=gcc --without-libraries=mpi,graph_parallel &&\
    ./b2 ました toolset=gcc variant=release link=static runtime-link=static cxxflags=""-std=c++2b"" stage &&\
    ./b2 -j3 toolset=gcc variant=release link=static runtime-link=static cxxflags=""-std=c++2b"" --prefix=/opt/boost/gcc install

# Eigen
RUN apt install -y libeigen3-dev=3.4.0-2ubuntu2

# git
RUN apt install -y git

# online-judge-tools/verification-helper
RUN apt install -y python3-pip &&\
    #pip3 install online-judge-tools &&\
    pip3 install git+https://github.com/sukenori/oj.git &&\
    pip3 install online-judge-verify-helper

# Docker
RUN apt install -y docker.io

devcontainer.json

そのDockerfileをdevcontainerにしていくわけですが、その際、まずは、環境を置いてあるフォルダをマウントし、さらに、提出コード集を分離したので、それも別にマウントしてやります。

devcontainer.json
{
  "name": "AtCoder-C++",
  "build": {
    "dockerfile": "Dockerfile_AtCoder-C++"
  },
  "workspaceMount": "source=C:/Users/ユーザー名/OneDrive/ローカルの保管場所/AtCoder-C++,target=/workspaces/AtCoder-C++,type=bind",
  "workspaceFolder": "/workspaces/AtCoder-C++",
  "mounts": [
    "source=C:/Users/ユーザー名/OneDrive/ローカルの保管場所/Solved_Code,target=/workspaces/Solved_Code,type=bind",
    "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind"
  ],
  "customizations": {
    "vscode": {
      "extensions": [
        "ms-vscode.cpptools",
        "GitHub.copilot",
        "ms-azuretools.vscode-docker"
      ]
    }
  },
  "postCreateCommand": "./.devcontainer/postCreateCommand.sh",
}

postCreateCommand.sh

文字通り、コンテナクリエイト後に走るスクリプトですので、stdc++.hのプリコンパイルを置いたり、ojのログインなんかをします。

postCreateCommand.sh
#!/bin/bash
cp /workspaces/AtCoder-C++/stdc++.h.gch /usr/include/x86_64-linux-gnu/c++/12/bits
oj login -u ログイン名 -p パスワード https://atcoder.jp/

.code-workspace

さっきマウントしておいたディレクトリで、ワークスペースを構成します。
(提出コード集部分は一例です。)
コンテストが終わったら、.Contesutから中身を移していったりします。

AtCoder-C++.code-workspace
{
	"folders": [
		{
			"name": "AtCoder-C++",
			"path": "/workspaces/AtCoder-C++"
		},
		{
			"name": "AtCoder_Beginner_Contest",
			"path": "/workspaces/Solved_Code/AtCoder/AtCoder_Beginner_Contest"
		},
		{
			"name": "AtCoder_Problems-Recommendation",
			"path": "/workspaces/Solved_Code/AtCoder/AtCoder_Problems-Recommendation"
		}
	]
}

tasks.json

ojの自動化を導入するので、コンパイル、テストのダウンロード、テスト、提出の各taskを用意します。
コンパイルオプションは、これもしつこく言語アップデート時の提案シートに揃えてみたりしています。
コンパイルをデフォルトのタスクにして、提出のタスクにショーカットをつければ、その2つのショートカットでほぼほぼCLI要らずになります。というのも、提出のタスクは、testディレクトリの削除→サンプルケースのダウンロード→テスト→提出のコンボにしてあるからです。URL渡しは、サンプルケースのダウンロード時に、出てくるウィンドウに問題文URLをコピペするだけです(Chrome拡張機能を使うとさらにいい感じです)。
加えて自作ライブラリをoj-bundleで展開して提出できるようにしておきます(.vscodeに後述のbundle.shを入れておく)。

tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "cpp build",
            "group": {
                "kind": "build",
                "isDefault":true
            },
            "type": "shell",
            "command": "/usr/bin/g++-12",
            "args": [
                "-std=gnu++2b",
                "-O2",
                "-DONLINE_JUDGE",
                "-DATCODER",
                "-Wall",
                "-Wextra",
                "-mtune=native",
                "-march=native",
                "-fconstexpr-depth=2147483647",
                "-fconstexpr-loop-limit=2147483647",
                "-fconstexpr-ops-limit=2147483647",
                "-I/opt/ac-library",
                "-I/opt/boost/gcc/include",
                "-L/opt/boost/gcc/lib",
                "-oa.out",
                "${file}",
                "-lgmpxx",
                "-lgmp",
                "-I/usr/include/eigen3",
                "-I${workspaceFolder}/.Library",
                "-ggdb3"
            ],
            "presentation": {
                "panel": "shared"
            }
        },
        {
            "label": "remove test",
            "type": "shell",
            "command": "rm",
            "args": [
                "-rf",
                "test"
            ],
            "presentation": {
                "panel": "shared"
            }
        },
        {
            "label": "download test",
            "type": "shell",
            "command": "oj",
            "args": [
                "d",
                "${input:url}",
                "-d",
                "test",
                "-s"
            ],
            "presentation": {
                "panel": "shared",
                "showReuseMessage": false
            },
            "dependsOn": ["remove test"],
            "dependsOrder": "sequence"
        },
        {
            "label": "test",
            "type": "shell",
            "command": "oj",
            "args": [
                "t",
                "-c",
                "${workspaceFolder}/a.out",
                "-d",
                "${workspaceFolder}/test/"
            ],
            "presentation": {
                "panel": "shared"
            }
        },        {
            "label": "cpp bundle",
            "type": "shell",
            "command": "${workspaceFolder}/.vscode/bundle.sh",
            "presentation": {
                "panel": "shared"
            },
            "options": {
                "env": {
                    "workspaceFolder": "${workspaceFolder}",
                    "file": "${file}"
                }
            }
        },
        {
            "label": "cpp submit",
            "type": "shell",
            "command": "oj",
            "args": [
                "s",
                "${input:url}",
                "bundled.cpp",
                "-w",
                "0",
                "-y"
            ],
            "presentation": {
                "panel": "shared"
            },
            "dependsOn": ["cpp build","download test","test","cpp bundle"],
            "dependsOrder": "sequence"
        },
    ],
    "inputs": [
        {
            "type":"promptString",
            "id":"url",
            "description": "URL of the task"
        }
    ]
}
bundle.sh
#!/bin/bash
oj-bundle -I ${workspaceFolder}/.Library "${file}"\
|grep -v '^#line '\
|cat -s|sed -e '1{/^$/d}'\
> ${workspaceFolder}/bundled.cpp

これで、提出前にimportした自作ライブラリを展開してbundled.cppとし、それを提出してくれます。

まぁ、これだけやったところで、精進しなけりゃレートはちっとも上がらないんですけどね…

Discussion