pyてよn日記

一寸先は闇が人生

Dart/Flutter の VSCode 拡張が上手く動かないときの調査

気分転換に Flutter でも触ってみるかとなったときにエディタの設定がうまくいかなかった。

具体的には、Flutter の SDK を入れ、公式が提供している VSCode 拡張を入れても自動補完、フォーマットが動いてくれなかった。そのときの調査の過程と解決策を備忘録として残しておく。

flutter.dev

Summary

簡単にまとめだけ。

原因

SDK への「PATH が通ってなかった」というあるあるなやつだった。

ただし、嵌った点があって、それは「シェルが参照している PATH と VSCode が参照している PATH が異なっていた」という点。

解決策

これだけ。

以下、長いかつ退屈な作業なのでお暇な方だけご覧ください。

調査

状況の整理

まずは状況の整理。

  • Flutter の SDK はダウンロード済み
  • dotfiles で PATH は通した
  • PATH を通したので、シェルで Flutter/DartSDK を使える
    • 例えば、dart format ./src/hello.py というコマンドを実行したらフォーマットは行えていた
    • 補足しておくと、Flutter の SDK には DartSDK もバンドルされている
  • VSCodeDart の拡張を入れた
  • コードのシンタックスハイライトは行えているが、フォーマットが行えていない(下図)
  • settings.json で保存時の自動フォーマット、デフォルトのフォーマッタは設定済み(下のコード)

f:id:pytwbf201830:20210428040931p:plain
hello.dartシンタックスハイライトは行えているが、フォーマットが行えていない

  • settings.json
{
  "[dart]": {
    "editor.defaultFormatter": "Dart-Code.dart-code",
    "editor.formatOnSave": true
  }
}

ここまでで最低限の設定は出来ている。それでも補完とフォーマットが効かなかった。

原因の調査 1:Dart 拡張のログの調査

色々 issue を漁っていたところ、以下の issue に「Dart の拡張に dart.extensionLogFile という拡張自体のログを吐き出すファイルのパスを指定する設定項目がある」と書いてあった。

github.com

早速その設定項目を settings.json に追加し、ログを確認した。設定ファイルは以下。

  • settings.json
    • とりあえずログをデスクトップに吐き出すようにした
{
  "[dart]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "Dart-Code.dart-code"
  },
  "dart.extensionLogFile": "/Users/pyteyon/Desktop/dart-extension-log.txt"
}

以下のようなログが吐かれていた。

!! PLEASE REVIEW THIS LOG FOR SENSITIVE INFORMATION BEFORE SHARING !!

Dart Code extension: 3.21.1
Flutter extension: 3.21.0 (not activated)

App: Visual Studio Code
Version: 1.55.2
Platform: mac

HTTP_PROXY: undefined
NO_PROXY: undefined

Logging Categories:
    General

