0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Chromium の index を作成する : clangd-indexer

0
Posted at

はじめに

Chromiumのソースコードはざっくり数千万行ある。ファイル数だけでも万を超える。この規模のC++プロジェクトでは予め clangd の利用が標準で用意されている。 Chromiumほどの巨大プロジェクトだと初回のバックグラウンドインデックスにそうとう数時間がかかるため、予め clangd-indexer を使ってChromiumのインデックスを事前に作っておく方法を確認する。

clangd とは

clangd は LLVM プロジェクトが開発しているC/C++向けのLanguage Serverだ。Language Server Protocol (LSP) というしくみを使って、エディタに「コード補完」「定義ジャンプ」「参照検索」「エラー表示」といった機能を提供する。

ふつうの C++ プロジェクトなら、clangdをインストールしてエディタにつなぐだけで快適に使える。しかし、Chromiumの場合はそう単純にはいかない。

  • Chromium が使っているClangのバージョンとclangdのバージョンを合わせないと、診断メッセージがずれたり、存在しないエラーが出る
  • compile_commands.jsonという「各ファイルをどうコンパイルするか」の情報をclangdに教える必要がある
  • Chromium全体のインデックスをバックグラウンドで構築すると48コア64GBのマシンでも簡単に数時間は時間かかる

clangdのインデックスの方法

バックグラウンドインデックス
clangd が裏でこつこつ全ファイルを解析して作る。clangd 9 からデフォルトで有効であり、.cache/clangd/index/ 以下にキャッシュされる。

静的インデックス(Static Index)
clangd-indexer というツールを使ってオフラインで事前に作る。clangdの起動時に読み込ませることで、バックグラウンドインデックスを待たなくてよくなる。今回の話はこのツールの導入方法になる。

リモートインデックス
別のサーバーにインデックスを置いて、ネットワーク越しに問い合わせるしくみ。Chromiumプロジェクトは公式のリモートインデックスサーバーを運用している。

Chromium 向けの clangd を用意する

方法1: gclientで同梱版を取得する

一番かんたんな方法は、.gclient ファイルに checkout_clangd を設定すること

solutions = [
  {
    "url": "https://chromium.googlesource.com/chromium/src.git",
    "managed": False,
    "name": "src",
    "custom_deps": {},
    "custom_vars": {
      "checkout_clangd": True,
    },
  },
]

この設定で gclient sync を走らせると、Chromiumが使っているClangとバージョンが揃ったclangdバイナリが以下のパスに配置される。

third_party/llvm-build/Release+Asserts/bin/clangd

ただし注意点がひとつある。この同梱版clangdはgRPCサポートなしでビルドされている。つまりリモートインデックスが使えない。

方法2: ソースからビルドする

Chromiumのリポジトリに含まれている build_clang_tools_extra.py を使う。

tools/clang/scripts/build_clang_tools_extra.py --fetch out/Default clangd

ビルドされたバイナリは以下のパスに出力される

out/Default/tools/clang/third_party/llvm/build/bin/clangd

この方法ならChromiumが参照しているLLVMのリビジョンからビルドされるので、バージョンの不一致が起きない。ただしcmakeが必要なので、入っていなければ先にインストールしておく。

方法3: GitHub公式リリースを使う

clangdプロジェクトがGitHubで配布しているリリースビルドを使う手もある。こちらはgRPC対応でビルドされているので、リモートインデックスも使える。ただしChromiumのClangとバージョンが完全には一致しない可能性がある点には気をつけたい。

compile_commands.json の作成

compile_commands.json とは、 C/C++の各ソースファイルが「どんなコンパイルコマンドでビルドされるか」を一覧にしたデータベースです。clangd は「実際のコンパイル条件」を知ることで、C/C++コードを正しく解釈できるようになります。

cd ${HOME}/chromium/src
tools/clang/scripts/generate_compdb.py -p out/Default chrome > compile_commands.json

ここでポイントになるのが、末尾の chrome という引数です。これはninjaのビルドターゲットを指定します。ターゲットを指定すると、そのターゲットのビルドに必要なソースファイルだけがcompile_commands.jsonに含まれます。ターゲットを省略するとプロジェクト全体が対象になるが、不要なテストやツールのコンパイルコマンドまで入ってしまい、インデックスの生成時間が伸びます。自分が作業するターゲットに絞るのが良い。

