DockerでRustのAtCoder環境をいい感じに作る


# 始めに
マシンによる環境の違いで環境構築ができないという問題を解決するため、RustでAtCoderに取り組むための環境をDocker上で構築しました。
できる限り便利にしたいと思い、いろいろツールを入れています。

なお、この記事は2023に言語アップデートに伴い複数回書き直しています。


# できる環境
クリックまたは簡単なコマンドを打つことでビルド、テスト、提出を行う。
ファイルからの入力でデバッグを行う。

こちらにコードを載せています
https://github.com/uriuriboo/atcoder_rust
一人ではなかなか厳しいところがあるので機能の追加や問題点、言語アップデートなどので改善案や報告がありましたらissueやPRなどをください！

## 注意点

改行コードの違いでシェルスクリプトが動かないことがあったので注意してください。


## 環境、ツール

windows10、11
Docker
WSL2
vscode
[cargo-compete](https://github.com/qryxip/cargo-compete)
[cargo-member](https://github.com/qryxip/cargo-member)
python3

### vscodeの拡張機能




**ローカルで扱うもの**
Docker
Dev Containers

**仮想環境で扱うもの(だいたい環境構築時に入ってます)** 
rust-analyzer
CodeLLDB（デバッガー)
rust-anayzer
Github copilot
taskrunner


## 手順1 Dockerのインストール
windowsならまずWSL2をインストールします。
window10ならpowershellで
```powershell:
wsl --install
```
と入力してください。

その後Docker公式サイトからインストーラをダウンロードして実行してください。
macならdockerをすぐにインストールできたと思います。

参考となりそうなもの
https://learn.microsoft.com/ja-jp/windows/wsl/install
https://www.youtube.com/watch?v=lZD1MIHwMBY

## 手順2 コンテナの立ち上げ
vscodeを立ち上げて拡張機能のDockerをインストールしてください。次に左下にある緑のボタンを押してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2302637/6763c5f1-67e8-edc4-7a59-f8f4c10a66b9.png)

すると上部にOpen Folder in Cointanerと出てきますのでクリックし、ダウンロードしたリポジトリを開いてください。私はフォルダ名をatcoder_rust(githubのリポジトリ名と同じ)に設定しました。後にテストをする際のパスの指定に使用しますので気をつけてください。そうすれば簡単なRust環境の完成です。
ここで必要なツールが一部インストールされます。
途中で数字の入力が求められるため、こだわりがなければ 2 を入力してください
もしエラーがでるようならシェルスクリプトで改行の形式が原因かもしれません。

.devcontainer/postCreateCommand.bashでエラーが出たら(i)、(ii)の方法を試してください

## (i)(必要なら)改行コードをLFにする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2302637/5147bab5-c3fc-a964-8c70-946eb7e6be9f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2302637/a271237e-3e3a-b7b3-d1df-efd0eb0bc58b.png)



## (ii)(必要なら)以下のコマンドを打つ
打たなくてもよいようにしました
改行コードでうまくいかないときは実行してください

```bash:
$ sudo chmod a+x .devcontainer/postCreateCommand.bash
```

最後に.devcontainer/postCreateCommand.bashをコマンドラインから実行してください。


実行方法
```bash:
$ ./.devcontainer/postCreateCommand.bash
```

立ち上げ後はAtCoderの環境にするか、ログインするか聞かれるのでコマンドラインのメッセージを読んで適宜操作してください。


## 手順3 vscodeの設定


### 拡張機能のインストール
個人で好きなものをインストールしてください。
rust-analyzerなど基本必要なものはすでに入れているつもりです。






### スニペット
スニペットの機能を使うことで簡単にコードを呼び出すことができます。スニペットは破片という意味を持ちます。

