自動的にcondaの環境を切り替える

## やったこと

ディレクトリを経由することでcondaの環境を自動で切り替えられるようにしました。
自動切換したいディレクトリ直下の`.env`ファイルに`CONDA_ENV="hogehoge"`を追記する形で環境を指定することで、ディレクトリに入ると自動で環境を切り替えてくれます。

## 中身

まず、前提としてcondaをインストールした後、condaのbase環境が自動的にactivateされる機能を切ります。
```sh
conda config --set auto_activate_base false
```

以下を`~/.profile`なり`~/.bashrc`なりの設定ファイルに追記します。
```sh
function conda_auto_env() {
  LAST_COMMAND=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//')
  if [[ -e ".env" && $LAST_COMMAND != "conda"* ]]; then
    source .env

    if [[ -n "$CONDA_ENV" && $CONDA_DEFAULT_ENV != "$CONDA_ENV" ]]; then
      if conda activate "$CONDA_ENV" &>/dev/null; then
        export CONDA_ACTIVATED_DIR="$PWD"
        export CONDA_ACTIVATED_ENV="$CONDA_ENV"
      else
        echo "Failed to activate conda environment: $CONDA_ENV"
      fi
    fi
  elif [[ -n "$CONDA_ACTIVATED_DIR" && "$PWD" != "$CONDA_ACTIVATED_DIR"* ]]; then
    if [[ -n "$CONDA_ACTIVATED_ENV" && $CONDA_DEFAULT_ENV == "$CONDA_ACTIVATED_ENV" ]]; then
      conda deactivate &>/dev/null
    fi
    export CONDA_ACTIVATED_DIR=""
    export CONDA_ACTIVATED_ENV=""
  fi
}
export PROMPT_COMMAND="conda_auto_env;$PROMPT_COMMAND"
```

自動アクティベートをしたいときは、対象のディレクトリ以下、たとえは`/example/hoge/fuga`以下に`.env`ファイルを作成し、以下のように書き込みます。

```sh:.env
CONDA_ENV="hogehoge"
```

この状態で、`cd /example/hoge/fuga`すると自動的にactivateが走り`hogehoge`環境に入ります。このディレクトリや子孫のディレクトリではdeactivateはせず、子孫を含むディレクトリの外に出ると自動的にdeactivateします。

実装はわりと雑なので、自分なりにカスタマイズする必要があるかもしれません…。

## やりたいこと

pyenvでは.pyenv-versionファイルがあるディレクトリ以下に移動した際、自動的にpythonバージョンが切り替わる仕組みになっています。しかし、condaでは環境を使うたびに毎度activateしなければなりません。
同じプロジェクトのディレクトリ内で環境を変えることはほぼないので、できればpyenvのようにディレクトリに移動した段階で、自動的に切り替わってほしいです。

ただ、ディレクトリの内部で環境を変えたいことも、たまーにあるので、そのときに干渉してほしくないという願いもあります。

## やってること(コードの中で)

```sh
export PROMPT_COMMAND="conda_auto_env;$PROMPT_COMMAND"
```

まず、最後の行の`PROMPT_COMMAND`によってbash等でなにかを実行した後に自動的に呼び出される関数として、`conda_auto_env`を登録しておきます。これによって、bashで何かを打ち込むたびに、`conda_auto_env`が自動的に呼び出されるようになります。

つぎに、関数の中身です。

```sh
LAST_COMMAND=$(history 1 | sed 's/^[ ]*[0-9]*[ ]*//')
if [[ -e ".env" && $LAST_COMMAND != "conda"* ]]; then
    source .env

    if [[ -n "$CONDA_ENV" && $CONDA_PREFIX != *"$CONDA_ENV"* ]]; then
      if conda activate "$CONDA_ENV" &>/dev/null; then
        export CONDA_ACTIVATED_DIR="$PWD"
        export CONDA_ACTIVATED_ENV="$CONDA_ENV"
      else
        echo "Failed to activate conda environment: $CONDA_ENV"
      fi
    fi
```

ディレクトリ内部に.envファイルが存在する場合は、それを読み込んでCONDA_ENVが定義されているか探します。それが存在し、かつactivateされていないことを`$CONDA_DEFAULT_ENV != $CONDA_ENV`によって確かめています。この`CONDA_DEFAULT_ENV`はcondaが勝手につくってくれる環境変数で、現時点でアクティベートされているenvの名前が入っています。

アクティベートされていないことが分かったら、こんどは`CONDA_ENV`の名前で`conda activate`します。もしCONDA_ENVで指定された名前の環境が存在しなかった場合はエラーメッセージを表示し、存在した場合は`CONDA_ACTIVATED_DIR`と`CONDA_ACTIVATED_ENV`をそれぞれ環境変数としてexportします。

```sh
  elif [[ -n "$CONDA_ACTIVATED_DIR" && "$PWD" != "$CONDA_ACTIVATED_DIR"* ]]; then
    if [[ -n "$CONDA_ACTIVATED_ENV" && $CONDA_DEFAULT_ENV == "$CONDA_ACTIVATED_ENV" ]]; then
      conda deactivate &>/dev/null
    fi
    export CONDA_ACTIVATED_DIR=""
    export CONDA_ACTIVATED_ENV=""
  fi
}
```

つぎに、`.env`ファイルが存在しない場合、まず現在のディレクトリと自動アクティベート時にexportした`CONDA_ACTIVATED_DIR`を比較し、もし`CONDA_ACTIVATED_DIR`以下にいなかった場合はdeactivateを試みます。なお、この際ユーザーによって自動でactivateした環境と異なる環境が読み込まれていた場合はdeactivateを行いません。

このほかにも、この関数が呼び出される直前に行った操作(ユーザーが打ったコマンド)を取得して、condaそれがconda関係の場合は処理を行わないなどしています。負荷軽減のため、`cd`以外で呼び出さないとかにしてもいいかもしれません。あと、使うファイルが`.env`なのも人によって嫌悪感があると思いますので、人によって書き換えたほうがいいと思います。個人的にはgitなどで共有することのないファイルがお勧めです。環境名は人によって違うので。

## まとめ
ぱっと調べた感じ、condaの自動activateがなかったので作ったのですが、実はあったりするんですかね…。ChatGPTに聞いた感じだとdirenvとかでできるっぽいんですが、いうて管理したいのがcondaくらいだったので簡単にこういった形で実装してみました。っていうか、そもそもないのがおかしくないですかね…。

基本的にはpipとかを利用しているのですが、たまーにcondaが欲しくなったりして、そうすると後々普通にpyenvでpythonを入れたことを後悔しがちです。condaであれば、この環境変更の煩わしさを抜ければ、pipを使う場合は環境だけ作ってconda使わなければいいですし、使いたいときはcondaも使えるので良いかもしれません。特に、condaは昔のバージョンのTorchとかを入れるときにわりと便利(最近のバージョンではpipでもcuda勝手にインストールしてくれる)ですね。

## 参考
https://dev.classmethod.jp/articles/how-to-auto-conda-activate/