ポイント
compile_commands.json は自動更新されない。gclient sync で新しいファイルが増えたり、ビルドルールが変わったりしたら、手動で再生成する必要がある。
ちなみにChromiumの場合、chromeターゲットだけでもこのファイルは500MB超になる。中には7万以上のコンパイルコマンドが入っている。

Chromiumのドキュメントによると、Windows PowerShellの場合はエンコーディングに注意。UTF-8を明示しないとclangdがYAMLパースエラーを出すようです。

tools/clang/scripts/generate_compdb.py -p out/Default chrome | out-file -encoding utf8 compile_commands.json

clangd-indexer で静的インデックスを作る

ここからが本題です。clangd-indexer は、compile_commands.json を元にプロジェクト全体を解析して、1つのインデックスファイルを生成するバッチツールです。ヘッダファイルに含まれるシンボル(関数、クラス、型など)を収集します。

clangd-indexer を用意する

clangd-indexerは多くのLinuxディストリビューションの clang-tools パッケージには含まれていない。

clangdのGitHubリリースページに clangd_indexing_tools-linux-XX.X.X.zip という配布物があり、ここにclangd-indexerが同梱されている。ただし Chromium に対して使うと、Clang のバージョン不一致に起因するエラーが出ることがある。Chromium は独自に管理している Clang バージョンに強く依存しているので、GitHubリリース版のc langd-indexer はおすすめしない。

Chromium で使う場合は、clangdと同じく Chromium に同梱されている build_clang_tools_extra.py でビルドする。clangdをビルドしたときと同じ要領で、ターゲットを clangd-indexer に変えれば良い。

tools/clang/scripts/build_clang_tools_extra.py --fetch out/Default clangd-indexer

ビルドが完了すると以下のパスにバイナリが生成される。

out/Default/tools/clang/third_party/llvm/build/bin/clangd-indexer

clangd も同じスクリプトでビルドしているなら、同じディレクトリに clangd とc langd-indexer が並ぶことになる。バージョンが揃っているのでそのまま組み合わせて使える。

インデックスファイルの生成

compile_commands.json が Chromium のソースルートにある状態で以下のコマンドを実行する。

out/Default/tools/clang/third_party/llvm/build/bin/clangd-indexer \
  --extra-arg=-resource-dir=${HOME}/chromium/src/third_party/llvm-build/Release+Asserts/lib/clang/23 \
  --executor=all-TUs \
  compile_commands.json > index.idx 2>indexer.log