詳しくはこちらをご覧ください。
https://qiita.com/12345/items/97ba616d530b4f692c97
ファイル→ユーザ設定→スニペットを選択し、コマンド欄にrustと打ち込みrust.jsonを開いて、よく使うコードを書きましょう。
私はテンプレートとして以下のスニペットを登録しています。
```json:rust.json
{
    "template": {
        "prefix": "tmplt",
        "body": [
            "#[allow(unused_imports)]",
            "use proconio::{marker::*, *};",
            "#[allow(unused_imports)]",
            "use std::{cmp::*, collections::*, *};\n",
            "#[fastout]",
            "fn main(){",
            "\tlet mut ans:usize = 0;",
            "\tinput!{",
            "\t\tn:usize,",
            "\t\ta:[usize;n]",
            "\t}\n",
            "\tprintln!(\"{}\",ans);",
            "}"
        ],
        "description": "テンプレート"
    },
}
```





# 問題を解くときの使用方法

### ビルドとテスト


ビルドとテストを行うスクリプトは以下のように書いています
arcやagcに出るときはcontest変数の値を変更してください

```bash:bt.bash
#!/bin/bash
contest="abc"
num=$1
aplha=$2
cd /workspaces/atcoder_rust/${contest}${num}/src/bin/
cargo compete s ${aplha}
```

使い方

```shell-session:
$ ./bt.bash コンテストの番号 問題のアルファベット
```

使用方法の具体例 
ABCコンテスト277 A問題の場合(contestの変数は変更していません)
```shell-session:
$ ./bt.bash 277 a
```

**クリックで行う場合**


abc,arc,agcのみに対応しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2302637/42790cc6-35ad-d2cf-0144-3e052985b7a7.png)
図のように **提出したいファイルを開いた状態** で左下のTASKRUNNER->Workspace->build & test をクリックすると新たにターミナルが開きテスト結果が表示されます。
`release`コマンドをつけてコンパイルするようにもできます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2302637/10f02c3e-6aff-f0d8-1bdb-2ffd1acd53e7.png)



### 問題の提出
提出するためのスクリプトは以下のように書いています

```bash:sub.bash
#!/bin/bash
contest="abc"
num=$1
aplha=$2
cd /workspaces/atcder_rust/${contest}${num}/src/bin/
cargo compete s ${aplha}
```

使い方
```shell-session:
$ ./sub.bash コンテストの番号 問題のアルファベット
```

具体例
ABCコンテスト208 D問題の場合(contestの変数は変更していません)
```shell-session:
$ ./sub.bash 208 d
```

試験的にTASKRUNNER->submitから提出できる機能を追加しました。
エラーがありましたらお知らせください。



# 環境設定の説明

## postCreateCommand.bash

コンテナを作成したときに実行するコマンドです

1. 手で実行するスクリプト用に権限を与えています。ビルドとテストを行うスクリプトと提出用スクリプトに権限を与えています。
1. rust-toolchainが存在しているときcargo-competeを実行するとうまく実行できないことがあったため削除しています。はっきりはしりませんがエディションが2018にしか対応していないかあたりの問題だったと思います。
1. rust-analyzerを使用するためcargo-memberが書き込むことができるようにしています。
3. cargo-competeでAtCoder向けの設定をしています。このときac-libraryをクローンしています
3. フォーマッターとリンターをインストールしています
3. ログイン機能を使ったログインをしています。
3. 最後にatcoderのRustバージョンに合わせるのとテストで使用するツールチェインをインストールしています。




```bash:.devcontainer/postCreateCommand.bash
#!/bin/bash

#!/bin/bash

echo give permission to bashfiles
chmod a+x bt.bash sub.bash
chmod a+x scripts/change_launch.bash


# 先にrust-toolchainをインストールするとcargo-competeがコンパイルできなかった2024/4/19
if [ -f "rust-toolchain" ]; then
    rm rust-toolchain
fi

# cargo-memberで書き込むため
echo make Cargo.toml
touch Cargo.toml
sudo chmod a+w Cargo.toml

# install cargo tools
echo install cargo tools
cargo install cargo-compete
cargo install cargo-member
cargo compete i atcoder
echo clone ac-library-rs
git clone https://github.com/rust-lang-ja/ac-library-rs.git

rustup component add rustfmt
rustup component add clippy

echo login to atcoder
cargo compete l atcoder

# install rustup tools
echo install rust-toolchain
echo "1.70" > rust-toolchain
cargo -V

# install online-judge-tools for generater
# pip3 install online-judge-template-generator


# rustup install 1.70
# rustup install 1.70.0-x86_64-unknown-linux-gnu

# The installation of fish is your choice.
# echo install fish and fisher
# sudo apt-add-repository ppa:fish-shell/release-3
# sudo apt-get update
# sudo apt-get install fish
# curl -sL https://git.io/fisher | source && fisher install jorgebucaran/fisher
```
## launch.json
.vscode/launch.jsonに以下のコードを記述します。
テスト時に基本自動で生成されるようにしています。

