Archive

WSL2やgit-bashではなくWindowsのnushell環境でdirenvを使う

先日 Windows の nushell 環境で direnv を使えるようにした際、少しハマりどころがあったのでメモ。Windowsネイティブでも direnv が使えてとても便利

結論

結論から言ってしまうと scoop などで Windows 向けの direnv をインストールして、以下の手順を踏む

1. direnv そのものを使うための環境変数をセット

Windowsの場合以下の環境変数をセットしないとdirenvがちゃんと動かない。nvim関連のファイルがおかれるパスを参考に以下の通りに $nu.config-path に追記したが、ここは「こうしなければいけない」という決まりはないので自分にとって都合のいいやり方でいいと思う

$env.DIRENV_CONFIG = $env.USERPROFILE + "\\AppData\\Local\\direnv"
$env.XDG_CACHE_HOME = $env.USERPROFILE + "\\AppData\\Local\\Temp"
$env.XDG_DATA_HOME = $env.USERPROFILE + "\\AppData\\Local"

参考(実際の自分のdotfilesの該当箇所): dotfiles/nushell/config.nu#L609-L612 at main · laughk/dotfiles

2. nushell の設定 $env.config.hooks.pre_prompt にdirenv用のhookを追記

追記内容は次の通り。後述する環境変数 $envPath$env.PATH ではない) が null になってしまうので、そこをケアしてあげる感じ

   --- (中略) ---

  hooks: {
    pre_prompt: [{||
      if (which direnv | is-empty) {
        return
      }

      mut envrc = (direnv export json | from json | default {})
      if ($envrc | is-empty) {
        return
      } 

      if ($envrc.Path == null) {
        $envrc.Path = ($envrc.PATH | split row ";")
      }
      $envrc | load-env

    }]

   --- (中略) ---

参考(実際の自分のdotfilesの該当箇所): dotfiles/nushell/config.nu#L308-L320 at main · laughk/dotfiles

試行錯誤メモ

nushell 公式ドキュメントの手順そのままではだめ

一応nushellの公式ドキュメントに以下のページがある

このページに従って設定し、envrc のあるディレクトリに移動すると次のエラーが発生する。

  × Input type not supported.
    ╭─[C:\Users\laughk\AppData\Roaming\nushell\env.nu:39:22]
 38 │     from_string: { |s| $s | split row (char esep) }
 39 │     to_string: { |v| $v | path expand | str join (char esep) }
    ·                      ─┬   ─────┬─────
    ·                       │        ╰── only string, record or list input data is supported
    ·                       ╰── input type: nothing
 40 │   }
    ╰────

詰んでる様子

direnvの設定にミスって詰んでる様子

$env.Pathnull になってしまい $nu.env-path の設定で行われる環境変数回りの調整(?)でこけるようになり、エラー文言が非常に長く、実行したコマンドの結果がほとんど見えなくなって実質詰んでしまう。

試しに一度設定を無効にして direnv export json を実行すると PATH に中身はあるが、 Pathnull になってしまうことがわかる。以下は実行例(一部マスク済み)

 direnv export json
