DockerのElixir環境にVSCodeでリモート接続してデバッグする【Windows環境】

Dockerコンテナ上に構築された環境にVSCodeからリモートアクセスしてデバッグまでできるそうなので、Elixirで試してみました。なお、せっかくなのでErlang、Elixirのバージョンは2019/10/1時点で最新のものを使っていきたいと思います。

## 環境
- Windows 10 Pro 64bit
- VSCode 1.38.1
- Docker Desktop for Windows 2.1.0.3
- Docker Engine 19.03.2

## 事前準備
[こちら](https://blog.okazuki.jp/entry/2019/09/15/213405)を参考に以下をインストールしました。

- VSCodeのインストール
- VSCodeのリモート開発の拡張機能インストール
- Dockerのインストール

## Dockerfileの作成
- 作業用ディレクトリを作り、VSCodeで開きます。
-  F1キーを押してコマンドパレットを開き、 `Remote Containers: Add Development Container Configuration Files...`を実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/84c19dc1-321f-b3cb-6a4f-48cc4e8101bf.png)

- ベースとなる開発環境を選択しますが、DockerFileを編集するので何でも良いです。とりあえず`Ubuntu 18.04 & Git`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/45a7072e-c722-71da-581b-bffaf8ff768d.png)

- 作業用ディレクトリに`.devcontainer`ディレクトリが作成され、`devcontainer.json`と`Dockerfile`が自動生成されますので、`Dockerfile`をElixir環境用に変更します。
	- [Elixri1.9.1のDockerfile](https://github.com/c0b/docker-elixir/blob/8c92148d07fc89a57706dcb22c926a96e73a4194/1.9/Dockerfile)の内容をコピーして自動生成された`Dockerfile`に上書きします。
	- Elixri1.9.1のDockerfileから以下の箇所を変更しました。
		- LANGUAGE、LC_ALLの追加
		- mix local.hexの実行を追加
		- iexの起動の削除

```diff
FROM erlang:22

# elixir expects utf8.
ENV ELIXIR_VERSION="v1.9.1" \
-	LANG=C.UTF-8
+	LANG=C.UTF-8 \
+	LANGUAGE=C.UTF-8 \
+	LC_ALL=C.UTF-8

RUN set -xe \
	&& ELIXIR_DOWNLOAD_URL="https://github.com/elixir-lang/elixir/archive/${ELIXIR_VERSION}.tar.gz" \
	&& ELIXIR_DOWNLOAD_SHA256="94daa716abbd4493405fb2032514195077ac7bc73dc2999922f13c7d8ea58777" \
	&& curl -fSL -o elixir-src.tar.gz $ELIXIR_DOWNLOAD_URL \
	&& echo "$ELIXIR_DOWNLOAD_SHA256  elixir-src.tar.gz" | sha256sum -c - \
	&& mkdir -p /usr/local/src/elixir \
	&& tar -xzC /usr/local/src/elixir --strip-components=1 -f elixir-src.tar.gz \
	&& rm elixir-src.tar.gz \
	&& cd /usr/local/src/elixir \
	&& make install clean \
+	&& cd $HOME \
+	&& mix local.hex --force

- CMD ["iex"]
```

- `devcontainer.json`でコンテナ名が定義できます。`name`の値を任意の名前に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/80464394-9164-0d19-5d92-624e3711502d.png)


## コンテナにリモート接続し、拡張機能を有効化する
ここまで出来たらコンテナを立ち上げ、VSCodeからリモート接続します。

- VSCodeの左下の`リモートウィンドウを開く`を押すとリモート機能に関するメニューが出てくるので、`Remote-Containers: Reopen in Container`を選択します。そうすると、コンテナのビルドが行われリモート接続が完了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/7c4c7460-59b7-9c52-7b9d-93a478cc9954.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/569ca5a2-2d1c-cd38-630d-5989f9b26d48.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/930925f0-eb66-689e-efe1-159742875532.png)


- ターミナルを表示し、`elixir -v`を実行してバージョンを確認します。Erlang/OTP:22、Elixir:1.9.1がインストールされていることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/92728a42-e3bf-d1e5-a80d-00047cbdd908.png)