```json:launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "lldb",
            "request": "launch",
            "name": "Debug",
            "sourceLanguages": [
                "rust"
            ],
            //"preLaunchTask": "cargo build",
            "internalConsoleOptions": "openOnSessionStart",
            "program": "${workspaceRoot}/target/debug/abc252-${fileBasenameNoExtension}",
            "args": [],
            "stdio": [
               "${workspaceFolder}/input.txt"
            ],
        }
    ]
}
```
ホームディレクトリ直下にinput.txtが入力ファイルです。
そしてデバッグしたいファイルを開きF10キーを押せば開始できます。


コンテストごとにprogramの項目にあるabc-252の部分を現在参加しているコンテストに書き換えてください。ここは今回作った環境の要改善です。
例:
abcコンテスト108回 → abc-108
arcコンテスト10回 → arc-10


## tasks.json

.vscode/tasks.jsonとシェルスクリプトとTASKRUNNERを使ってクリックからビルドとテスト、提出を行えるようにしています。scriptフォルダに使用されるファイルは置かれています。

Rustのファイルを開いた状態で使うことを想定しており、pythonにそのファイルの絶対パスを渡してそのpythonからシェルスクリプトを呼び出しています。

script/change_launch.bashでテストを実行するごとに書き換えられています。

参考
https://magicode.io/shunnya0715/articles/9734ba4922894b329a756b6f3592e8ed

```json:tasks.json
{
	"version": "2.0.0",
	"tasks": [
		// {
			
		// 	"type": "cargo",
		// 	"command": "build",
		// 	"problemMatcher": [
		// 		"$rustc"
		// 	],
		// 	"group": "build",
		// 	"label": "rust: cargo build"
		// },
		// {
		// 	"type": "shell",
		// 	"command":[ "cd ${workspaceFolder}/abc253/src/bin/ & pwd",
		// 		//"cargo build & cargo compete t ${fileBasenameNoExtension}",
				
		// 	],
			
		// 	"group": "build",
		// 	"label": "rust: cd cargo build"
		// },
		// {
		// 	"type": "shell",
		// 	"command": [
		// 		"pwd"
		// 	],
		// 	"group": "test",
		// 	"label": "pwd"
		// },
		{
			"label": "build & test",
			"type": "shell",
			"command": "python3",
			"args": [
				"${workspaceFolder}/scripts/test.py",
				"${file}"
			],
			"group": {
				"kind": "build",
				"isDefault": true
			},
			"problemMatcher": [],
		},
		{
			"label": "build & test release",
			"type": "shell",
			"command": "python3",
			"args": [
				"${workspaceFolder}/scripts/test_release.py",
				"${file}"
			],
			"group": {
				"kind": "build",
				"isDefault": true
			},
			"problemMatcher": [],
		},
		{
			"label": "submit",
			"type": "shell",
			"command": "python3",
			"args": [
				"${workspaceFolder}/scripts/sub.py",
				"${file}"
			],
			"group": {
				"kind": "build",
				"isDefault": true
			},
			"problemMatcher": [],
		},
		{
			"label": "rust: cargo compete new",
			"type": "cargo",
			"command": "compete",
			"args": [
				"new",
				"${input:contest}"
			]
		},
		{
			"label": "rust: cargo member include",
			"type": "cargo",
			"command": "member",
			"args": [
				"include",
				"${input:contest}"
			]
		},
		{
			"label": "new contest",
			"dependsOrder": "sequence",
			"dependsOn": [
				"rust: cargo compete new",
				"rust: cargo member include",
			],
			"presentation": {
				"echo": false,
				"reveal": "silent",
				"focus": false,
				"panel": "shared",
				"showReuseMessage": true,
				"clear": true
			},
			"problemMatcher": []
		}
	],
	"inputs": [
		{
			"id": "contest",
			"description": "contestID",
			"type": "promptString"
		}
	]
}
```
## settings.json

