TF+Docker+Emacs+LSP

# 計算機サーバでうまいこと動かせる自由な環境が欲しい！
大学などでは一般に、ssh アクセスすることで利用可能となる計算機サーバが共有で用いられる傾向があります。
すると、例えばあの ~~大変迷惑な~~ Tensorflow を用いる際、大凡環境のバージョン問題などに悩まされます。

環境のバージョンをうまいこと調節するには、Dockerを使うことが一般的だったりしますので、こいつの上手い使い方を考える必要があります。

さらに計算機サーバへの ssh 接続は切れる可能性が高いので、 ssh 接続を切ってもプロセスが生きている必要があるわけです。

Solution としては 2 つくらいあります。

1. Nvidia-Docker + (tmux or gnu-screen) + vim    
    一般的な Solution。みんな大好き vim を使って世界平和

2. Nvidia-Docker + emacsdaemon    
    ちょっと変わった Solution。多分あんまりやる人はいない。

# TF + Docker + Emacs
## 要件定義
今回想定する環境は次の通り

- Nvidia-Docker が install できる環境 
    Ubuntu 14 とか CentOS 4 みたいなサポートが切れているOSは知りません。諦めてください。

- Emacs (Spacemacs)
    今回は Emacs の、 特に Spacemacs を用います。理由は構築速度が早いこと、`.spacemacs` ファイルで情報が管理されること、`python language server` を用いることが簡単なためです。

- remote access
    今回はサーバ上で Emacs を daemon 化しておき、そこへ適宜アタッチすることで ssh 接続が切れてもプロセスを継続する方法を取ります。そして Emacs から Docker 内へアクセスを図ります。

# Nvidia-Docker + Docker-compose + tensorflow
長々と書いてありますが、驚くほど簡潔に書くことができます。(`/home/meguru/Github` は適宜書き換えてください。)

```dockerfile:dockerfile
FROM tensorflow/tensorflow:2.0.0-gpu-py3
RUN mkdir /workspace
RUN pip3 install -U pip
RUN pip3 install python-language-server[all] ptvsd yapf
```

```yaml:docker-compose.yml
version: '2.3'
services:
    tensorflow-meguru:
        build: .
        volumes:
            - /home/meguru/Github:/workspace
        runtime: nvidia
```

これら2つのファイルを任意の同じフォルダに入れます。次にそのフォルダ下で container をビルドしておきます。

```shell
docker-compose build
```

これで docker コンテナを作る準備はできました。

# Settings for Spacemacs

Spacemacs の設定を書きます。この辺りは個人の趣味の範囲なので、最低限度(?)の設定ファイルを紹介します。必要に応じて書き足してください。

https://github.com/MokkeMeguru/docker-tensorflow-emacs-lsp



# ローカル環境 (linux) で実験してみる。
まずは 手元の Linux で試してみます。
emacsclient を CUI で立ち上げているのは、本番環境が ssh 先の計算機サーバであるためです。

```shell
emacs --daemon==tensorflow
emacsclient -c -nw --socket-name=="tensorflow"
```
emacs 上で multi-term を開いて

```shell
docker-compose run --rm tensorflow-meguru /bin/bash                                
________                               _______________                
___  __/__________________________________  ____/__  /________      __
__  /  _  _ \_  __ \_  ___/  __ \_  ___/_  /_   __  /_  __ \_ | /| / /
_  /   /  __/  / / /(__  )/ /_/ /  /   _  __/   _  / / /_/ /_ |/ |/ / 
/_/    \___//_/ /_//____/ \____//_/    /_/      /_/  \____/____/|__/


WARNING: You are running this container as root, which can cause new files in
mounted volumes to be created as the root user on your host machine.

To avoid this, run the container by specifying your user's userid:

$ docker run -u $(id -u):$(id -g) args...

0;root@8f44ad9e5967: /root@8f44ad9e5967:/# 
```

この `8f44ad9e5967` はコンテナIDです。
次に emacs の tramp-mode を使って、docker コンテナ にアクセスします。

```text:emacs-command
C-x C-f /docker:8f44ad9e5967:/workspace
```

次に、その tramp-buffer の状態で、 eshell を立ち上げましょう。

```text:emacs-command
M-x eshell
```

すると、eshell の root が `@8f44ad9e5967`。 つまり、docker コンテナの内部になります。
次に

```text:eshell_in_docker
@8f44ad9e5967 /d/workspace $
```

ここで、次の関数を評価します。これは docker 内の LSP を Emacs が呼び出すために必要な関数で、 **docker内から呼び出すことで同じ関数を使い回すことが出来ます。** (そうでないと `lsp-tramp-connection` に与える値が面倒なことになります。)

```emacs-lisp
(lsp-register-client
  (make-lsp-client :new-connection (lsp-tramp-connection "pyls")
                   :major-modes '(python-mode)
                   :remote? t
                   :server-id 'pyls-remote))
```

これで emacs が正しく LSP を認識できるようになりました。

試しに適当な python ファイルを開いてみましょう。

```text:emacs-command
C-x C-f /docker:8f44ad9e5967:/workspace/test.py
```

LSP モードのプロジェクトルートを設定するように言われますが、とりあえず今回はデフォルトの `/workspace` で良いでしょう。

ファイルを編集してみましょう。うまくLSPが動いていることがわかると思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147520/d48f4626-68b6-bbf5-eaf6-c339b068b225.png)

あとは通常通り Python を実行するなりしてください。(ref. [document: spacemacs の python layer](https://github.com/syl20bnr/spacemacs/tree/master/layers/%2Blang/python#inferior-repl-process) 但し `SPC = M-x`) 


### サーバの生存確認
次にクライアントを接続してもホストが生き残ることを確認します。
`C-x C-c` でクライアントを切断してみましょう。


次に新しいターミナルを開いて、`emacsclient -nw -c -socket-name==tensorflow` で接続してみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147520/42d39b21-0489-aad9-d2aa-e58b256f247c.png)

復帰して作業を再開できることが確認できました。

emacs-server を切断するには、 `M-x kill-emacs` で出来ます。

```shell
ps -aux | grep emacs
# meguru     936  0.0  0.0   7332  2568 pts/8    S+   07:52   0:00 grep --colour=auto emacs
```

サーバが落ちていることがわかると思います。


# まとめ

- tmux 以外でもうまいことサーバ-クライアント的な接続ができそう。
- emacs-lsp は tramp-mode を駆使すると簡単に接続できる。