- リモートコンテナでは、ローカル環境でインストールしていた拡張機能が適用されていないため、Elixir関連の以下の拡張機能を有効にしておきます。インストールしたら再読み込みします。

	- vscode-elixir
	- ElixirLS
	- ElixirFormatter

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/0aea87e4-1c4f-7d77-400b-15a0548062fc.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/a45ef8e1-7925-8459-887a-9aa7c12062b4.png)

## Elixirでコードを動かす
とりあえずHelloWorldが動くことを確認します。

- `mix new hello`で動作確認用プロジェクトを作成します。ターミナルを表示し`cd hello`を行いHelloプロジェクトのディレクトリへ移動します。
- VSCodeのエクスプローラーから`hello/lib/hello.ex`を選択して開きます。Helloモジュールに`hello`関数ができているため、関数名を`world`に変更します。docも併せて変更しておきます。変更が終わったら保存します。

```diff
defmodule Hello do
  @moduledoc """
  Documentation for Hello.
  """

  @doc """
  Hello world.

  ## Examples

-      iex> Hello.hello()
+      iex> Hello.world()
      :world

  """
-  def hello do
+  def world do
    :world
  end
end
```

- ターミナルで`iex -S mix`を実行し、プロジェクトのモジュールをiexで開きます。`Hello.world`で`:wold`が返ってくることが確認できました。`Ctrl + C`を2回押してiexを終了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/a5dece8f-a0fb-d2df-f2e9-7baef10d7b5e.png)

## ブレークポイントを張ってデバッグする
デバッグが可能であることを確認します。

- VSCodeのデバッグウィンドウを開きます。デバッグ構成が無いので、`構成の追加...`を押してデバッグ構成を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/c5ce18c3-55fd-542d-57b2-7f3b08023bbd.png)

- `launch.json`にデフォルトのデバッグ構成が追加され開かれます。`projectDir`が`${workspaceRoot}`になっており、このままではmix.exsが見つからないためエラーとなるので、`{workspaceRoot}/hello`に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/c3526f40-e305-58b1-5df1-8fc07ad31e10.png)

- `hello/lib/hello.ex`を開き、デバッグ確認用のコードを追加します。

```diff
defmodule Hello do
  @moduledoc """
  Documentation for Hello.
  """

  @doc """
  Hello world.

  ## Examples

      iex> Hello.world()
      :world

  """
  def world do
+	x = 1
+	x = 2
    :world
  end
end
```

- `hello/test/hello_test.exs`のテストコードが自動生成されたままとなっているので、関数名を変更します。変更した行にブレークポイントを設定します。

```diff
defmodule HelloTest do
  use ExUnit.Case
  doctest Hello

  test "greets the world" do
-    assert Hello.hello() == :world
+    assert Hello.world() == :world
  end
end
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/ec4a30f1-2b68-b5c9-ccd6-de65cece183b.png)

- デバッグウィンドウを開き、デバッグ構成で`mix test`を選択した上でデバッグを開始します。ブレークポイントで止まっていることが確認できました。また、デバッグ確認用のコードがステップ実行できることも確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/9bddf788-8877-4777-f8e7-df64066ae324.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48472/21cd8ec6-71a3-59b6-7bef-96f2c0d65957.png)

## 所感
コンテナなのでローカル環境に影響が無い。コーディングはVSCodeの機能を活用できる。ローカルのディレクトリをマウントしているため、コンテナを終了しても成果物が残り続ける。結構良い感じなのではないでしょうか。次はコンテナ内でビルドする必要があるためにコーディングが中々捗らなかった[ElixirをAWS Lambdaで動かす](https://qiita.com/ansoposKAZ/items/508fd51640a8201c683b)ための環境を整えたいと思います。

## 参考
- [プログラミング言語勉強用の環境を Visual Studio Code + Docker で手に入れてみる](https://blog.okazuki.jp/entry/2019/09/15/213405)
- [Dockerで立ち上げた開発環境をVS Codeで開く!](https://qiita.com/yoskeoka/items/01c52c069123e0298660)