Rustの開発環境を整えるまでにやったことメモ

## 環境
| | version | 備考
|:-:|:-:|:-:|
| windows10 Pro | 1903 | (OSビルド) 18362.1082 |
| Docker Desktop | 2.4.0.0(48506) | wsl2で動作 |
| vscode | 1.49.3 |  |
コンテナで開発・ビルドできるようにする。
しばらく離れていた＆環境が変わったので自分用備忘録
wsl2+Docker Desktopに関しては[ここ](https://dev.classmethod.jp/articles/docker-desktop-for-windows-on-wsl2/)と[ここ](https://dev.classmethod.jp/articles/how-to-setup-wsl2/)と[ここ](https://docs.microsoft.com/ja-jp/windows/wsl/install-win10)を参考にした。

hyper-vからの乗り換えの場合、
wsl2を使えるようにしてからhyper-vを無効化したらエラーが出たので、

1. （Docker Desktopアンインストール） 
2. hyper-v 無効化
3. wsl2有効化
4. （Docker Desktop再インストール）

とすると確実かもしれない。

## 本編
### 1. コンテナイメージの取得・確認
PowerShellにて

```
PS:> docker pull rust:latest
     (結果略)

PS:> docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
rust                latest              c69fb046b993        3 days ago          1.28GB
```

### 2. composeファイルの作成
vscodeで開発用コンテナを立ち上げるために、docker-composeファイルを作成します。
vscodeの機能で作成することもできますが、追加で少しやりたいことがあるため自作します。

```{docker-compose.local.yml}
version: '3.7'

services:
  rust-dev:
    image: rust:latest
    container_name: rust_dev
    working_dir: /app
    volumes:
      - ./:/app
      - rust_dev_target:/app/target
    tty: true
    environment:
      USER: xxxxx # cargo で必要になる
      CARGO_BUILD_TARGET_DIR: /app/target

volumes:
  rust_dev_target:
    external: true

```
基本的な部分の解説はdocker-composeの[リファレンス](https://docs.docker.jp/compose/compose-file.html)とか、その他の解説記事に任せるとして、
やりたかったことに関わる2点だけ

1.  **environmentの`CARGO_BUILD_TARGET_DIR`**  
  この環境変数で指定したディレクトリに、targetが吐き出される
1.  **volumes**  
node_modulesを隔離するための方法から着想を得たやり方。  
`CARGO_BUILD_TARGET_DIR`でtargetフォルダをプロジェクトの外に出し、[^1]  
そこに*rust_dev_target*という外部ボリュームをマウントしなおしている。  
これによってコンテナを削除してもtargetの中身を維持することができる。 

<details><summary>小技とか</summary><div>
#### composeファイル内で変数を使う
composeファイルと同じ階層に*.env*ファイルを置いておくと、環境変数として**自動で**読み込んでくれる。[^2]
これを使って一部置き換えておくことで、例えばマウント先のフォルダ変えたいなーってなったときに少しだけ便利。
composeファイル内での環境変数の参照は${~~}で出来る

```{ ..env }
mnt=/app
```
```{docker-compose.local.yml}
version: '3.7'

services:
  rust-dev:
    image: rust:latest
    container_name: rust_dev
    working_dir: ${mnt}
    volumes:
      - ./:${mnt}
      - rust_dev_target:${mnt}/target
    tty: true
    environment:
      USER: xxxxx 
      CARGO_BUILD_TARGET_DIR: ${mnt}/target

volumes:
  rust_dev_target:
    external: true
```
</div></details>

### 3. vscodeでコンテナを立ち上げる
1. 適当なフォルダを作って上記のcomposeファイルを置き、codeで開く 
1. vscodeに拡張機能[Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)を追加する。
1. 左下に追加される緑のボタンを押し、  
`Remote-Containers: Add Development Container Configuration Files...`  
を選択する
1. `From 'docker-compose.local.yml'`を選択する。
  すると、*.devcontainer*というフォルダに*devcontainer.json*、*docker-compose.yml* が作成されるのでそれぞれ編集する
    
    ```{docker-compose.yml}
  version: '3.7'
  services:
    rust-dev:
      # volumes: # この行コメントアウトした
        # Update this to wherever you want VS Code to mount the folder of your project
        # - .:/workspace:cached # この行もコメントアウト

      # Overrides default command so things don't shut down after the process ends.
      command: /bin/sh -c "while sleep 1000; do :; done"
    ```
こちらはvolumesをコメントアウトするだけ。
見やすさのために省略しているが、コメントに説明が書いてあるので、読んで適宜変更。

    ```{json:devcontainer.json}
    {
        "name": "rust😀",
        "dockerComposeFile": [
            "..\\docker-compose.local.yml",
            "docker-compose.yml"
        ],
        "service": "rust-dev",
        "workspaceFolder": "/app",
        "settings": {
            "terminal.integrated.shell.linux": null,
            "editor.formatOnSave": true,
            "rust.target_dir": "/app/target"
        },
        "extensions": [
            "rust-lang.rust",
            "vadimcn.vscode-lldb"
        ]
    }

    ```
    <details><summary>分かる範囲で詳細</summary><div>
    + name  
        これだけコメントがない。vscodeでの表示上の名前、だけだと思う。
    + dockerComposeFile  
        composeファイルを読み込む順に書く。composeファイルをさらに分割している場合はここに書けばよい。  
    **基本的には変更する必要はない。**
    + service  
    composeファイルのservicesに定義した名前。複数定義している場合はメインとなるものを。  
    **基本的には変更する必要はない**
    + workspaceFolder  
    composeファイルで定義した、*working_dir*と同じ値を設定する。  
    コメントアウトすると`/`を開くのでdocker-compose.local.ymlで定義した値が上書きされるっぽい  
    じゃあ*working_dir*要らなかったじゃん。ｿﾀﾞﾈｰ
    + settings  
    vscodeのsetting.jsonに書いている内容をそのまま書ける。  
    コンテナ内部にだけ適用されるので、下記extensionsで追加する拡張機能に対する設定はここに書いておけば間違いない。
    + extensions  
    コンテナに導入したい拡張機能のIDを書く。IDは拡張機能の名前の横に表示されている。  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293810/a7fdd103-4519-b75d-81fe-2c8d2ee2a495.png)  
    例）図の灰色ががっている部分がID  
    とりあえずRust拡張機能とCodeLLDB。他にいいものがあれば知りたい。
    + forwardPorts  
    コンテナのポート開放する際にはアンコメントする。composeファイルに*ports*で書いても同じ。

    </div></details>

    **注意点**
    settingsのrust.target_dir
    これを書かないと、追加したcrateをrlsが見つけられずエラーが出る
   

1. volume作成  

    ```
    PS:> docker volume create rust_dev_target

    PS:> docker volume ls
    DRIVER              VOLUME NAME
    local               rust_dev_target
    ```
   composeファイルで`external:true`しているので先に作っておかないとエラーになる

1. 左下の緑のボタンを押し、
    `Remote-Containers: Reopen in Container`
    を選択する。準備編終わり。

### 4. プロジェクトの作成

開発は[The Rust Programming Language](https://doc.rust-jp.rs/book-ja/)を参考にすれば問題ないと思う。
著者とコミュニティに感謝。
Cargoでプロジェクトを作成すればHello worldが出来る。

```{ shell:TERMINAL }
root@02080d70998d:/app# cargo --version
cargo 1.47.0 (f3c7e066a 2020-08-28)
root@02080d70998d:/app# cargo new hello --bin
     Created binary (application) `hello` package
root@02080d70998d:/app# cd hello
root@02080d70998d:/app/hello# cargo run
(略)
Hello, world!
```

### 5. オートフォーマット
右下のベルマークのところに
`Some Rust components not installed. Install?`
と出るのでYesでインストール。
終わると保存したときに整形されるようになる。
コンテナを削除(orリビルド)した場合はもう一度やる必要がある。

### 6. マルチステージビルド
リリースビルドと同時にコンテナ化したいので専用のDockerfileとcomposeファイルを作る。
ついでにパッケージインストールはキャッシュさせて高速化したいのでそのあたりも対応。 [^3]

```{Dockerfile:Dockerfile}
FROM rust:latest AS builder

WORKDIR /app
ENV USER=root

# RUN rustup target add x86_64-unknown-linux-musl

RUN cargo new my_prj
WORKDIR /app/my_prj

COPY ./hello/Cargo.toml ./hello/Cargo.lock ./
RUN cargo build --release 

COPY ./hello/src ./src
RUN cargo build --release 

FROM debian:stretch-slim AS prod
ENV TZ=Asia/Tokyo
COPY --from=builder /app/my_prj/target/release/ /usr/local/bin
WORKDIR /app
CMD ["hello"]
```
空プロジェクトを一度ビルドしてからsrcを入れ替えて再ビルドしている。
alpine向けにビルドしようとしてうまくいかなかったので調査中。
続いてcompose

```{docker-compose.build.yml}
version: '3.7'

services:
  builder:
    build:
      context: .
      dockerfile: DockerFile
      target: builder
    image: rust-test:builder

  rust_test:
    build:
      context: .
      dockerfile: DockerFile
      target: prod
    image: rust-test:latest
```
builderサービスをtarget: builder[^4]として定義してあげることで、マルチステージビルドにおけるビルドステージにあたるコンテナにタグをつけておくことができる。
こうすることでdanglingイメージ（`<none>:<none>`のこと）にならないので、ビルド途中の状態がキャッシュ出来る。

### 7. 実行用compose作成
rust-test:latestに必要な環境変数とか与えて実行できるようにする。
目新しいことがないので割愛

## おわりに
開発用のDBも同時に立ち上がるようにしたり、他にもやったことがあるけど今回はここまで

## 追記
### hot-reload効かない問題への対処
プロジェクトをwindowsのファイルシステム上[^5]に構築すると、
windows-linuxのファイルシステム間の問題でソースコードの変更検知がうまく走らない。
そのため、wslで管理しているディレクトリにコードを置く必要がある。
具体的には、
```\\wsl$\Ubuntu-20.04\home\ユーザー名\workspace\プロジェクト名```
など。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293810/ee890108-5ffa-0705-a40a-7ebfae9ec44d.png)


[^1]: targetフォルダは、通常プロジェクトフォルダの内部にできるが、composeファイルにプロジェクトファイルの名称を入れたくなかったため外出ししている
[^2]: 別フォルダに置く場合はenv_filesに指定する。
[^3]: Dockerfile的にはキャッシュしていると思うが、Rustのビルドの仕組みに明るくないので保証はできない。
[^4]: AS で付けた名前
[^5]: \cや\d以下の任意のフォルダ