フラグ / 記法 意味 なぜ必要か(Chromium文脈) 注意点
--executor=all-TUs compile_commands.json に含まれる すべての Translation Unit(翻訳単位)を処理する Chromiumのような巨大プロジェクトでは、部分的ではなく全体インデックスを作るため必須 デフォルトだと一部しか処理されない場合がある
--extra-arg=-resource-dir=... Clang に対して resource directory(組み込みヘッダの場所)を明示指定 Chromium付属Clangと、clangd-indexer をビルドしたClangでパスがズレるため、基本ヘッダ(stddef.h等)が見つからなくなるのを防ぐ Clangのバージョンに依存(例: .../clang/23

この処理にはかなりの時間がかかる
Chromium公式ドキュメントによると、48コア64GBのマシンでバックグラウンドインデックスに2〜3時間かかると書かれている。clangd-indexerはそれと同等かそれ以上の時間がかかる可能性がある。今回の 24 論理 CPU の Core i7-13700 環境でも、実測で約 4 時間 40 分を要した。夜中に回してコーヒーでも淹れて待とう。

実測: 出力されるファイルの規模

実際にChromiumで試した結果を載せておく。数字を見ればこの作業のスケール感がつかめると思う。

検証に使ったソースツリーのバージョンは以下のとおり。

$ cat chrome/VERSION
MAJOR=148
MINOR=0
BUILD=7735
PATCH=0

$ git log -1 --format='%H %cd %s'
f57c7ddf6440bdc56ed507089e6abba9b32cf627 Sat Mar 14 05:56:24 2026 -0700 Roll Chrome Win ARM64 PGO Profile

Chromium 148.0.7735.0、2026年3月14日時点のHEADだ。

まず compile_commands.json の中身だが、収録されているTranslation Unitの数はこれだけある

$ jq 'length' compile_commands.json
73193

73,193ファイル分のコンパイルコマンドだ。このJSON自体のサイズも相当なもので、約521MB になる。

$ ls -l compile_commands.json
-rw-rw-r-- 1 user user 546547907 Mar 17 16:03 compile_commands.json

そしてclangd-indexerが生成したインデックスファイルがこちら

$ ls -l index.idx
-rw-rw-r-- 1 user user 1622074604 Mar 17 20:43 index.idx

約1.5GB だ。compile_commands.jsonの3倍近いサイズになった。7万以上のTUから抽出されたシンボル情報がぎっしり詰まっているわけだから、この大きさも納得はできる。

このインデックスをclangdに読み込ませたときのプロセスの状態がこれだ。

$ ps aux | grep clangd
user   2512911  0.6 24.9 62355156 32785808 ?  Sl  Mar19  8:32 clangd ...

RSS(実メモリ使用量)が約31.3GB。仮想メモリは約59.5GBを確保している。Chromium公式ドキュメントには「フルインデックスで約2.7GBのメモリ」と書かれているが、あれはバックグラウンドインデックスの話だ。clangd-indexerで作った静的インデックスをまるごと読み込むと、桁が1つ違うメモリが必要になる。128GBクラスのマシンで作業することを前提にしたほうがいい。

静的インデックスをclangdに読み込ませる

生成したインデックスファイルは、clangdの起動フラグで指定する。実測環境で使っていたフラグの全体像はこうなっている。

clangd \
  --index-file=${HOME}/chromium/src/index.idx \
  --compile-commands-dir=${HOME}/chromium/src \
  --background-index=false \
  --limit-references=1000 \
  --limit-results=1000 \
  --pch-storage=memory \
  --clang-tidy=false \
  --log=error \
  --malloc-trim
フラグ 意味
--limit-references=1000 参照検索の結果数を1000件に制限し、結果過多によるエディタのフリーズを防ぐ
--limit-results=1000 補完候補などの結果数を1000件に制限し、応答性を維持する
--pch-storage=memory プリコンパイルヘッダをメモリに保持し、ディスクI/Oを減らして高速化する(メモリ消費は増える)
--clang-tidy=false clang-tidyによるリアルタイム静的解析を無効化し、負荷を軽減する
--malloc-trim 未使用メモリをOSに返却しやすくし、長時間動作時のメモリ肥大を抑える

これらのフラグをVSCodeやNeovimなどのエディタ側でどう渡すかは、各エディタのclangd拡張のドキュメントを参照してほしい。

ほかのインデックス手段について

この記事では clangd-indexer による静的インデックスの作成に絞って書いたが、clangdにはほかにもインデックスの選択肢がある。

リモートインデックスサーバー
clangd-indexerで生成したインデックスファイルをclangd-index-serverで配信するしくみで、mainブランチ以外のインデックスが必要な場合やチームで共有したい場合に使える。詳しくは clangd remote-index 設計ドキュメント に書かれている。

まとめ

Chromiumほどの巨大プロジェクト(compile_commands.json:73,193エントリ、521MB)で clangd を使おうとすると、バックグラウンドインデックスだけでは時間が掛かる。

clangd-indexerを使えば、事前にインデックスファイルを作っておいてclangd起動時に読み込ませることができる。生成されるインデックスは1.5GB級、読み込み時のメモリは31GB級と重量はあるが、一度作ればバックグラウンドインデックスを無効にできて、clangdの起動直後から全シンボルが検索可能になる。

ポイントは、Chromiumが参照しているLLVMリビジョンに合わせたclangd-indexerを使うこと。GitHubリリース版ではバージョン不一致でエラーが出る。そしてcompile_commands.jsonの生成は忘れずに行うこと。ビルドルールが変わるたびに再生成しなければならない。

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?