ClangdでC++ソースコードを補完する際にコンパイルオプションを指定する方法
この記事では Clangd を使って C++ のソースコードを補完する際に独自のコンパイルオプションを指定する方法を説明します.
事の発端
私は C++ で OS を自作しています.エディタは Vim を使っていますが,これまで大した補完機能は使っていませんでした. しかし Vim の神 mattn さん曰く
vim-clang で C/C++ の補完してた人はぜひ vim-lsp & clangd 試して欲しいな。かなりのストレスが解消される。
— mattn来日 (@mattn_jp) December 27, 2018
とのことなので,早速試してみました.
その際,Makefile 内で指定した独自のコンパイルオプションが Clangd に認識されず(例えば -I
オプションが認識されずインクルードファイルが file not found になってしまう),
補完が正しく動かなかったので,解決方法を探りました.
結論
Clangd にコンパイルオプションを伝えるには JSON Compilation Database Format Specification に従って compile_commands.json または compile_flags.txt というファイルを作り,そこにコンパイルオプションを列挙します.
Vim で Clangd を使う方法
これは色々記事があるので私がわざわざ書く必要はないかなと思います. 私は Ubuntu18.04でneovim(or vim8)+LanguageClient-neovim+clangdでC++の補完をする あたりを参考にしました.
compile_commands.json
compile_commands.json はソースコードをコンパイルする際のコンパイルオプションを Clangd に伝えるためのファイルです.Clangd を使って補完しようとするユーザが生成してあげる必要があります. 中身はこんな感じです.
[ { "arguments": [ "c++", "-c", "-I.", "-std=c++2a", "-fno-exceptions", "-o", "usb/classdriver/hid.o", "usb/classdriver/hid.cpp" ], "directory": "/home/uchan/mikanos/kernel", "file": "usb/classdriver/hid.cpp" }, ... ]
arguments
にはコンパイルオプションを列挙します.配列の先頭要素はコンパイラのコマンド名らしいのですが,どうやら Clang は特別扱いされ,clang++
は c++
に,clang
は cc
に変換されるようです.
directory
はコンパイルを行う際のワーキングディレクトリを指定します.file
はコンパイル対象のソースファイルを指定します.
このファイルを何とかして作ってカレントディレクトリに置いておけば Clangd が自動的に認識してくれます.
compile_commands.json の生成
フォーマットに従っていれば手書きしてもいいのですが,面倒なので普通は自動生成をすると思います.
CMake を利用しているプロジェクトであれば cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
とすることで compile_commands.json を生成することができます.
Makefile を直接使っているプロジェクトでは compiledb や bear を使うと同じようなことが可能です.Ubuntu であれば APT で bear をインストールできるのでお手軽です.
bear make
とすると,make で実行されるコマンドを bear が自動的に記録し,その記録をもとに compile_commands.json を生成してくれます.
compile_commands.json の問題点
bear コマンドを使って compile_commands.json を自動生成してみたところ,cpp ファイルについては上手く生成されました.しかしヘッダファイルは全く無視されました.まあ当然ですよね.Makefile に登場するのは cpp ファイルだけですから.
ここで困ったことになります.Vim で編集するのは cpp ファイルだけではありません.当然,ヘッダファイルも編集します.ヘッダファイルも他のヘッダファイルをインクルードしますし,C++20 の機能を使ってプログラムを書きますから,cpp ファイルと同じようにコンパイルオプションを指定してあげないとうまく補完されません.
compile_commands.json の file
には *
を設定できないようですから,頑張ってすべてのヘッダファイルに関する設定を手動で追加するわけです1が,そんな面倒なことはやってられないわけです.
compile_flags.txt
そこで登場するのが compile_flags.txt です.このファイルは 1 行に 1 つのコンパイルオプションを書きます. こうすることで,すべてのファイルで共通のコンパイルオプションが適用されます.書き方はこんな感じです.
-c -I. -std=c++2a -fno-exceptions
compile_commands.json に手書きで設定を増やすよりずっと楽ですし,ファイルが増えるたびに設定を追加する必要もありません.
compile_flags.txt の欠点は,コンパイルオプションを切り替えて複数のバイナリを生成する必要のあるプロジェクトには使えない可能性がある点です. compile_commands.json は 1 つのソースファイルについて複数のコンパイル設定を書けますから,x86 向けのビルド,arm 向けのビルドなど,コンパイルオプションを使い分けるプロジェクトにも対応可能です. compile_flags.txt を使ってしまうと,すべてのソースコードで共通の設定になってしまいます.
といっても,compile_flags.txt はあくまで補完用の設定ですから,そのあたりは工夫次第で何とかなるんじゃないかとは思います.
ではでは,Clangd でハッピー補完ライフを!
-
ヘッダファイルの場合は cpp と違い,
arguments
から-o
とhoge.o
の指定を外してあげるとうまくいくみたいです.↩