```json:settings.json
{
    "[rust]": {
        "editor.formatOnSave": true,
        "editor.formatOnPaste": true,
        "editor.formatOnType": true,
        "editor.defaultFormatter": "rust-lang.rust-analyzer",
    },
    "rust-analyzer.check.command": "clippy"
}

```
Rust言語使用時に保存、ペースト、タイプ時にRustfmtを使用してフォーマットを行うようにしてます。
なぜか保存時にしかフォーマットされないみたいでした。解決方法を模索中です。
また、clippyというリンターを使用してコードを静的解析しています。

## scripts/test.py

ファイルの絶対パスからabc、arc、agcなどのコンテストの種類、コンテストの回数、問題のアルファベットを取得してcargo competeで実行しています。
テストした.rsファイル用にlauch.jsonを書き換える処理も行っています。

```python:test.py
import os,sys
import subprocess

def get_contest_id(path) -> tuple:
    parsed_path = path.split("/")
    contest_name = parsed_path[3][:3]
    contest_id = parsed_path[3][3:]
    problem_id = parsed_path[6][0]
    return contest_name,contest_id,problem_id

contest_name,contest_id,problem_id = get_contest_id(sys.argv[1])
path = '/workspaces/atcoder_rust/{}{}/src/bin'.format(contest_name,contest_id)
os.chdir(path)
cp = subprocess.run(["/workspaces/atcoder_rust/scripts/change_launch.bash",contest_name,contest_id,problem_id])
cp = subprocess.run(["cargo" ,"build"])
cp = subprocess.run(["cargo" ,"compete" ,"t" ,problem_id])
```

## scripts/test_release.py

test.pyにreleaseコマンドをつけてコンパイルしています

```python:test_release.py
import os,sys
import subprocess

def get_contest_id(path) -> tuple:
    parsed_path = path.split("/")
    contest_name = parsed_path[3][:3]
    contest_id = parsed_path[3][3:]
    problem_id = parsed_path[6][0]
    return contest_name,contest_id,problem_id


contest_name,contest_id,problem_id = get_contest_id(sys.argv[1])
path = '/workspaces/atcoder_rust/{}{}/src/bin'.format(contest_name,contest_id)
os.chdir(path)
cp = subprocess.run(["/workspaces/atcoder_rust/scripts/change_launch.bash",contest_name,contest_id,problem_id])
cp = subprocess.run(["cargo" ,"build"])
cp = subprocess.run(["cargo" ,"compete" ,"t" ,problem_id ,"--release"])
```

## sub.py
cargo-competeを使ってvscodeで開いているrustファイルを提出します。
```python:sub.py
import os,sys
import subprocess

def get_contest_id(path) -> tuple:
    parsed_path = path.split("/")
    contest_name = parsed_path[3][:3]
    contest_id = parsed_path[3][3:]
    problem_id = parsed_path[6][0]
    return contest_name,contest_id,problem_id

contest_name,contest_id,problem_id = get_contest_id(sys.argv[1])
path = '/workspaces/atcoder_rust/{}{}/src/bin'.format(contest_name,contest_id)
os.chdir(path)
cp = subprocess.run(["cargo" ,"compete" ,"s" ,problem_id])
```

## change_launch.bash

test.pyから呼び出しテストする問題にむけてlaunch.jsonを書き換えています

```bash:change_launch.bash
#!/bin/bash

contest_name=$1
contest_id=$2
problem_id=$3
workspaceFolder=/workspaces/atcoder_rust
workspaceRoot=/workspaces/atcoder_rust
echo change launch.json for debag ${contest_name}${contest_id}-${problem_id}

# pwd

echo -e \
"{\n\
    \"version\": \"0.2.0\",\n\
    \"configurations\": [\n\
        {\n\
            \"type\": \"lldb\",\n\
            \"request\": \"launch\",\n\
            \"name\": \"Debug\",\n\
            \"sourceLanguages\": [\n\
                \"rust\"\n\
            ],\n\
            //\"preLaunchTask\": \"cargo build\",\n\
            \"internalConsoleOptions\": \"openOnSessionStart\",\n\
            \"program\": \"/workspaces/atcoder_rust/target/debug/${contest_name}${contest_id}-${problem_id}\",\n\
            \"args\": [],\n\
            \"stdio\": [\n\
                \"/workspaces/atcoder_rust/input.txt\"\n\
            ],\n\
        }\n\
    ]\n\
}"\
> $workspaceRoot/.vscode/launch.json
```