Wed Apr 28 2021 [04:02:56 GMT+0900 (Japan Standard Time)] Log file started
[4:02:56 AM] [General] [Info] Searching for SDKs...
[4:02:56 AM] [General] [Info] Environment PATH:
[4:02:56 AM] [General] [Info]     /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin
[4:02:56 AM] [General] [Info]     /Users/pyteyon/.poetry/bin
[4:02:56 AM] [General] [Info]     /Users/pyteyon/.anyenv/bin
[4:02:56 AM] [General] [Info]     /Users/pyteyon/.anyenv/envs/nodenv/shims
[4:02:56 AM] [General] [Info]     /Users/pyteyon/.anyenv/envs/nodenv/bin
[4:02:56 AM] [General] [Info]     /usr/local/bin
[4:02:56 AM] [General] [Info]     /usr/local/sbin
[4:02:56 AM] [General] [Info]     /usr/local/bin
[4:02:56 AM] [General] [Info]     /usr/bin
[4:02:56 AM] [General] [Info]     /bin
[4:02:56 AM] [General] [Info]     /usr/sbin
[4:02:56 AM] [General] [Info]     /sbin
[4:02:56 AM] [General] [Info] Searching for flutter
[4:02:56 AM] [General] [Info]     Looking for flutter in:
[4:02:56 AM] [General] [Info]         /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin
[4:02:56 AM] [General] [Info]         /Users/pyteyon/.poetry/bin
[4:02:56 AM] [General] [Info]         /Users/pyteyon/.anyenv/bin
[4:02:56 AM] [General] [Info]         /Users/pyteyon/.anyenv/envs/nodenv/shims
[4:02:56 AM] [General] [Info]         /Users/pyteyon/.anyenv/envs/nodenv/shims/bin
[4:02:56 AM] [General] [Info]         /Users/pyteyon/.anyenv/envs/nodenv/bin
[4:02:56 AM] [General] [Info]         /usr/local/bin
[4:02:56 AM] [General] [Info]         /usr/local/sbin
[4:02:56 AM] [General] [Info]         /usr/local/bin
[4:02:56 AM] [General] [Info]         /usr/bin
[4:02:56 AM] [General] [Info]         /bin
[4:02:56 AM] [General] [Info]         /usr/sbin
[4:02:56 AM] [General] [Info]         /sbin
[4:02:56 AM] [General] [Info]     Found at:
[4:02:56 AM] [General] [Info]     Candidate paths to be post-filtered:
[4:02:56 AM] [General] [Info]     Returning SDK path undefined for flutter
[4:02:56 AM] [General] [Info] Searching for dart
[4:02:56 AM] [General] [Info]     Looking for dart in:
[4:02:56 AM] [General] [Info]         /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin
[4:02:56 AM] [General] [Info]         /Users/pyteyon/.poetry/bin
[4:02:56 AM] [General] [Info]         /Users/pyteyon/.anyenv/bin
[4:02:56 AM] [General] [Info]         /Users/pyteyon/.anyenv/envs/nodenv/shims
[4:02:56 AM] [General] [Info]         /Users/pyteyon/.anyenv/envs/nodenv/shims/bin
[4:02:56 AM] [General] [Info]         /Users/pyteyon/.anyenv/envs/nodenv/bin
[4:02:56 AM] [General] [Info]         /usr/local/bin
[4:02:56 AM] [General] [Info]         /usr/local/sbin
[4:02:56 AM] [General] [Info]         /usr/local/bin
[4:02:56 AM] [General] [Info]         /usr/bin
[4:02:56 AM] [General] [Info]         /bin
[4:02:56 AM] [General] [Info]         /usr/sbin
[4:02:56 AM] [General] [Info]         /sbin
[4:02:56 AM] [General] [Info]     Found at:
[4:02:56 AM] [General] [Info]     Candidate paths to be post-filtered:
[4:02:56 AM] [General] [Info]     Returning SDK path undefined for dart
[4:02:57 AM] [General] [Error] No Dart or Flutter SDK was found. Suppressing prompt because it doesn't appear that a Dart/Flutter project is open.

上に示したログを見ると分かるように、Dart / Flutter の拡張は自動でローカルの PATH を参照して SDK を探し回ってくれる。ログの最終行付近を見ると、「SDK がないよ」というエラーログが吐かれていた。これがシンタックスハイライトは動くがフォーマットや補完は効かなかった原因。

ひとまず原因は分かった。

原因の調査 2:PATH に関する調査

冒頭で述べたとおり、嵌った点は、

  • シェルが参照している PATH と VSCode が参照している PATH が異なっていた

という点である。

SDK はそもそも PATH を通さないと使えないので、SDK をダウンロードした時点で dotfiles を更新し、既に SDK をダウンロードしたディレクトリには PATH を通していた。以下に示した PATH の出力の末尾を見ると、たしかに SDK への PATH は通してあるし、実際に SDK も使えていた。

  • PATH の出力
echo $PATH

/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin:/Users/pyteyon/.poetry/bin:/Users/pyteyon/.anyenv/bin:/Users/pyteyon/.anyenv/envs/nodenv/shims:/Users/pyteyon/.anyenv/envs/nodenv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin:/Users/pyteyon/.poetry/bin:/Users/pyteyon/.anyenv/bin:/Users/pyteyon/.anyenv/envs/nodenv/shims:/Users/pyteyon/.anyenv/envs/nodenv/bin:/usr/local/sbin:/Users/pyteyon/SDKs/flutter/bin

見づらいのでディレクトリ毎に改行してみた。

  • PATH の出力
echo $PATH

