uchan note

プログラミングや電子工作の話題を書きます

ClangdでC++ソースコードを補完する際にコンパイルオプションを指定する方法

この記事では Clangd を使って C++ソースコードを補完する際に独自のコンパイルオプションを指定する方法を説明します.

事の発端

私は C++ で OS を自作しています.エディタは Vim を使っていますが,これまで大した補完機能は使っていませんでした. しかし Vim の神 mattn さん曰く

とのことなので,早速試してみました.

その際,Makefile 内で指定した独自のコンパイルオプションが Clangd に認識されず(例えば -I オプションが認識されずインクルードファイルが file not found になってしまう), 補完が正しく動かなかったので,解決方法を探りました.

f:id:uchan_nos:20181229104831p:plain
include しているファイルが 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++ に,clangcc に変換されるようです.

directoryコンパイルを行う際のワーキングディレクトリを指定します.fileコンパイル対象のソースファイルを指定します.

このファイルを何とかして作ってカレントディレクトリに置いておけば Clangd が自動的に認識してくれます.

compile_commands.json の生成

フォーマットに従っていれば手書きしてもいいのですが,面倒なので普通は自動生成をすると思います.

CMake を利用しているプロジェクトであれば cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON とすることで compile_commands.json を生成することができます.

Makefile を直接使っているプロジェクトでは compiledbbear を使うと同じようなことが可能です.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.jsonfile には * を設定できないようですから,頑張ってすべてのヘッダファイルに関する設定を手動で追加するわけです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 でハッピー補完ライフを!


  1. ヘッダファイルの場合は cpp と違い,arguments から -ohoge.o の指定を外してあげるとうまくいくみたいです.