Visual Studio CodeのRemote ContainersでC++開発環境構築

C++の開発をすることになったので、とりあえず開発環境をどうにかしようと思い、安定板にも入ったということで、今流行りのVisual Studio Codeの`Remote - Containers`を使って環境構築しました。

# はじめに

**注意** Docker上でgdbを使うためにセキュリティレベルを下げる設定を追加しています。外部に公開される環境では用いないようにしてください。


# 動作環境

macOS Mojave  
version 10.14.5

```
$ docker --version
Docker version 18.09.2, build 6247962
$ code --version
1.35.1
c7d83e57cd18f18026a8162d042843bda1bcf21f
x64
```

# 実行方法

[gitリポジトリ](https://github.com/dbgso/vscode-cpp-devcontainer.git)にプロジェクトのテンプレートを上げているので、そこから取得します。

簡単な設定概要については [設定の概要](#設定の概要)をご参照ください。

```console
git clone https://github.com/dbgso/vscode-cpp-devcontainer.git
code vscode-cpp-devcontainer
```

以下のダイアログがでるので、`Reopen in Container`を選択します。


![alt](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/9c0930f0-915e-eda2-8980-05b1014a596d.png)

あるいは、コマンドパレットから`Remote-Containers: Reopen Folder in Container`を選択しても同様です。

docker-compose.ymlとDockerfileに定義したビルドが走るので、しばらく待機します。

ビルドが終わればこんな感じで拡張機能ありの状態でコンテナ内の環境が立ち上がります。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/eca20a36-5d29-f72d-b9ad-bea7ac2aba54.png)


コマンドパレットから`Cmake: Quick Start`を実行して、プロジェクトの雛形を作ってもらいます。

途中でこんなダイアログが出たら`Scan for kits`を選択します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/e1c2cc9e-8111-6ee7-f885-3c643278cc08.png)


Kitの種類も聞かれますが、`[Unspecified]` でとりあえずCMakeにお任せしておきます
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/9e090772-6eda-9b69-9b56-5c8bbdaebc03.png)


ここからいくつか入力を促されます。

* Projectname
    * 任意(`hello`としました)
* `Library` or `Executable`
    * 今回は実行ファイルを作りたいので`Executable`を選択しました

ここまで、プロジェクトの雛形が作成されました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/415b44ff-5366-631f-df51-d541e18b233f.png)


ちなみにこんな感じの階層になってました。実際にはもっと深いですが参考までに

```console
$ find . -maxdepth 2
.
./CMakeLists.txt
./.gitkeep
./build
./build/compile_commands.json
./build/CMakeFiles
./build/Makefile
./build/cmake_install.cmake
./build/CTestTestfile.cmake
./build/Testing
./build/CPackConfig.cmake
./build/CPackSourceConfig.cmake
./build/hello
./build/CMakeCache.txt
./build/DartConfiguration.tcl
./.vscode
./.vscode/ipch
./main.cpp
```

ここから`CMakeLists.txt`と`main.cpp`をベースに変更していけば良さそうです。

# 実行

この時点で最低限実行に必要な設定はされているので、下部の`Debug`ボタン押下で起動できます。
`Hello world!`がちゃんと表示されました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/93287083-2b06-c0cb-3c79-bfd13af9afb2.png)


# デバッグ機能をつかってみる

ブレークと変数書き換えをしたかったので、こんな感じに書き換えて最終行で止めてみました。

```cpp
#include <iostream>

int main(int, char**) {
    std::cout << "Hello, world!\n";
    int i = 10;
    std::cout << i;
}
```

ちゃんと指定した行で止まってくれたので、variablesのパネルで変数`i`を`10->5`に書き換えました。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/0f669945-a37f-46fb-af6e-3a6242d59179.png)

再開すると、ちゃんと`5`で表示されました。ちゃんとデバッガで書き換えできてますね。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330514/30e99726-b0c7-dc34-ce85-69ac56152d1d.png)


# 設定の概要

コンテナの設定は`.devcontainer/devcontainer.json`に定義します。

```.devcontainer/devcontainer.json
{
    "name": "cpp-devcontainer-project",
    "dockerComposeFile": [
        "./docker-compose.yml"
    ],
    "service": "cpp",
    "extensions": [
        "ms-vscode.cpptools",
        "vector-of-bool.cmake-tools",
        "twxs.cmake"
    ],
    // 社内プロキシなどで、拡張機能がインストールできない場合にコメントを外す
    //"settings": {
    //    "http.proxyStrictSSL": false
    //},
    "workspaceFolder": "/data"
} 
```

同ディレクトリ内のdocker-compose.ymlに従って起動するようにしています。
[Developing inside a Container using Visual Studio Code Remote Development](https://code.visualstudio.com/docs/remote/containers) を見た感じ、port forwarding等も`devContainer.json`に設定することで可能なようですが、既存設定の流用を考えた場合はdocker-composeの方が楽なので、docker-composeで起動しています。
ちなみにdocker-composeの場合は、立ち上がった複数のコンテナの内このコンテナに接続しますよ、というのを指定する必要があるのでdocker-composeのサービス名(`cpp`)を指定しています。

Visual Studio Codeの拡張機能も定義できるので、以下３つのC++とCMake関連を入れています。

* [C/C++ - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)
* [CMake Tools - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=vector-of-bool.cmake-tools)
* [CMake - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=twxs.cmake)

docker-compose.ymlとDockerファイルはシンプルにC++関連の設定をしているだけです。ただし、[注意点](#注意点)に書いている通り、gdb利用のための設定を入れています。

```docker-compose.yml
version: '3.3'

services:
  cpp:
    build: .
    volumes:
      - ../data:/data
    working_dir: /data
    tty: true
    # to use gdb
    cap_add:
      - SYS_PTRACE
    security_opt:
      - seccomp:unconfined
```

```Dockerfile
FROM ubuntu:18.04

RUN apt-get update && \
    apt-get install -y git build-essential cmake clang libssl-dev && \
    apt-get install -y cpputest libsqlite3-dev clang-format gdb
```

# 注意点

[Dockerコンテナ上のプロセスにstraceでアタッチする - Qiita](https://qiita.com/koemu/items/547e6e8d0043b815279e)

[Docker上でgdbを動かす - 俺より凄いやつしかいない。](https://www.cyamax.com/entry/2018/02/09/070000)

の記事そのままになるのですが、Docker上でgdbを動かすには以下のオプションを付けてコンテナを起する必要があります。

```yaml
    cap_add:
      - SYS_PTRACE
    security_opt:
      - seccomp:unconfined
```


付けない場合、`GDB: Failed to set controlling terminal: Operation not permitted\n cmake` のエラーが出ます。理由がわからず結構はまったので気をつけてください

ただし、最初にも書きましたがセキュリティレベルが下がることになりますので、開発環境でのみ使うよう気をつけてください。


# おわりに

C++環境をVisual Studio Code + Docker で構築してみました。
複雑なプロジェクトになった場合は試せてないですが、なかなか良さそうな感じがします。

あと`Remote - Containers`楽しい便利。

# 参考

[Dockerコンテナ上のプロセスにstraceでアタッチする - Qiita](https://qiita.com/koemu/items/547e6e8d0043b815279e)

[Docker上でgdbを動かす - 俺より凄いやつしかいない。](https://www.cyamax.com/entry/2018/02/09/070000)

[Visual Studio Code を使った C++ のビルド＆デバッグ方法（with CMAKE） - Daily Tech Blog](http://daily-tech.hatenablog.com/entry/2018/11/17/022047)