/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin
/Users/pyteyon/.poetry/bin
/Users/pyteyon/.anyenv/bin
/Users/pyteyon/.anyenv/envs/nodenv/shims
/Users/pyteyon/.anyenv/envs/nodenv/bin
/usr/local/bin
/usr/local/sbin
/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin
/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin
/Users/pyteyon/.poetry/bin
/Users/pyteyon/.anyenv/bin
/Users/pyteyon/.anyenv/envs/nodenv/shims
/Users/pyteyon/.anyenv/envs/nodenv/bin
/usr/local/sbin
# ここで Flutter SDK に PATH を通してることを確認
/Users/pyteyon/SDKs/flutter/bin

(なんか重複してるけどなんだこれ)

ここで、先程の dart.extensionLogFile という Dart 拡張の設定項目で吐かれたログと、echo で出力された PATH を比較すると、ログの方には /Users/pyteyon/SDKs/flutter/bin の文字列がない。VSCode が参照してる PATH には SDK の場所が追加されておらず、Dart 拡張が SDKディレクトリにたどり着けていない。ここでやっとシェルの PATH と VSCode が参照している PATH に違いがあるということが分かった。

ひとまず、dotfiles(.zshrc)で PATH を設定している部分を見直してみる。繰り返しになるが、以下の設定によってシェルから SDK は使えるようになっている。

  • .zshrc
export PATH="/usr/local/sbin:$PATH"
export PATH="/usr/local/bin:$PATH"

# for anyenv
eval "$(anyenv init -)"
export PATH="$HOME/.anyenv/bin:$PATH"

# for poetry
export PATH="$HOME/.poetry/bin:$PATH"

# for the Dart/Flutter SDK
export PATH="$PATH:$HOME/SDKs/flutter/bin"

# for GCP
source '/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/path.zsh.inc'
source '/usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/completion.zsh.inc'

多分問題なさそう(?)。というかここに問題あったらそもそもシェルで SDK 使えてない。

てかなんで Flutter の SDK の設定だけ丁寧にスルーされてるんだ。dotfiles の更新が反映されてない?一個引っ掛かる点といえば、ホームディレクトリにオリジナルのディレクトリ($HOME/SDKs)を作ってそこに Flutter SDK を置いてることくらい?あんま関係ないのかな。いやそれだと Anyenv とか Poetry とかの PATH の設定が VSCode 側に伝わっているのが説明つかないか。(全く関係なかった)

原因の調査 3:VSCode が参照する PATH はどこから来るのか

ここまでの調査により、後は VSCode が PATH を参照する仕組みを調べるのみとなった。

さっそく Dart Code(拡張の名前)の公式ドキュメントの FAQ にそれっぽい記述を見つけた。

dartcode.org

「なぜ Dart Code は私の Dart/Flutter の SDK を見つけてくれないの?」。まさしくこれ。この質問に対する回答が以下。

Dart Code generally will try to find Dart and/or Flutter from your PATH variable. Depending on how you launch VS Code it amy not inherit the PATH from your terminal. For information on how best to set your PATH see Configuring PATH and Environment Variables.

ざっくり訳すと、「Dart Code は自分の環境の PATH から Dart/Flutter SDK を探そうとする。VSCode の起動の仕方によって、ターミナルの PATH(シェルの起動時に dotfiles によって更新される PATH)を引き継いでくれないかもしれない。PATH をセットするベストな方法を知るには Configuring PATH and Environment Variables を見てね」とのこと。

mayamy になってたのでついでに PR 投げといた。2 文字変更しただけの PR。

PR は置いといて、FAQ に推奨されたページを見に行く。

  • Configuring PATH and Environment Variables

dartcode.org

以下、リンク先の引用。

By default, Dart Code will try to locate Dart and Flutter SDKs from your PATH variable. Additionally, Flutter may use environment variables to locate some dependencies (such as ANDROID_HOME for the Android SDK). It’s common to set PATH and environment variables in a terminal script like .bash_profile or .bashrc however these changes often only apply to terminal sessions so if you launch VS Code from a launcher/dock you may find that Dart Code is unaware of them.

If you have issues with Dart Code not locating your Dart or Flutter SDKs (or Flutter Doctor saying it cannot locate your Android SDK) try the suggestions below.