direnv: loading ~/path/to/.envrc
direnv: export +COMMONPROGRAMFILES +COMSPEC +EXEPATH +HOME +MESSAGE_MAX_LENGTH +MSYSTEM +PATH +PLINK_PROTOCOL +PROGRAMFILES +SYSTEMDRIVE +SYSTEMROOT +TARGET_RECENT_DAYS +WINDIR -ComSpec -CommonProgramFiles -Path -ProgramFiles -SystemDrive -SystemRoot -windir
{
  "COMMONPROGRAMFILES": "C:\\Program Files\\Common Files",
  "COMSPEC": "C:\\Windows\\system32\\cmd.exe",
  "ComSpec": null,
  "CommonProgramFiles": null,
  "DIRENV_DIFF": "eJzs****************"
  "DIRENV_DIR": "-C:\\Path\\to\\direnvdir",
  "DIRENV_FILE": "C:\\Path\\to\.envrc",
  "DIRENV_WATCHES": "eJy0z**************************Ap_m",
  "EXEPATH": "C:\\Users\\laughk\\scoop\\apps\\git\\2.44.0\\bin",
  "HOME": "C:\\Users\\laughk",
  "MESSAGE_MAX_LENGTH": "100",
  "MSYSTEM": "MINGW64",
  "PATH": "C:\\Users\\laughk\\scoop\\apps\\git\\2.44.0\\mingw64\\bin;C:\\Users\\laughk\\scoop\\apps\\git\\2.44.0\\usr\\bin;C:\\Users\\laughk\\bin;C:\\Windows\\System32;C:\\Windows;C:\\Windows\\System32\\wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0;C:\\Windows\\System32\\OpenSSH;C:\\Program Files\\Amazon\\SessionManagerPlugin\\bin;C:\\Program Files\\RedHat\\Podman;C:\\Users\\laughk\\go\\bin;C:\\Users\\laughk\\scoop\\persist\\python311\\scripts;C:\\Users\\laughk\\scoop\\apps\\python311\\3.11.9;C:\\Users\\laughk\\scoop\\persist\\nodejs-lts\\bin;C:\\Users\\laughk\\scoop\\apps\\nodejs-lts\\20.11.1;C:\\Users\\laughk\\scoop\\persist\\python\\scripts;C:\\Users\\laughk\\scoop\\apps\\python\\3.12.2;C:\\Users\\laughk\\scoop\\apps\\gcc\\13.2.0\\bin;C:\\Users\\laughk\\AppData\\Local\\aquaproj-aqua\\bat;C:\\Users\\laughk\\AppData\\Local\\aquaproj-aqua\\bin;C:\\Users\\laughk\\AppData\\Local\\Programs\\my_apps\\exe;C:\\Users\\laughk\\scoop\\persist\\python39\\Scripts;C:\\Users\\laughk\\scoop\\apps\\python39\\3.9.13;C:\\Users\\laughk\\scoop\\persist\\python310\\Scripts;C:\\Users\\laughk\\scoop\\apps\\python310\\3.10.11;C:\\Users\\laughk\\scoop\\shims;C:\\Users\\laughk\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Users\\laughk\\.local\\bin;C:\\Users\\laughk\\AppData\\Roaming\\Python\\Python311\\Scripts;C:\\Users\\laughk\\AppData\\Local\\Programs\\Microsoft VS Code\\bin",
  "PLINK_PROTOCOL": "ssh",
  "PROGRAMFILES": "C:\\Program Files",
  "Path": null,
  "ProgramFiles": null,
  "SYSTEMDRIVE": "C:",
  "SYSTEMROOT": "C:\\Windows",
  "SystemDrive": null,
  "SystemRoot": null,
  "TARGET_RECENT_DAYS": "1",
  "WINDIR": "C:\\Windows",
  "windir": null
}

Windows 環境における nushell では $env.Path にLists型としてPATHの情報が入る感じになっているので、hook 内でそれに合わせるようにすると次のようになる

          mut envrc = (direnv export json | from json | default {})
          if ($envrc | is-empty) {
            return
          } 

          if ($envrc.Path == null) {
            $envrc.Path = ($envrc.PATH | split row ";")
          }
          $envrc | load-env
  • mut で変数宣言することで、mutable な変数となる(let の場合 imutable な変数となり、内容の変更ができなくなる)
  • direnv 経由で変更対象となる環境変数(変数 $envrc ) において、Pathnull の場合は $envrc.PATH の内容からLists型に変換したものを $env.Path へ格納
  • 最終的な調整ができたものを | load-env することで $env.Pathnull のまま読み込まれてしまうことを防ぐ

direnv export json の内容をよく見ると windir とか ProgramFiles なんかも null になってしまっているので、何か問題が発生するようであれば同様にして null になることを hook 内で防ぐ必要がありそうではある。ただ、現状は Path だけケアしておけば問題ない感じではある。