WSL2 + Docker + VSCode で C++ と Python の実行環境を作る

競プロで C++ と Python を使っていて，Windows での実行環境が欲しかったので作ってみました．
開発用ではないので必要最低限の設定になっています．

2020/1/11: VSCode 上で C++ 17 の文法を使えるようにする「C++ Standard の設定」を追加しました

# 対象

- C++ や Python の簡単な実行環境を作りたい方
- Windows ユーザ
    - Docker 導入済み
- VSCode を使っている方

# 使用環境 / ツール

- GitHub
- Windows 10 + WSL2
- Docker Desktop for Windows (v3.0.0)
- VSCode (v1.52.1)

この記事で作れる実行環境のサンプルリポジトリ : https://github.com/e5pe0n/algo-training-sample

# GitHub にリポジトリを作る

コードを管理しやすいように GitHub にリポジトリを作ります．
適当な Repository Name を入力し，`Add a README file` にチェックを入れて Create Repository をクリックします．
.gitignore はあとから作るのでここではチェックしません．

![Create a New Repository - Google Chrome 1_3_2021 11_50_21 AM (2)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/823c4622-98b9-4ab6-f623-7b40c96db565.jpeg)

# Dev Container の作成

いま作ったリポジトリを，実行環境となる Docker コンテナにクローンします．

## VSCode に Remote Development を入れる

まずは VSCode からコンテナに接続できるように， VSCode を開いて拡張機能 Remote Development (ms-vscode-remote.vscode-remote-extensionpack) をインストールします．

![Extension_ Remote Development - Visual Studio Code 1_3_2021 12_01_58 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/404ddfd1-5dd9-47ba-528a-13c1aecdd7a2.png)

## VSCode と GitHub を紐づける

インストールが完了すると，エディタの一番左下に Remote Development の機能が使える緑色のボタンが表示されるので，それをクリックします．
出てきたメニューの中から `Remote-Containers: Clone Repository in Container Volume` をクリックします．

![Extension_ Remote Development - Visual Studio Code 1_3_2021 12_03_34 PM_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/20a61c5b-78f1-b65c-12b4-0aa370129eff.jpeg)

リポジトリの URL を入力してエンターを押します．