なるほど。そもそも dotfiles の設定を VSCode に反映させたければ、シェルから起動する必要があったのか。dotfiles の内容が反映されているシェルのプロセス内で起動しないと環境変数を引き継げない、という解釈で良さそう(これは半分くらいあってる。新規プロセスとして VSCode を起動するときに裏側でシェルが起動されてその際に dotfiles を読んでくれて、もしその中で PATH に関する設定を更新してるならその更新後の PATH を VSCode が参照するようにしてくれる。)。逆に、Launcher とか Dock(macOS)から起動すると新規のプロセスを起動するから .zshrc とかが実行されず、結果的に PATH が反映されていなかった。(これどうなってるんだろう?)。(これ間違い)

上記を鑑みると、今回のエラーを引き起こした自分の手順の問題点は、dotfiles を更新する場所、タイミングが悪かったことだと考えられる。

Flutter SDK のダウンロード、dotfiles の更新を行ったのは VSCode の Integrated Terminal 内だった。普段使いでターミナルを使うときも、割と VSCode を開いてから特定のプロジェクトに関するコマンドを実行するからこれが癖になっていた。

これにより、まず、VSCode 起動時の PATH が Flutter SDK をダウンロードする前の(Flutter SDK に PATH を通していない)dotfiles によって設定される。そして、SDK のダウンロード以降 1 回も VSCode を再起動してないからずっと同じプロセス上で VSCode が動き続けており、更新後の dotfiles は再読み込みされず、VSCode が参照する PATH がずっと更新前の PATH だったという状態になっていた。

Dart Code(拡張)が先述した .zshrc に書かれた Anyenv とか Poetry の CLI が置いてあるディレクトリは探しに行ってるけど、いつまで立っても Flutter SDK が置いてあるディレクトリに探しに行ってくれなかった理由はこれだった。単に更新後の dotfiles の反映がされておらず、PATH が更新されていなかったということである。自分は「Anyenv とかの Flutter SDK 以外の PATH の設定は VSCode が読みに行ってくれてるから、dotfiles はちゃんと参照してくれているはず」という勘違いをしてしまっていた。

もちろん、Integrated Terminal や iTerm2 などで新規にシェルを起動する度に dotfiles は読み込まれるので、dotfiles が適切に更新されていばシェル上では Flutter SDK の PATH が通って使える状態になる。VSCode 側のプロセスはずっと起動したままなので PATH はそのまま。「シェルが参照している PATH と VSCode が参照している PATH が異なっていた」とはこういうことだった。

多分これに気づかなかったのは、今まで使っていた各言語に関する VSCode 拡張の多くが明示的にランタイムのパスを設定する項目が既に用意されており(例えば、Python 拡張は venv の場所を設定する必要がある)、今回の Dart Code のような SDK の ディスカバリ機能がなかったからかな?

何にせよ解決策は「VSCode の再起動」だった... ただし、エディタのウィンドウを閉じるとかリロードするとかだけではダメ。VSCode のプロセス自体を kill する必要がある。親プロセスの環境変数は子プロセスには引き継がれるので親ごと kill する必要がある。Mac の場合、Dock に表示されている VSCode を右クリックして "Quit" をクリックすれば再起動は全てを解決してくれる。

そして、「dotfiles を管理している」という(謎の)安心感と「今更 PATH 関連で引っかからんやろ」というあほみたいな自信によって再起動を真っ先にすることを忘れていた自分にはいい薬になりました。すぐに再起動していたら時間は消費しなかったけどその反面この挙動に気付くことは出来なかったからそれはそれで良かった。

補足:VSCode のプロセスの親子関係

VSCode のプロセスの親子関係を調べてみた。各プロセスの中身は調べてないからわからないけど、メインとなるプロセスは 1 つであることが視覚的に分かる。

f:id:pytwbf201830:20210428132533p:plain
VSCode のプロセスツリー

画像の一番下の方に子を持たない小さめのトップレベルのプロセスがあるけど、多分今回の文脈では関係なさそう?

ちなみに、macOS だと ps auxf コマンドが使えないからプロセスツリーを見るには pstree という CLI ツールを別途インストールする必要がある。Homebrew でインストールできる。

  • pstree

formulae.brew.sh

反省点

  • dotfiles の更新が伴う(PATH の更新が伴う)作業はエディタの外でやれ。もしくは更新したらそれを反映させるために再起動を怠るな。
  • 困ったら再起動しろ。