## bt.bash

リポジトリ直下のtest.pyと実行内容は似ています。コマンドラインからがabcか、などを引数として受け取るようにしています。

```bash:bt.bash
#!/bin/bash
contest="abc"
num=$1
aplha=$2
cd /workspaces/atcoder_rust/${contest}${num}/src/bin/
cargo build & cargo compete t ${aplha}
```

# 注意点まとめ
1. コマンドラインからスクリプトを使うときは
launch.json
bt.bash
sub.bash
の三つのファイルをコンテスト、コンテストの番号つまりabc、arc、agcやその回数ごとに書き換える必要があること
2. postCreateCommand.bashは改行コード、権限の関係で実行できないことがあるため、適宜直すこと
2. フォルダ名をatcoder_rustから書き換えないこと。書き換える場合はscript以下のパスを書き変える。

## 補足 

### rust-toolchainのインストール
rust-toolchainのインストールがうまくいかないときに読んでください

コマンドは


```bash:
$ touch rust-toolchain
$ echo "1.70" > rust-toolchain
$ cargo -V
$ rustup install 1.70
$ rustup install 1.70.0-x86_64-unknown-linux-gnu
```

競技プロ補助ツールのcargo-competeがテストなどを行うために使用するrust-toolchainをインストールします。
昔rust-toolchainで動かなかったときいろいろ試した結果このようにインストールすると解決しました。(私自身このあたりはあまりわかっておらず勉強中です)

ここではもしかしたらrustup install 1.70はいらないかもしれないです。

追記
こちらに質問が投稿されていました。
https://stackoverflow.com/questions/49368232/what-is-a-default-host-triple-in-rust

公式に書いてありました。
https://github.com/rust-lang-ja/atcoder-rust-resources/wiki/2020-Update
https://doc.rust-lang.org/stable/rustc/platform-support.html

rust-analyerが動かないときはvscodeを再起動したら動きました。

### rust-anlyzer

二つ以上のプロジェクトを作ると動かなくなるようです
https://zenn.dev/fah_72946_engr/articles/cf53487d3cc5fc
解決方法としてcargo-memberを使っています


# あったら便利そうなツール

### github copilot
自動補完ツールその1
これはお金を払わないと使えないため課金したくない人は以下二つを推奨

### Tabnine
機械学習を用いた自動補完ツールその2
重いですがかなり便利です
proにすると軽くなるらしい

### Kite
機械学習を用いた自動補完ツールその3
enginをインストールしてそちらのプログラムからvscode-pluginをインストールすると使えるようになるらしい


# 今後の改善点、追加したい機能
1. 環境構築を完全に自動化する
1. グラフの可視化
1. サンプルの自動生成
1. cargo equipなどで外部のライブラリを使用しやすくする
1. シェルスクリプトでエラーが起こったらユーザーに問題を通知




# 最後に

- 今後はcodeforcesやyukicoderに対応した環境も作るかもしれません。

- Dockerに強い方やRustに詳しい方などは改善点やさらなる自動化できる点がありましたらプルリクをいただけると嬉しいです。問題点がありましたらコメントやgithubのissueにお願いします。有志で集まって便利な環境構築ができたらいいですね。

# 参考・関連記事
https://qiita.com/qryxip/items/bff57848ac9310d27f1a
https://magicode.io/shunnya0715/articles/9734ba4922894b329a756b6f3592e8ed
https://qiita.com/qryxip/items/b945c0adbd62ce5f1f3d
https://qiita.com/okaponta_/items/7e82de5d1f78f547fe4b
https://zenn.dev/23prime/articles/74cda5a096a3b3