IntelliJ+Dockerでlocalを汚さず開発

# 背景・動機

IntelliJで開発するとき、依存ライブラリ(External Libraries)を手元に用意しておかないとジャンプとか補完とか便利機能が効かなくなる。

ただnpmならProject（intellijのProjectのこと。大体Gitのリポジトリ単位）のディレクトリ以下にしか影響を及ぼさないので問題ないのだが、
pythonのpipのデフォだと、全Projectから参照可能なinstallをする。書いたコードが手元で動いた理由が、実は別Projectの依存関係のおかげだったりしたらまずい。というか汚れるのがちょっと......（潔癖症）

venvも悪くないけど（AI周りはvenv流行ってるし）、docker開発が現場ではメジャーだと思う。
IntelliJのinterpreterをDockerに繋ぐことで、コンテナ内のExternal Libraryを参照させる[^1]。

# 方法

1. Preferences[^2]->Pluginsで`Docker`を入れる
1. Preferences->Build,Execution,Deployment->Dockerで`+`を押す
　　　　- Docker for Macがデフォルト。Connection successfulが出たらOK
![image.png](https://qiita-image-store.s3.amazonaws.com/0/100881/50786be7-44d7-f5dd-45d8-1397934bb59f.png)
1. docker-composeを使う場合は、Preferences->Build,Execution,Deployment->Docker->Toolsでパスを確認
　　　　- `docker compose`とサブコマンドで使っているならv2なのでパスじゃなくてチェックボックス 
1. docker( compose)の使いたいコンテナ(サービス)を起動しておく
1. File->Project Structure[^3]でSDKsを選択。（IntelliJならPythonのpluginsを入れておき、）`+` -> `Add Python SDK` -> `Docker( Compose)`[^4]を選択し設定。docker composeの場合、configを複数重ねられる。virtual envは`Add Local...`で選択
1. その場でDockerとの接続確認が行われ、Project Settings->ModulesのDependenciesに登録される。ダブルクリックするとPlatform Settings->SDKsに飛ぶので、Packagesで依存ライブラリがちゃんとあるか確認

これで再起動すれば完了です。
indexingに少し時間がかかると思いますが、Navigate to Definition(定義箇所へジャンプ、Command +B or Ctrl + B)などが使えます。

## docker内部でpoetryを使っている

docker外なら[この設定](https://python-poetry.org/docs/managing-environments/)で良いが、docker内なら以下など。

```
$docker compose exec -it service /bin/sh
$ poetry env info
Virtualenv
Python:         3.11.4
Implementation: CPython
Path:           /path
Executable:     /path/bin/python
Valid:          True
...
```

上のProject Structureでの設定中のdocker( compose)でのpython interpreterをpoetryのpathに変更(`/path/bin/python`)

# おまけ
## この方法での開発を推奨するケース

- localに環境を構築したくない
  - 今回のケースならｐythonやらpipやら入れたくない場合
- 依存パッケージの導入がlocalだと難しい・面倒
  - 開発に使うライブラリの導入についてDockerfileは既にあるがlocalで構築するには難しい・あれこれマシンにinstallして汚したくない
- 概要の通り、言語のデファクトのパッケージマネージャの特性に問題がある
  - `npm install`なら先述の通り、そのProject以下でしか依存ライブラリは呼び出せず他のProjectへは影響を与えない（注意点として、Projectディレクトリの親階層に`node_modules`があるとそこのモジュールも利用できる）が、今回題材にしたPythonの`pip install`は全プロジェクトに影響を与える
  - pyenvでユーザーディレクトリ以下の管理に変えても、pythonのversion単位にパッケージが集約されてしまい、Projectと依存パッケージが紐付かず下記不都合が生じる
      - Projectで依存として明記されていないパッケージのモジュールも呼び出せてしまう。結果として`requirements.txt`や`constraints.txt`から漏れて、他の人が環境を再現できなかったり、リリースできなかったり
      - Projectとパッケージが紐付いていないので消すのが面倒。他のProjectに影響が出てしまったり
      - 色んなProjectで`pip install`を繰り返したせい（？）で`pip list`で表示されるのにimport Errorが起きたり(ex, airflow入れるとsixが消えたり)

## idea tips
### 対応言語

IntelliJにPythonプラグインを入れればPyCharmの補完が使えるし、同様にPhpStormの代替にもできます。
NodeJSやGoにも対応しているので、[All Products Pack](https://www.jetbrains.com/store/#edition=personal)買う必要ないと思ってたのですが、実はC/C++などいくつかの言語には対応してなかったんですね......
詳細は下記を参照ください[^5]。
https://www.jetbrains.com/products.html#

### PHP pluginはデフォであれこれインストールしてる？

PHP pluginを有効にしてると、PHPを使ってないプロダクトでもapacheなどが勝手にExternal Librariesに入っているように見えます。
今のところ回避策を見つけられてない......

https://intellij-support.jetbrains.com/hc/en-us/community/posts/115000256564-Why-is-PHP-runtime-in-Java-project-How-to-remove-

### Project treeが壊れたら

File->Project Structure[^3]でModuleを見直して再起動すれば解決します
Preferencesじゃないってのをよく忘れます

### File Pathから開く

ディレクトリ数の多いProjectだとツリーを展開して探すの面倒ですよね
Tools->Create Command-line Launcherで使えるideaコマンドなら、パス指定でTerminalから開けます
https://pleiades.io/help/idea/working-with-the-ide-features-from-command-line.html


```shell
$idea path/to/file
# lineも指定でき`git grep`と組み合わせるとコピペでその行数から開けて便利
$git grep 'class Hoge' ./a/b
a/b/c.py:800:class Hoge(fuga):
$idea a/b/c.py:800
```

### 推しショートカット

- Search Everywhere(どこでも検索):Shift + Shift
- Show Intention Actions(よしな):Option(Alt) + Enter
- Find Action(アクション検索):(Ctrl)+Shift+A
  - ここからscratch fileをよく使う。特にJSON
- Navigation back or forward(カーソル位置の戻る・進む）: `Cmd + [` or ]
  - vimでいうCtrl+oだがファイル横断なのが便利
- Page top: fn + Cmd + left-arrow

---

[^1]: IntelliJ Ultimate（有料版）のみ確認済
[^2]: Preferencesを開くショートカット: Command + ,
[^3]: Project Structureを開くショートカット: Command + ;
[^4]: 参考: http://www.rhoboro.com/2017/01/29/pycharm-docker-2.html
[^5]: サムライズムの人が各製品とサポート言語の対応表を持っていたのですが、探しても落ちてないorz