# 告别卡顿：在大型Bazel C++项目中为VS Code注入强劲动力

> 讲解 Bazel 项目生成 compile_commands.json 并接入 clangd 的实用流程。

作者：[汤国放](https://tonwork.fun/#person)
发布日期：2024-09-01
原文：https://tonwork.fun/blog/bazel-%E7%94%9F%E6%88%90-compile_commands-json-%E7%AE%80%E6%98%93%E7%89%88%E6%95%99%E7%A8%8B/

---

如果你正在使用VS Code开发一个庞大的、基于Bazel的C++项目，你很可能正忍受着缓慢甚至错误的代码提示（IntelliSense）。这是因为VS Code默认情况下难以理解Bazel复杂的构建系统。

别担心，这个问题可以被完美解决。本指南将带你一步步配置，最终实现如丝般顺滑、精准无比的代码开发体验。我们的核心武器是两个：`clangd` 和它赖以生存的“地图”—— `compile_commands.json`。

#### 核心思路

我们的策略很简单：

1. **利用工具**：使用 `hedron/bazel-compile-commands-extractor` 这个出色的工具，让它“审问”Bazel，生成一份详细的编译指南 `compile_commands.json`。

3. **路径映射**：由于我们的编译环境（Docker）和编辑环境（宿主机）分离，我们需要告诉 `clangd` 如何“翻译”文件路径。

5. **配置VS Code**：让VS Code放弃自带的引擎，全面拥抱更高效的 `clangd`。

* * *

### 第一步：准备“蓝图” (`WORKSPACE`与`BUILD`)

首先，我们需要告诉Bazel我们的新工具从哪里来，以及我们要用它做什么。

1. **在 `WORKSPACE` 文件中：引入供应商** 在你的项目根目录的 `WORKSPACE` 文件中，添加以下代码。这会告诉Bazel如何下载并设置好 `hedron` 工具及其所有依赖。 `# In /qcraft/WORKSPACE http_archive( name = "hedron_compile_commands", sha256 = "2317f0b51f03672a7d41a72171ea95146c2658828b6d88c871db68a514d183f2", # 建议使用最新版本的校验和 strip_prefix = "bazel-compile-commands-extractor-0.7.0", urls = ["https://github.com/hedronvision/bazel-compile-commands-extractor/archive/refs/tags/0.7.0.zip"], ) load("@hedron_compile_commands//:workspace_setup.bzl", "hedron_compile_commands_setup") hedron_compile_commands_setup() load("@hedron_compile_commands//:workspace_setup_transitive.bzl", "hedron_compile_commands_setup_transitive") hedron_compile_commands_setup_transitive() # ... 如果需要，可以继续添加更深层次的 transitive setup ...`

3. **在根 `BUILD` 文件中：下达生产指令** 在你的项目根目录的 `BUILD` 文件（`/qcraft/BUILD`）中，定义一个具体的任务目标。最佳实践是为你常工作的模块创建专门的目标，这样可以极大地提升生成速度。 `# In /qcraft/BUILD load("@hedron_compile_commands//:refresh_compile_commands.bzl", "refresh_compile_commands") # 为Perception模块创建一个专门的刷新命令 refresh_compile_commands( name = "refresh_perception_commands", targets = [ "//onboard/perception/...", # 只分析perception模块及其子目录 ], # 推荐：排除外部依赖，大幅提速 exclude_external_sources = True, ) # 你也可以为其他模块创建，或创建一个分析全部内容的目标 # refresh_compile_commands( # name = "refresh_all_commands", # targets = ["@//..."], # )`

* * *

### 第二步：在Docker中生成`compile_commands.json`

现在，进入你的Docker容器，运行你刚刚定义的目标。

```
# 在Docker容器内部执行
cd /qcraft
bazel run //:refresh_perception_commands
```

命令成功后，你会在项目根目录看到一个新生成的文件：`compile_commands.json`。

* * *

### 第三步：连接Docker与宿主机——路径映射的魔法

直接在宿主机使用这份JSON文件是行不通的，因为它里面的路径都是Docker内部的路径（如 `/qcraft/...`）。我们需要为 `clangd` 配备一个“翻译官”。

在**宿主机**上，在你项目的根目录（与`compile_commands.json`同级）创建一个名为 `.clangd` 的文件，并写入以下内容：

```
# .clangd

CompileFlags:
  # 告诉clangd如何翻译路径
  PathMappings:
    # 格式：- '/docker/中的项目绝对路径=/宿主机/上的项目绝对路径'
    # 请务必替换成你自己的真实路径！
    - '/qcraft=/home/your_user/your_project_folder'
```

- **如何获取路径？**
    - Docker路径：在容器的项目根目录执行 `pwd`。
    
    - 宿主机路径：在宿主机的项目根目录执行 `pwd`。

* * *

### 第四步：配置VS Code，迎接新生

这是最后的临门一脚。

1. **安装扩展**：在VS Code中安装官方的 `clangd` 扩展。

3. **禁用冲突**：强烈建议禁用微软官方 `C/C++` 扩展的IntelliSense引擎，以避免冲突。在VS Code设置中搜索 `C_Cpp.intelliSenseEngine` 并将其设为 `Disabled`。

5. **重启服务**：用VS Code打开你的项目根目录。按 `Ctrl+Shift+P` 打开命令面板，运行 `clangd: Restart language server` 来确保它加载了最新的配置。

* * *

### 总结

恭喜！你已经完成了所有的配置。现在，打开任何一个C++文件，你将体验到由 `clangd` 带来的快速、精准的代码补全、跳转和错误诊断。你可以专注于代码本身，而不是等待工具响应。这套流程将彻底改变你在大型Bazel项目中的开发效率。

在使用 Bazel 构建项目时，生成 `compile_commands.json` 文件对于集成一些代码分析工具（如 `clang-tidy` 或 IDE）非常有用。以下是如何使用 `bazel-compdb` 生成 `compile_commands.json` 文件的步骤。

### 环境准备

首先，你需要安装 `bazel-compdb` 工具。你可以通过以下脚本来下载并安装 `bazel-compdb`：

```
INSTALL_DIR="/usr/local/bin"VERSION="0.5.2"# 下载并创建符号链接(  cd "${INSTALL_DIR}" \  && curl -L "https://github.com/grailbio/bazel-compilation-database/archive/${VERSION}.tar.gz" | sudo tar -xz \  && sudo ln -f -s "${INSTALL_DIR}/bazel-compilation-database-${VERSION}/generate.py" bazel-compdb)
```

这将会在你的指定目录下创建一个名为 `bazel-compdb` 的可执行文件。

### 使用 `bazel-compdb` 生成 `compile_commands.json`

执行以下命令生成 `compile_commands.json` 文件：

```
bazel-compdb
```

这将在工作区的根目录下生成 `compile_commands.json` 文件。

如果你需要传递其他 Bazel 参数，可以使用以下命令格式：

```
bazel-compdb -- [additional flags for bazel]
```

例如，如果你想让 Bazel 在生成数据库时使用多线程构建，可以这样做：

```
bazel-compdb -- --jobs=4
```

你还可以根据需要使用以下选项来定制 `bazel-compdb` 的行为：

- **使用源目录**：如果你希望使用源目录而不是 Bazel 执行根目录作为 Clang 命令运行的目录，可以使用 `-s` 选项：`bazel-compdb -s`

- **指定目标查询模式**：你可以使用 `-q` 选项来限制生成的编译命令数据库只包含特定的目标，例如：`bazel-compdb -q //cc/...`

### 常见问题与解决

**问题 1**：当在 `cc_library` 中使用 `..`（上级目录引用）时，可能会出现如下错误：

```
invalid label '../xxxc.h' in element 0 of attribute 'hdrs' in 'cc_library' rule: invalid target name '../xxx.h': target names may not contain up-level references '..'
```

**原因**：Bazel 对路径引用有严格的规范，禁止使用上级目录引用（`..`）以确保构建规则的可维护性。

**解决方法**：

- 使用 Bazel 的绝对路径（从工作区根目录开始），例如：`cc_library( name = "type_basic", hdrs = [ "//xxx.h", ], )`

- 通过调整项目结构或创建新的 `cc_library` 来组织代码，避免使用 `..`。

**问题 2**：`Build did NOT complete successfully` 提示头文件缺失。

**解决方法**：确保所有头文件在正确的位置，并且在 BUILD 文件中被正确引用。你可以通过移动文件或调整引用来修复此问题。

### 总结

通过以上步骤，你应该能够成功使用 `bazel-compdb` 生成 `compile_commands.json` 文件，并解决一些常见问题。`Bazel` 的灵活性和强大构建系统能够很好地支持大型项目的开发，而 `compile_commands.json` 的生成能够帮助你在开发中更好地利用现代工具进行代码分析和调试。
