Bear-全能的代码索引工具

0 序言: LSP

现代 C/C++ 开发中,代码补全、跳转定义等功能是提升开发效率的关键, 今年来的IDE 都支持 LSP 来实现这些功能,而 clangd 是目前最流行的 C/C++ 语言服务器。

如果你使用过像 clangd、ccls 或 cquery 这样的语言服务器(LSP),你一定对compile_commands.json这个文件不陌生——它记录了每个源文件是如何被编译的(包括头文件路径、宏定义、编译选项等), 例如:

1
2
3
4
5
6
7
[
{
"directory": "/home/user/myproject",
"command": "gcc -I./include -DDEBUG -c src/main.c -o src/main.o",
"file": "src/main.c"
}
]

通常, 现代化项目的编译脚本都会支持 compile_commands.json 文件的生成,但对于那些没有原生生成 compile_commands.json 能力的构建系统(比如 Makefile、CMake 未启用特定选项、或者手写的构建脚本),我们该怎么办?

这时,Bear 就派上用场了。

1 Bear简介

Bear(Build EAR)是一个命令行工具,通过拦截构建过程中对编译器(如 gccclang)的调用,记录完整的编译命令,并将其转换为标准的 compile_commands.json 文件。

具体来说, Bear 并不解析你的 Makefile 或构建脚本,而是利用 动态链接库预加载(LD_PRELOAD,在 Linux/macOS 上)或 进程监控 技术,在你运行构建命令(如 make)时:

  1. 劫持(intercept)所有子进程中对编译器(如 gcc、g++、clang 等)的调用;
  2. 记录每个源文件对应的完整命令行、工作目录和环境;
  3. 构建完成后,将这些信息整理成符合 Clang Compilation Database 格式的 JSON 文件——即 compile_commands.json

后面我们会结合具体的例子来说明他的原理。

Bear的安装参考仓库主页: https://github.com/rizsotto/Bear

2 Bear 的使用

2.1 基础使用

Bear 的使用非常简单, 只需要在你正常构建的脚本前加上bear --即可。例如, 对于一个使用 Makefile 的项目:

1
bear -- make

对这个命令来说, Bear 实际做了:

  • 启动 make
  • make 调用命令时拦截并记录

假设你的make执行时有gcc -I./include -DDEBUG -c src/main.c -o src/main.o 这个命令,Bear 会捕获到这个调用,并记录下来, 其对应compile_commands.json 中的条目如下:

1
2
3
4
5
{
"directory": "/path/to/project",
"command": "gcc -I./include -DDEBUG -c foo.c -o foo.o",
"file": "foo.c"
}

需要说明的是, Bear 并非仅仅用于make, 它基本上支持所有的构建系统:

1
2
3
4
bear -- ninja
bear -- cmake --build .
bear -- ./build.sh
bear -- ./build.py

2.2 高级选项

  • –output FILE:指定输出文件名(默认为 compile_commands.json
  • –append:追加到已有的 compile_commands.json
  • –verbose:显示详细日志

实际效果:配合 clangd 使用

一旦生成了 compile_commands.json,只需将其放在项目根目录,clangd 就会自动读取。此时,在 VS Code(安装 clangd 插件)、Neovim(配置较为复杂…)等编辑器中,你将获得:

  • 精准的代码跳转(Go to Definition)
  • 智能补全(包括宏和模板)
  • 实时错误诊断
  • 查找所有引用(Find References)

这大大提升了阅读大型 C/C++ 项目(如 Linux 内核、Postgresql 等)的体验。