![Extension_ Remote Development - Visual Studio Code 1_3_2021 12_03_57 PM (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/b26d76e5-a395-14b9-876f-4cc6a80f3f0d.png)

初回では，VSCode で GitHub アカウントにサインインするかのダイアログが表示されたり，ブラウザに飛んで VSCode が GitHub にアクセスすることを許可するか聞かれたりするので，それぞれ Yes や Continue をクリックします．

![Extension_ Remote Development - Visual Studio Code 1_3_2021 12_04_05 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/1fd6e391-ee3d-a929-8441-228f66ba5fae.png)
![GitHub for VS Code - Google Chrome 1_3_2021 12_04_28 PM (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/753943b7-667b-c996-f162-6809a286449c.png)
![Visual Studio Code 1_3_2021 12_05_25 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/13d325b4-ef70-9f3f-d5f0-5168c8c2be4c.png)

## クローンするブランチ

VSCode と GitHub が紐づけられると，クローンするブランチを聞かれるので `main` を選択します．
![Visual Studio Code 1_3_2021 12_06_16 PM (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/78856157-376a-d134-2459-2909c74db7f2.png)

## ボリュームの種類

ほかのリポジトリと併用しないので今回は `Create a unique volume` を選択します．
![Welcome - Visual Studio Code 1_3_2021 12_07_36 PM (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/a4325452-17f3-13ba-a30f-0c81ddf517dd.png)

## コンテナの種類

コンテナの種類は `Ubuntu` にしましょう． 
続いてバージョンを聞かれますが `focal` にします．

![Welcome - Visual Studio Code 1_3_2021 12_08_02 PM (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/06683c9e-81a3-a5de-dcc8-0ee01463a076.png)

これでひとまずリポジトリに紐づいたコンテナを作ることができました．
ここからは C++ や Python が実行できるようにコンテナの設定を整えていきます．

# コンテナの設定

初めてコンテナが作られたときは以下のようなディレクトリ構成になっていると思います．
ここから設定ファイルを編集したり，新しく設定ファイルを追加したりしていきます．

```
/workspaces/<repo-name>
├── .devcontainer
│   ├── devcontainer.json
│   └── Dockerfile
├── .git
└── README.md
```

最終的なディレクトリ構成はこんな感じです．

```
/workspaces/<repo-name>
├── .clang-format
├── .devcontainer
│   ├── devcontainer.json
│   └── Dockerfile
├── .git
├── .gitignore
├── README.md
└── requirements.txt
```

## .gitignore の作成

リポジトリのディレクトリ直下で `touch .gitignore` を実行して空のファイルを作っておきます．
[gitignore.io](https://www.toptal.com/developers/gitignore) で `vscode`，`C++`，`Python` を入力して Create をクリックします．
表示された内容を全部先ほど作成した .gitignore にコピーして完成です．
必要があれば編集してください．

## Dockerfile の編集

Dockerfile の `# [Optional] Uncomment this ...` 以下の部分に，コンテナが作られるときに実行されるコマンドを追加していきます．
`apt-get` でインストールする build-essential は C++ のコンパイラ g++ が入っていて，clang-format は C++ ファイルのフォーマット用です．
また，デフォルトでは Python 3.8.2 が入っていますが，Python のパッケージインストーラ pip が入っていないので python3-pip をインストールします．

```Dockerfile
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.154.0/containers/ubuntu/.devcontainer/base.Dockerfile

# [Choice] Ubuntu version: bionic, focal
ARG VARIANT="focal"
FROM mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT}

# [Optional] Uncomment this section to install additional OS packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
    && apt-get -y install --no-install-recommends \
    build-essential \
    clang-format \
    python3-pip
```

## .clang-format の作成

C++ ファイルのフォーマットの設定ファイルとして .clang-format を用意します．
VSCode のオートフォーマットを on にして C++ のフォーマッターを Clang-Format に設定することで，.clang-format の設定通り自動的にコードをフォーマットできます．
設定できる項目はめっちゃいっぱいある（ https://clang.llvm.org/docs/ClangFormatStyleOptions.html ）のでお好みで．
自分は正直よくわかってないのでとりあえず気になったものだけ設定しています．

```.clang-format
ColumnLimit: 110
AllowShortBlocksOnASingleLine: true
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: true
BinPackArguments: false
BinPackParameters: false
BreakBeforeBinaryOperators: NonAssignment
ConstructorInitializerAllOnOneLineOrOnePerLine: true
IndentWidth: 2
```

## requirements.txt の作成

requirements.txt は Python のパッケージ管理に使うファイルです．
ここにパッケージを列挙しておき，`pip3 install -r requirements.txt` を実行することで必要なパッケージを 1 コマンドでインストールすることができます．
導入するのは次のパッケージです．

|パッケージ|説明|
|:---:|:---:|
|numpy|行列演算・数値計算とか用|
|flake8|リンター|
|autopep8|フォーマッター|

これらを `pip3 install` でインストールしたあと，インストールしたパッケージの一覧を `pip3 freeze` で requirements.txt に書き出します．

```
$ pwd
/workspaces/<repo-name>
$ pip3 install numpy flake8 autopep8
$ pip3 freeze > requirements.txt
```

依存しているパッケージも合わせると requirements.txt は次のようになっていると思います．

```requirements.txt
autopep8==1.5.4
flake8==3.8.4
mccabe==0.6.1
numpy==1.19.4
pycodestyle==2.6.0
pyflakes==2.2.0
toml==0.10.2
```

## devcontainer.json の編集

コンテナの設定ファイルです．
この中に VSCode の設定やコンテナが作られたあとのコマンドなどを書いておくことで，コンテナを作成したとき設定が自動的に反映されます．

|オプション|説明|
|:---:|:---:
|settings|コンテナ独自の VSCode の設定|
|extensions|コンテナで使う VSCode の拡張機能|
|postCreatedCommand|コンテナが作られたあとに実行したいコマンド|


### settings

VSCode の設定を書く部分です．

|オプション|説明|
|:---:|:---:|
|editor.formatOnSave|`true` でファイル保存時に自動フォーマット|
|python.languageServer|Python IntelliCode のサーバ|
|python.pythonPath|使用する Python インタプリタのパス|
|python.linting.flake8Args|flake8 の引数|
|python.formatting.provider|Python のフォーマッタを選択|
|[cpp]->editor.tabSize|C++ ファイルでのインデントの文字数|
|[cpp]->editor.defaultFormatter|C++ のフォーマッタを選択|


### extensions

インストールしたい VSCode の拡張機能を列挙するところです．
自分はとりあえず以下のものを書いています．

|拡張機能|ID|説明|
|:---:|:---:|:---:|
|Visual Studio IntelliCode | visualstudioexptteam.vscodeintellicode |AI アシスタントがコード補完を提示してくれる|
|C/C++|ms-vscode.cpptools|C++ 用|
|Clang-Format|xaver.clang-format|C++ ファイル用フォーマッタ|
|Git Extension Pack|donjayamanne.git-extension-pack|Git 用|
|Python|ms-python.python|Python 用|
|Pylance|ms-python.vscode-pylance|Python 用|
|Bracket Pair Colorizer 2|coenraads.bracket-pair-colorizer-2|対応する括弧をカラーリングしてくれる|
|Trailing Spaces|shardulm94.trailing-spaces|余分なスペースをハイライト・除去|
|Vim|vscodevim.vim|VSCode 用 Vim エミュレータ|

### postCreateCommand

コンテナ作成後に実行されるコマンドを書くところです．
`pip3 install -r requirements.txt` をここに書いておくことで，先ほど書いた requirements.txt のパッケージを自動的にインストールしてくれます．

### remoteUser

root でコンテナに接続したいときは以下のようにコメントアウトします．
パーミッションまわりがいろいろ面倒なので自分は基本 root で使っています．

```
// "remoteUser": "vscode"
```

これらを設定すると devcontainer.json はこんな感じになります．

```jsonc
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.154.0/containers/ubuntu
{
	"name": "Ubuntu",
	"build": {
		"dockerfile": "Dockerfile",
		// Update 'VARIANT' to pick an Ubuntu version: focal, bionic
		"args": {
			"VARIANT": "focal"
		}
	},
	// Set *default* container specific settings.json values on container create.
	"settings": {
		"terminal.integrated.shell.linux": "/bin/bash",
		"editor.formatOnSave": true,
		"python.languageServer": "Pylance",
		"python.pythonPath": "/usr/bin/python3",
		"python.linting.flake8Args": [
			"--max-line-length", // 1 行あたりの文字数を 110 に設定
			"110"
		],
		"python.formatting.provider": "autopep8",
		"python.formatting.autopep8Args": [
			"--max-line-length", // 1 行あたりの文字数を 110 に設定
			"110"
		],
		"[cpp]": {
			"editor.tabSize": 2,
			"editor.defaultFormatter": "xaver.clang-format" // 拡張機能 Clang-Format を選択
		},
	},
	// Add the IDs of extensions you want installed when the container is created.
	"extensions": [
		"visualstudioexptteam.vscodeintellicode",
		"ms-vscode.cpptools",
		"xaver.clang-format",
		"donjayamanne.git-extension-pack",
		"ms-python.python",
		"ms-python.vscode-pylance",
		"coenraads.bracket-pair-colorizer-2",
		"shardulm94.trailing-spaces",
		"vscodevim.vim"
	],
	// Use 'forwardPorts' to make a list of ports inside the container available locally.
	// "forwardPorts": [],

	// Use 'postCreateCommand' to run commands after the container is created.
	"postCreateCommand": "pip3 install -r requirements.txt",

	// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
	// "remoteUser": "vscode"
}
```

以上でコンテナの設定は完了です．


# コンテナのリビルド

仕上げとして，設定に基づいてコンテナをリビルドします．
左下の緑色の `Dev Container: Ubuntu` をクリックし，`Remote-Containers: Rebuild Container` を選択します．

![devcontainer.json - algo-training-sample [Dev Container_ Ubuntu] - Visual Studio Code 1_3_2021 5_20_06 PM_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/9cf99cc5-96d5-1e2e-0d21-758830db9f60.jpeg)

これで C++ と Python の実行環境ができました．
リビルドが完了したあと，例えば次の hello.cpp，hello.py をコンテナ内で実行できます．

```hello.cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
  cout << "I'm C++!" << endl;
}
```

```
# g++ -o hello hello.cpp
# ./hello 
I'm C++!
```

```hello.py
print("I'm Python!!")
```

```
# python3 hello.py
I'm Python!!
```

あとはこれをリモートのリポジトリにプッシュしておけば，同じ環境をすぐに作ることができます．

## C++ Standard の設定

入れたままだと拡張機能 C/C++ が C++ 17 の文法を書くとエラーメッセージが表示されてしまうので，C++ Standard の設定をします．
`Ctrl` + `Shift` + `P` でコマンドパレットを開き，`C/C++: Edit Configurations (JSON)` を選択します．

![hello.cpp - algo-training-sample [Dev Container_ Ubuntu] - Visual Studio Code 1_10_2021 11_45_26 PM_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/b83af807-0ac9-8107-98f8-87ddc6d7d915.jpeg)

すると，`/workspaces` 直下にディレクトリ `.vscode` が作られ，その中に `c_cpp_properties.json` が作られます．
この中の `cppStandard` を `"gnu++17"` に設定します．

```c_cpp_properties.json
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "gnu17",
            "cppStandard": "gnu++17",
            "intelliSenseMode": "gcc-x64"
        }
    ],
    "version": 4
}
```

例えば構造化束縛を使った以下のコードでもエラーメッセージが表示されなくなります．

![hello.cpp - algo-training-sample [Dev Container_ Ubuntu] - Visual Studio Code 1_11_2021 12_01_38 AM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319282/774f0503-208c-566c-5e94-0d6085f58228.png)



# おまけ

自分は新しくディレクトリを作るとき C++ と Python のディレクトリを分けたいので，テンプレートとして次のディレクトリをリポジトリに入れています．

```
template
├── cpp
│   ├── build         // C++ の実行ファイル置き場
│   │   └── .gitkeep
│   ├── .gitkeep
│   └── run.sh        // C++ ファイル実行用スクリプト
└── python
    └── .gitkeep
```

```run.sh
f=`echo $1 | sed -e 's/\(.*\).cpp/\1/'`
current_dir=$(eval pwd)
g++ -std=c++17 -g -o ${current_dir}/build/${f}.out $1
eval ${current_dir}/build/${f}.out
```

run.sh は C++ ファイルをコンパイル + 実行するスクリプトで， `sh run.sh A.cpp` みたいに使います．
競プロをやっていく上でもっといい運用方法があればぜひ教えてほしいです！