shell.nixとdirenvでプロジェクトごとに補完をいい感じにする
概要
HERPでは shell.nix
をgit repositoryのrootに置いて依存関係を解決しているが、PATH
がprojectごとに設定されるだけなので 例えば kubectl
の補完が有効になるわけではない。
その課題を解決するために shell.nix
にzshの補完もうまく済ませるワザップを今回は書く。
zsh completion について
zsh の completionの仕組みをざっくり書くと、 FPATHをいい感じにして compinit
すれば FPATH
から解決されるcompletion用のscriptが実行され補完される
手法
FPATH用のderivation
まず、completionを集めたderivationを作る。
buildEnv
というderivationの特定のディレクトリの集合体を作る関数があるのでそれを使う
FPATH = "${nixpkgs.buildEnv { name = "zsh-comp"; paths = buildInputs; pathsToLink = "/share/zsh"; }}/share/zsh/site-functions";
FPATHの設定
上記で作った FPATH
を $HOME/.zshrc
で追加された FPATH
に足せばいいのだが
安易な方法として shell.nix
にFPATHを足してしまうと、そのまま上書きされて既存の補完の全てを失ってしまう
なので、 project_FPATH
のような環境変数に一時的に保存する。
このまま nix-shell --run zsh
のように実行する場合は, ZDOTDIR
をshell.nix中で上書きするなどのコードをを足せば問題ないのだが、
いちいち実行するのが面倒なので direnvを使っている場合は工夫が必要になる。
use nix
path_add FPATH $project_FPATH
以下の理由でこのコードは動きそうに見えて動かない(人によっては動く)
direnvは .envrc
の評価をbash上で行っており、評価した結果の差分を既存のshellに足すような実装になっている。(コレは echo $0
してあげるとわかる)
実はzshの FPATH
は exportされていない変数なので (コレは echo ${(t)FPATH}
でわかる、コレをlocal変数と言っていいのかはわからない) bash上で評価した際に存在していない変数として扱われFPATHが上書きされる
なので direnvの評価が始まる前、つまり ~/.zshrc
などで export FPATH
(typeset
でも可) しておく必要がある
export FPATH
してない人間向け対応
世の中には export FPATH
していない人間もいるので、そう言った人が projectに入って direnv allow
した瞬間に壊れて cd
するのも困るのは不親切という考え方もあるので対応をする。(考え方によっては学習機会を奪っている。人間は困らないと進化しないので)
上記で書いたように 評価がbash上で行っているので export -p
した中に FPATH
が含まれていなければ export FPATH
されていないことがわかる
よって以下のようになる
use nix if export -p|grep "declare -x FPATH" ; then path_add FPATH $project_FPATH fi
compinitの設定
あとは compinit するだけなのだが毎回 compinit
していてはダルい(ダルい)
なので direnv がhookで評価した後にFPATHが書き変わっていれば compinit
するようなhookを作ってあげれば良い
export COMPINIT_DIFF="" _chpwd_compinit() { if [ -n "$IN_NIX_SHELL" -a "$COMPINIT_DIFF" != "$DIRENV_DIFF" ]; then compinit -u COMPINIT_DIFF="$DIRENV_DIFF" echo "compinited !" fi } if [[ -z ''${precmd_functions[(r)_chpwd_compinit]} ]]; then precmd_functions=( ''${precmd_functions[@]} _chpwd_compinit ) fi if [[ -z ''${chpwd_functions[(r)_chpwd_compinit]} ]]; then chpwd_functions=( ''${chpwd_functions[@]} _chpwd_compinit ) fi
FPATH
の diffを取ると言ったが色々考えた結果,nix-shell中でdirenv が実行されたときにcompinitされればいいので上記のようにした。(compinit以外のこともしたくなるかもしれない)
蛇足として一応書いておくが autoload compinit
は他でするように
終わりに
nix-shell
と direnv
の組み合わせは環境を揃える手法として有名だが、実は補完まで揃えることができる。
各個人のzshrcに追記する必要はあるがオススメできる手法である。
余談
HERPのSREチームはnixを使って各種ツールの環境を揃えている。