%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
%%
# 关于 clang
### clang 功能及所用编译器
**在 Windows 系统中 clang 指令只是负责编译的前端工作,即识别编译指令与提供报错提示**,并**不负责具体的代码编译工作**,所以在 windows 下只安装了 llvm 是不够的,还需要**有实际的编译链接库**,例如**安装 MinGW 或者 MSVC 来获取需要的运行库**。[^9]
**在 Windows 上 clang(官方发行的 windows 版 clang)默认使用 MSVC 编译器工具链来进行编译**,所以查找系统头文件时都是在 MSVC 的路径下去找。
> windows 上通过 `clang -v` 查看默认 target:
> 
> windows 上通过 `clang -v -c -xc++ nul` 查看具体的系统库头文件搜索路径:
> 
> 可见,**默认使用 MSVC 编译工具链,系统库文件搜索路径为 MSVC 下的路径**。
>
> 如果加上 `--target` 选项指定使用 MinGW 编译工具链,使用命令:`clang --target=x86_x64-pc-windows-gnu -v -c -xc++ nul`,则显示信息如下:
> 
> 可见,系统库文件搜索路径变为了 MinGW 下的路径。
可通过 `--target` 选项指定 clang 的编译器后端。
windows 上 MSCV 与 MinGW 对应的 `target` 名称如下:
- **windows 下 32 位 clang**一般默认是**i686-pc-windows-msvc**
- **windows 下 64 位 clang**一般默认是**x86_x64-pc-windows-msvc**
- **MinGW-32**对应 clang 中的 target 是**i686-pc-windows-gnu**
- **MinGW-64**对应 clang 中的 target 是**x86_64-pc-windows-gnu**
```shell
# 默认gcc风格时,可以是如下两种形式
clang -target x86_x64-pc-windows-msvc
clang --target=x86_x64-pc-windows-msvc
# 如果使用了clang-cl或者指定--driver-mode=cl,则只能使用下面形式
clang-cl --target=x86_x64-pc-windows-msvc
clang --driver-mode=cl --target=x86_x64-pc-windows-msvc
```
<br><br>
## clang 编译命令
**默认情况下 clang 的基本编译指令参数与 gcc 是一样的**,所以按照 gcc 的用法来使用 clang 即可,也可以显式指定 clang 的指令风格为 gcc 风格:`--driver-mode=gcc`。
要用 msvc 风格的编译参数,需要使用 `--driver-mode=cl` 参数,或者使用 `clang-cl` 指令。
示例:
```shell
# 注:
# clang 等价于 clang --driver-mode=gcc
# clang++等价于 clang --driver-mode=g++
# 默认按 gcc 风格来给定编译参数
clang -Wall -g3 -o main. exe main. c
clang -Wall -g3 -o main. exe main. cpp
clang++ -Wall -g3 -o main. exe main. cpp
# 显式说明以 gcc 风格来给定编译参数
clang --driver-mode=gcc -Wall -g3 -o main. exe main. c
clang --driver-mode=gcc -Wall -g3 -o main. exe main. cpp
clang --driver-mode=g++ -Wall -g3 -o main. exe main. cpp
# 按 msvc 风格来给定编译参数
clang --driver-mode=cl /W3 /Zi /EHsc /MDd /Fe: main. exe main. c
# 按 msvc 风格来给定编译参数
clang-cl /W3 /Zi /EHsc /MDd /Fe: main. exe main. c
# clang-cl 等价于 clang --driver-mode=cl
```
注意:
- 因为**clang 使用的默认工具链是 msvc**,所以**编译 cpp、c 时,不需要区分是使用 clang 还是 clang++**,同时,`-o` 后面如果不指定是 `.exe` 后缀文件,编译出来的可执行文件不会自动添加 `.exe` 后缀;
- 如果**用 target 参数指定使用 MinGW 工具链**,编译 cpp、c 时,**必须区分是 clang 还是 clang++**,同时,`-o` 后面指定文件时不用特意加上 `.exe` 后缀,会自动生成 `.exe` 后缀的可执行文件。
<br><br>
# 关于 Clangd
clangd 是一个基于 LLVM/Clang 的 C++**语言服务器**。
clangd 用于**为 C/C++ 语言提供智能编码辅助功能**,如代码自动补全、符号定义跳转、悬停信息、诊断(错误和警告)、代码格式化等,这些功能都是通过**解析源代码**实现的。
clangd **使用 Clang 前端(Clang 的 C/C++解析器)来解析源代码**,其**所有功能(提示、报错、建议等)都是基于 ==clang 的解析器==**,
因此其报告的**错误或警告信息**都与 **clang 编译器生成的错误或警告信息完全相同**(就像源文件通过命令 `clang some_file.cc` 进行编译一样)。
此外,clangd **支持大多数 ==clang-tidy 检查==**(可通过 `.clang-tidy` 文件设置)
> [!NOTE] Clangd 是独立于 Clang 的工具,但它**依赖于 Clang 的库**来解析和理解 C/C++ 代码。
>
> 通常情况下在安装 Clangd 时会**包含或自动安装所需的 Clang 库**。即使没有显式安装 Clang 编译器本身,只要 Clangd 能访问这些库其就可以正常工作。
> [!info] **语言服务器协议 LSP**
>
> LSP(Language Server Protocol)是开源的语言服务器协定,由红帽、微软和 Codenvy 联合推出,定义了在 **编辑器 (如 VSCode) 与语言服务器 (如 clangd)** 之间使用的协议,
> 可以让不同的代码编辑器与集成开发环境(IDE)方便嵌入各种程序语言。
>
> 编辑器通过 LSP 将代码传给**语言服务器**做语义分析,语言服务器再通过 LSP 传给编辑器做**渲染解析或者异常提示**之类的。
>
> C++的 **语言服务器**(Language Server)的具体实现主要有如下几个[^1],其中就包含了 VSCode 中 C/C++官方插件的实现,以及更受好评的 clangd。
>
> 
>
>
<br><br>
# clangd 工作原理
clangd 依赖于 **`compile_commands.json` 文件**(也称为 **compilation database**,**==CDB==**):
- clangd 通过该文件来**了解项目的==编译上下文和配置==**(**编译选项、编译标志,如预处理器定义、包含目录等**),
从而**基于这些编译选项正确地解析代码**,理解类、函数、变量等的定义和使用,进而提供准确的代码分析,包括**语法和语义错误的检测、代码补全建议**等。
- clangd 会扫描该文件并**为当前项目中的源码生成索引**,从而提供**代码跳转、头文件包含识别**等功能。
> [!NOTE] clangd 可以**独立于实际用于构建项目的编译器**工作
>
> 无论项目**实际使用哪种编译器进行编译**(例如 gcc/g++),clangd 都可以提供代码分析和智能提示等功能,
> 只需确保 `compile_commands.json` 文件存在即可,以便 clangd 可以了解到项目中每个文件是如何具体编译的。
> [!caution] clangd 的报告信息来自于 "clang" 的解析器
>
> clangd **基于 clang 的 C/C++解析器**,因此其给出的**所有错误或警告信息都来源于 clang 的解析器** (仿佛源文件是通过 clang 编译的),
> 而**与项目实际使用的编译器无关**(即**与 `compile_commands.json` 文件中编译命令 `command` 里使用的具体编译器是什么无关**)[^3]
> [!caution] 需要为 clangd 指定系统库文件的搜索路径
>
> 由于 clangd 假设的是源文件使用 `clang` 进行编译,因此其**对于==系统库文件的搜索路径==可能默认是 `clang` 编译器相关的路径**。
> 这会导致在代码编辑器页面中,**代码里对系统库头文件的引用被 clangd 报错**,以及在提供系统库头文件跳转功能时,跳转到 `clang` 编译器的系统库文件实现。
>
> 因此需要**根据项目实际使用的编译器,为 clangd 指定系统库文件的搜索路径**。
> [!important] 需要为 clangd 指定编译器驱动程序
>
> 尽管 `clangd` 的错误和警告信息来源于 Clang 前端,但为了准确解析和理解代码,**`clangd` 需要模拟实际的编译环境**,而不同的编译器标志可能会显著影响代码的解析和理解(例如通过宏定义条件编译的部分代码),因此 clangd 需要**就 `compile_commands.json` 中编译命令涉及的编译选项/编译器标志==向对应的编译器驱动程序进行查询==**,从而获得有关**如何编译特定文件**的信息(例如预处理器标志、包含路径特别是编译器对应的系统库文件实现的所在路径、宏定义等),进而让 clangd 内部的**clang 前端尽可能以与实际编译过程相似的方式解析代码**,从而提供更准确的代码分析和诊断信息。
>
> 为此,**需要通过 clangd 命令的 `--query-driver` 选项**为 clangd 指定**可用的编译器驱动程序**(如 `gcc`、`g++`、`clang` 等,特别是在使用 clang 以外的编译器工具链如 gcc/g++时),
> 例如:`--query-driver=/usr/bin/**/clang-*, /path/to/repo/**/g++-*`)
>
<br>
### 关于 `compile_commands.json` 文件
> `compile_commands.json` 文件也称为 "**compilation database**,**CDB**",参见[^2]
`compile.commands.json` 文件通常**由构建系统自动生成**,其中包含了构建系统**对项目中每个文件在编译时所使用的==精确编译命令==**。
> [!quote] The purpose of `compile_commands.json` is to provide clangd with include paths and other compiler flags (such as `-D SYMBOL`) needed to the files in the project.
> [!example] CMake 中由 `CMAKE_EXPORT_COMPILE_COMMANDS` 变量控制是否在构建目录下生成 `compile_commands.json` 文件。
**clangd 会在当前打开文件的==所有父级目录(祖先目录)==中,以及==所有父级目录的 `build/` 子目录==中搜索该文件** [^5]。
例如,当前打开编辑的文件为 `$SRC/gui/window.cpp`,则 clangd 会在以下路径查找该文件:`$SRC/gui/`,`$SRC/gui/build/`,`$SRC/`,`$SRC/build/`。
如果缺少该文件,`clangd` 可能会退回到**使用默认的编译设置**,这可能导致功能受限——代码解析错误或不完整,代码补全和诊断可能不准确或不可用。
> [!example] `compile_commands.json` 文件内容示例:
>
> ```json
> [
> {
> "directory": "F:/cpp_project/vscode_cmake_demo/build",
> "command": "D:\\PROGRA~1\\MinGW\\mingw64\\bin\\G__~1. EXE -g -o CMakeFiles\\MyExec. dir\\main. cpp. obj -c F:\\cpp_project\\vscode_cmake_demo\\main. cpp",
> "file": "F:/cpp_project/vscode_cmake_demo/main. cpp",
> "output": "CMakeFiles/MyExec. dir/main. cpp. obj"
> }
> ]
> ```
>
<br>
### 关于 `compile_flags.txt` 文件
clang 工具也会识别**源文件目录下的`compile_flags.txt` 文件**,该文件中**每行指定一个编译标志**,**这些编译标志将在编译该文件所在目录下的任一源文件时被使用**。
这一文件**会影响 clang 所用的编译标志**,进而影响 clangd 的输出结果。
> [!caution] 仅当 `compile_commands.json` 不存在时,clangd 才会采用该文件中指定的编译标志,否则该文件将被 clangd 忽略
> [!example] `compile_flags.txt` 文件内容示例
>
> ```c++
> -xc++
> -I
> libwidget/include/
> ```
>
> 这里 `-I libwidget/include ` 是两个参数,所以需要按两行分开放。
>
> 注:编译标志中的**相对路径**是相对于该 `compile_flags.txt` 所在目录。
<br><br><br>
# clangd 的系统库头文件搜索路径
clangd 官方文档中介绍了 clangd 查找系统库头文件的几种方式[^6] [^8]:
- 方式 1:"**Search directories mentioned with compile flags**"
- 方式 2:"**Heuristic search for system headers**"
- 方式 2.2 "**Directory of the driver**"
- 方式 2.3 "**Target Triple**"
- 方式 3:"**Query-driver**"
### 方式说明
#### 方式 1:Search directions mentioned with compile flags.
1. **通过 `-isystem` 编译标志**指定的系统库包含目录搜索路径。
2. 或通过 clang 特有的 `-isysroot`,`-system-header-prefix` 编译标志及环境变量指定的搜索路径。
#### 方式 2.1:Directory of the Driver
clangd 会使用编译标志的第一个参数 `argv[0]`作为**编译器驱动**的路径。 (`argv[0]` 即 `compile_commands.json` 文件中 `commands` 字段的第一项,表示**所使用的编译器的绝对路径**)
确定编译器后,**clangd 会在该编译器对应的 "系统库头文件" 路径下进行搜索**。
#### 方式 2.2:Target Triple
通过 `--target` 编译标志指定目标后,clangd 会**根据指定的目标平台/系统查找对应的系统库头文件**。
例如设置 `--target=x86_64-w64-mingw32` ,则 clang 会查找 MinGW 实现的系统库文件。
> [!NOTE] 可通过 `clang --target=x86_64-w64-mingw32 -xc++ -v -c /dev/null` 命令查看在指定 `--target` 后的具体的头文件搜索目录,并与未指定 `--target` 时的命令进行对比。
#### 方式 3:Query-driver
若 `compile_commands.json` 文件中**编译命令所用的编译器** 匹配 **clangd 命令选项 `--query-driver` 中罗列的某项编译器**,
则 clangd 将执行类似于 `/custom/compiler -E -xc++ -v /dev/null` 的命令**向对应编译器进行查询,并解析编译器给出的输出,从而获取对应的系统库目录**。
> [!caution] ==该方式只在 clang 的启发式搜索 ("Heuristic search",即上文所述的方式 2.1 与 2.2) 没有检测到系统库路径时才会被作用==。
> [!example] `--query-driver` 选项使用示例
>
> `clangd --query-driver="/path/to/my/project/arm-g*,/path/to/other/project/gcc"`
> [!quote] 关于 clangd 的 `--query-drive` 选项的作用:
>
> The purpose of `--query-driver` is to **allow clangd to query another compiler driver** for its **built-in include paths**.
> This is only necessary in cases where **the build compiler uses different built-in include paths than clang (specifically, the clang from the same package as clangd) does**.
>
> `--query-driver` option **on itself just specifies an allowed list of drivers to execute**.
> **The real driver paths are deduced from compile commands**.
>
> `--query-driver` **requires** `compile_commands.json`, and the **argument needs to match (more specifically, it needs to be a glob which accepts) the compiler from `compile_commands.json`**.
要让 clangd 查询不同编译器的内置包含路径,需要将 `--query-driver` 与 `compile_commands.json` 一起使用 [^7] :
| `compile_commands.json` 文件存在 | 匹配 `--query_driver` 中所列的某项编译器 | 效果 |
| ---------------------------- | ----------------------------- | ------------------------------------------------------------------ |
| ✔️ | ✔️<br> | clangd **使用该文件中的编译标志** 并**向所匹配的编译器查询其系统库目录** |
| ✔️ | ❌ | clangd **使用该文件中的编译标志**,但**使用 clangd 内置的 clang 前端的 `include path`** |
| ❌ | ❌ | clangd **使用 clangd 内置 clang 前端的 `include path`** |
## 配置说明
> [!NOTE] 为 `clangd` 指定系统库文件搜索目录,建议通过编译标志 `--target` 指定,或者通过 `-isystem` 指定。
![[05-工具/GNU 工具/clangd 工具#^swj73r]]
> [!caution] 配置 `--query-driver` 选项不生效的原因
>
> 在我的电脑上为 clangd 配置 `--query-driver` 选项指定编译器为 gcc/g++不生效,
>
> **原因在于 clang 的 "Heuristic search" 已经找到了系统库搜索路径(但由于 Windows 下的 clang 默认 target 是 `x86_64-pc-windows-msvc`,所以系统库搜索路径是在 MSCV 的路径下)**,
>
> 所以**根本没有启用 "Query-driver" 的这一方法**,因此即使 `compile_commands.json` 中的编译命令里使用的是 gcc 并通过 `--query-driver` 指定编译器 gcc 也没有 work,仍然是在 MSVC 路径下进行查找。
>
> 如下图所示,指定了 `--query-driver` 也没有作用。
>
> 
>
> 生成的 `compile_commands.json` 中的编译器命令没有问题,
>
> 
>
> 直接使用上述编译器命令,运行结果如下:
>
> 
>
> `compile_commands.json` 里**编译命令的头文件查找路径确实是在 MinGW 的路径下**。
>
> 这说明即使 `compile_commands.json` 中的编译命令里使用的是 gcc,并且通过 `--query-driver` 配置了编译器 gcc,clangd 也并**没有为该文件而特别地去向 gcc 驱动程序查询系统库文件路径**,
> 而是 clang 前端直接使用了默认的 `target` 即 MSVC,从而在 MSVC 的路径下查找系统库文件。
>
>
<br><br><br>
# clangd 配置自定义头文件搜索路径
参见[^10],有以下两种方式,
![[_attachment/05-工具/GNU 工具/clangd 工具.assets/IMG-clangd 工具-0358035B607E9832AB9478FD10CB2625.png|596]]
1. **通过 `--include` 指定引入某个具体的头文件**:例如 `--include=/headers/file.h`;
2. **通过 `-I` 指定头文件搜索路径**,例如 `-I/path/heads`
> [!caution] 关于 `-I` 指定的路径
>
> `CompileFlags` 是针对各个源文件的 "**编译标志**",因此如果使用 **==相对路径==**,将会是 "**==相对于当前打开的文件==**"(而不是相对于 `.clangd` 文件)
>
> 因此,在需要指定某一固定路径时,必须使用 "**==绝对路径==**"
<br><br>
# clangd 运行命令
> [!tip]
> - 通过 `clangd --help` 命令可查看"可用的"具体参数:
> - 通过 `clangd --help-list-hidden` 命令可查看所有命令选项,包括已弃用的("Obsolete flag, ignored")。
**编译标志选项**:
- `--compile-commands-dir` :指定查找 `compile_commands.json` 的路径(相对于工作区的路径)未指定时,cliangd 会查找**源文件所在目录**及其**所有父目录/祖先目录**。
- `--query-driver=<string>`:可安全执行的**gcc 兼容驱动程序**的**通配符列表**,以逗号分隔。该选项用于指定哪些**编译器驱动程序**(drivers,如 `gcc`、`g++`、`clang` 等)可以被 clangd 安全地调用来查询编译命令中的**编译选项**/**编译器标志**,从而获取有关**如何编译特定文件**的信息。同时,指定的编译器驱动程序也可用于提取**系统包含目录**。
**功能选项**:
- `--all-scopes-completion`:全局补全 <br>为 `true` 时,代码补全将能够索引当前作用域中不可见的符号(例如其它命名空间)。 <br>例如输入时弹出的建议将会提供 CMakeLists. txt 里配置的所有文件中可能的符号,会自动补充头文件
- `--background-index`:是否在后台自动建立代码索引 (基于 `complie_commands.json` 文件)索引会保存到磁盘上持久化。
- `--background-index-priority`:后台构建索引的线程的优先级。<br>可选项为:
- `background`: 最低优先级,只在空闲 cpu 上运行构建索引的线程;
- `low`:优先级低于交互式工作
- `normal`:与其它 clangd 作业具有相同的优先级
- `--clang-tidy`:**启用 Clang-tidy** 以提供「静态检查」
- `--completion-style=<value>`:补全风格,可选项为
- `detailed` :每个语义上不同的项都将作为独立的补全项,包含完整的类型信息。
- `bundled` :相似的补全项(例如重载函数)将被合并
- `--fallback-style=<string>`:`.clang-format` 文件不存在时,使用的 clang-format 风格。
- `--function-arg-placeholders`:启用时,函数补全将会为参数提供占位符,键入后按 Tab 可以切换到下一占位符,乃至函数末。
- `--header-insertion=<value>`:接受代码补全时,添加 `#include` 指令。可选值:
- `iwyu`:"Include what you use",为顶层符号**插入其所属的头文件**,除非头文件已经被直接包含或者该符号是前向声明的。
- `never`:不会插入 `#include` 作为代码补全内容的一部分;
- `--header-insertion-decorators`:输入建议,根据 `include line` 是否已经存在,在补全标签前添加圆点以示区分(即区分已包含头文件的项与还未包含头文件的项)
- `--import-insertions`:如果启用了 `header-insertion` 选项,在接受代码补全或修复 Objective-C 代码中的包含时添加 `#import` 指令
- `--limit-references=<int>`:限制 clangd 返回的引用的数量。0 表示没有限制 (默认值=1000)
- `--limit-results=<int>`:限制 clangd 返回的结果数量。0 表示没有限制 (默认值=100)
- `--rename-file-limit=<int>`:限制受符号重命名影响的文件数量。0 表示没有限制 (默认值=50)
**其它选项**:
- `--check[=<string>]`:单独解析一个文件,而不是充当语言服务器。可用于调查/重现崩溃或配置问题
- 通过 `clangd --check=<file> ` 可对单个源文件进行分析(语法和语义检查等),打印**诊断信息**。
- 该命令用于快速检查**单个文件**,检查语法错误,进行语义分析(包括类型检查、符号解析错误等),但不会考虑项目级别的配置,如编译选项、宏定义等。
- `--enable-config`:从 YAML 文件中读取用户和项目配置;
- `-j <unit>` :clangd 使用的后台线程数。后天索引也将使用同样的线程数量
- `--pch-storage=<value>`:存储 PCHs 优化的位置,可选值为 `memory` 或 `disk`;选择 memory 会增加内存开销,但会提升性能)
**clangd 协议和日志选项**:
- `--pretty`:输出的 JSON 文件更美观
- `--log=<value>`:控制写入 stderr 的日志消息的详细程度,可选值为:
- `error`:只包含错误消息
- `info`:高层的执行追踪
- `verbose`:底层细节
- `--offset-encoding=<value>`:强制指定用于字符位置的 offsetEncoding。这会绕过与客户端之间的协议。
- `utf-8`:Offsets are in UTF-8 bytes
- `utf-16`:Offsets are in UTF-16 code units
- `utf-32`:Offsets are in unicode codepoints
- `--path-mappings=<string>`:客户端路径(由远程编辑器所见)与服务器路径(clangd 查看磁盘上文件的地方)之间的映射,值为 `<client_path>=<server_path>` 的形式,多项间以逗号分隔,例如 `/home/project/incl=/opt/include,/home/project=/workarea/project `,首个被匹配上的路径将被使用。
> [!NOTE] 关于 `--query-driver=<string>` 编译标志
> 使用示例:`--query-driver=/usr/bin/**/clang-*, /path/to/repo/**/g++-*`)
>
> 这一选项只是一个 **"allowlist"**,实际被 clangd 询问的编译器取决于 `compile_commands.json` 里的编译命令:
> **当编译命令里的编译器匹配 `--query-driver` 中某一项时**,**clangd 将会运行类似 `/custom/compiler -E -xc++ -v /dev/null` 的命令并解析其输出,从而获取所需的相关信息(适用于 gcc, clang 等其它具有该兼容接口的编译器)**[^4]
> [!tip] 可通过以下命令**查看 Clang 编译器在处理 C++源代码时默认的包含路径及其它配置信息**
>
> ```shell
> # 由于"/dev/null"是空设备, 因此该命令不会产生编译输出,但会出发编译器展示处理C++代码时的默认配置
> clang -v -c -xc++ /dev/null
> - `-v`: 启用详细模式(verbose mode), 输出详细信息, 包括编译器调用过程、搜索路径
> - `-c`:只编译源文件, 不进行链接
> - `-xc++`: 指定接下来的文件按照C++语言处理
>
> # windows下`/dev/null`应替换为`nul`:
> clang -v -c -xc++ nul
> ```
>
> 
>
> 上图 `#include <...> search starts here` 之后列出了所有的系统库文件搜索路径。 ^swj73r
<br><br>
# clangd 配置文件
`clangd` 的配置文件 **为 `YMAL` 文件**,分为 **项目配置文件** 和 **用户配置文件**。
两文件中配置冲突时,**用户级文件中的配置优先级更高**。
- **==项目级配置文件==**:**`.clangd` 文件**,**位于项目的源码树中**(clangd 会在当前激活文件的所有父目录中搜索)
- 该文件中的配置只应用于其所在的源码树——例如该文件位于 `proj/.clangd`,则其中配置对 `proj/**` 路径下的所有文件有效。
- **==用户级配件文件==**:**`clangd/config.yaml` 文件**,其中的配置将应用于所有打开的文件 <br>该文件位于系统特定的目录:
- Windows: `%LocalAppData%\clangd\config.yaml`, <br>例如 `C:\Users\DHDouglas\AppData\Local\clangd\config.yaml`.
- macOS: `~/Library/Preferences/clangd/config.yaml`
- Linux and others: `$XDG_CONFIG_HOME/clangd/config.yaml`,<br>例如 `~/.config/clangd/config.yaml`
> 注:JSON 是 YAML 的一个子集,因此可以使用 JSON 语法。
<br><br>
## clangd 配置文件语法
clangd 配置文件为 `YMAL` 文件,遵循 `YMAL` 语法,**每一项都是一个键值对**。
配置文件中,**路径必须使用正斜杠(UNIX-style)**。
配置文件中包括以下几个部分[^6]:
#### `If` 选项
指定当前**段**的配置的应用对象,只对匹配上的文件应用。
```yaml
If: # Apply config in this fragment conditionally
PathMatch: .*\.h # to all headers
PathExclude: include/llvm-c/.* # except those under include/llvm-c/
```
> [!info] 段(Fragement)
> 配置文件中的内容可通过 `---` 分隔符划分为**多个独立的==段==**(fragment),每个段是**一个独立的配置区块**。
>
> 通过 `---` 分隔出的段仅在这些片段 **==具有不同 `If` 条件==** 时才有效。
> `If` 属性用于定义"**条件配置**",即指定**当前段中的配置的应用对象** (例如**针对不同项目路径,采用不同的配置**),从而实现在同一个配置文件中定义多个区块,**每个区块有自己独立的配置**。
>
>
#### `CompileFlags` 选项
`CompileFlags`:指定**编译标志**,其中可包括 `Add`,`Remove`,`ComplilationDatabase`,`Compiler` 四项。
经 `Add` 添加以及 `Remove` 移除后得到的最终编译标志集将被传给 clangd 内部的 clang 前端,从而让其更准确地模拟编译环境。因此,这里添加或移除的编译标志应当是 **clang 编译器前端本身能够接受的编译标志**。
示例:
```yaml
CompileFlags: # Tweak the parse settings
Add: [-xc++, -Wall] # treat all files as C++, enable more warnings
Remove: -W* # strip all other warning-related flags
Compiler: clang++ # Change argv[0] of compile flags to `clang++`
```
> clangd 模拟了 clang 解释文件的方式,clangd 分析一个文件的默认行为等同于 `clang $FILENAME`。
>
> 在此基础上,clangd 会在目标源文件的父目录中搜索 `compile_commands.json` 文件,根据该文件
>
> 来确定编译标志。而配置文件中的这一 `CompileFlags` 选项中则**额外指定了将被应用或将被移除的编译标志**。
参数选项:
- `Add:[flag1, flag2]` 添加编译标志;
- `Remove:[flag1,flag2...]` 移除编译标志;
- `CompilationDatabase`:指定搜索 `compile_commands.json` 的路径,可以是:
- 指向目录的单一路径 (绝对路径,或相对于 `If` 中指定的当前 `fragment` 的路径)
- `Ancestors`:搜索当前激活文件的所有祖先目录 (默认)
- `None`:不使用编译数据库,只使用默认标志。
- `Compiler`:指定使用的**编译器**。
- **该选项的主要目的是帮助 clangd 找到正确的标准库头文件**。<br>如果该选项匹配 `——query-driver` 中的通配符,则将调用它来提取包含目录(include paths)。
- 该项设置的值将替换 `compile_command.json` 中 `command` 项的首个参数 `argv[0]`(即所用的编译器),控制着标志解析 (clang vs clang-cl)、目标推断 (gcc-arm-noneabi) 等功能。
> [!NOTE] 关于 `Add` 参数中的命令参数形式
>
> `Add` 里的每一项,可以 `[flag1, flag2]` 的形式添加,也可以按如上形式添加,通过 `-` 指定多项。
> 注意,在命令行选项里,**`-xxx=<value>` 与 `-yyy <value>` 是两种不同形式的选项**,不可以混用!!
> 后者也可以写成 `-yyy<value>` 的形式即去掉空格,但不可以写成 `-y=<value>`,这是无效的。
> 例如 `-I <dir>` 和 ` -isystem <dir>`,**不要写成 `-I="..."` 的形式(会无法被正确解析)**,
指定编译命令的正确形式如下:
```yaml
CompileFlags:
Add:
- -Ipath/to/include # ✔
- "-Ipath/to/include" # ✔
# 或者
- -I
- path/to/include # ✔
# 或者
- "-I"
- "path/to/include" # ✔
```
示例:
```shell
# 原始命令
clang++ --include-directory=/usr/include -DFOO=42 foo.cc
# clangd配置文件中配置`CompileFlags`包含`Remove:[-I, -DFOO=*]`
# 则实际用于分析的命令将变为:
clang++ foo.cc
```
#### `Index` 选项
控制 clangd 如何理解当前文件之外的代码,包括 `Background`、`External`、`StandardLibrary` 三项。
clangd 的索引提供了关于"**对 clang's parser 而言不可用的符号**"的信息,例如传入引用。
示例:
```yaml
Index:
Background: Skip # Disable slow background indexing of other files.
External:
File: /abs/path/to/an/index.idx
# OR
Server: my.index.server.com:50051
MountPoint: /files/under/this/project/
StandardLibrary: No # 默认
```
- `Background`:是否在后台扫描文件以生成索引(只对翻译单元进行检查,而不对其包含的头文件)
- 可选值:`Build`(默认) 或 `Skip`。
- `External`:用于定义**外部索引源**,包括 `File` 或 `Server` & `MountPoint`
- `MountPoint` 用来指定索引的源根,对于处理相对路径转换是必需的。未提供时,默认为**当前段的路径**。
- 声明外部索引将 **隐式地禁用 `MountPoint` 下文件的后台索引**。用户可以在后面的段中显式通过 `Backgound: Build` 来开启。
- `StandardLibrary`:控制 clangd 是否主动索引标准库 (在空文件上提供标准库符号的代码补全),默认 No。
#### `Diagnostics` 选项
代码诊断
- `Suppress`:指定禁用的诊断代码。也可以通过其他方式控制诊断 (例如,禁用 clang-tidy 检查或 `-Wunused` 编译标志)。可选值为:
- `*` ,表示禁用所有诊断;
- clangd 给出的诊断(例如 `unused-result`,`-Wunused-result`)
- clang 内部的诊断(例如 `err_unkown_type`)
- 警告诊断(例如 `unsed-result`)
- clang-tidy check 项的名称(例如 `bugprone-narrowing-conversions`)
- `ClangTidy`:配置 clang-tidy 在文件上运行的方式。<br>此处指定的设置将于 `.clang-tidy` 配置文件中的设置合并,且后者中的设置优先。
- `UnusedIncludes`:启用 Include Cleaner's "unused includes diagnostics"。可选值为 `None` 或 `Strict`(默认)
- `Includes` :正则表达式列表。Include Cleaner 不会为路径与其中任何一个后缀匹配的头文件生成诊断。
- `MissingIncludes`:启用 Include Cleaner’s "missing includes diagnostics"。可选值为 `None` (默认)或 `Strict`。
配置 ClangTidy 示例:
```yaml
Diagnostics:
ClangTidy:
Add: [bugprone-*] # 添加check项
Remove: [modernize-use-trailing-return-type] # 移除check项
# 用于clang-tidy checks的选项
CheckOptions:
readability-identifier-naming.VariableCase: CamelCase
```
#### `Completion` 选项
- `AllScopes`:代码补全是否应该包括**来自不可见作用域的建议**。将插入所需的作用域前缀。
#### `InlayHints` 选项
配置嵌入提示 (InlayHints) 功能的行为。参见[^6]
```yaml
InlayHints:
Enabled: Yes # 启用/禁用所有类型的嵌入提示功能,当禁用时,忽略特定类型的配置。
ParameterNames: Yes # 启用/禁用函数调用中, 参数名的嵌入提示
BlockEnd: No # 启用/禁用块结束注释的嵌入提示
DeducedTypes: Yes # 启用/禁用类型推断的嵌入提示。
Designators: Yes # 启用/禁用列表初始化中的下标指示符
TypeNameLimit: 24 # 类型提示的字符限制. 超过该限制的提示将不会显示. 0表示无限制
```
#### `Hover` 选项
配置悬浮卡片的内容.
```yaml
Hover:
# aka表示: also known as
ShowAKA: No # 默认. 是否打印类型简称, 例如`vector<int>::value_type (aka int)`
```
#### `Semantic Tokens` 选项
配置语法高亮
```yaml
SemanticTokens:
DisabledKinds: []
DisabledModifiers: []
```
## 配置文件示例
配置文件示例一:`---` 分隔符用于划分**段**,用于区分通用配置和特定于某些路径的条件配置
```yaml
# 通用配置
# Fragment common to C and C++ source files
CompileFlags:
Add: [-std=c++17, -Wall, -Wextra]
# OR
Add:
- "--include-directory=some/directory/to/search/includes/in"
- "-D"
- "SOME_MACRO_TO_DEFINE"
- "-include"
- "some/file/to/force/inclusion/of.h"
---
# Fragment 1
# 针对 `/path/to/projectA` 路径的配置
# 这里通过`If`指定当前段的配置将应用于`/path/to/projectA`目录下的所有文件
If:
PathMatch: /path/to/projectA/**
CompileFlags:
Remove: [-Wextra] # 为特定项目移除编译标志
Add: [-I/path/to/projectA/include] # 为特定项目添加包含路径
---
# Fragment 2
# 针对 /path/to/projectB/ 路径的配置
If:
PathMatch: /path/to/projectB/**
CompileFlags:
Add: [-I/path/to/projectB/include, -DBUILD_PROJECT_B] # 为另一项目添加特定编译标志和宏定义
---
# Fragment 3: specific to C++ source files
If:
PathMatch: [.*\.c, .*\.h]
CompileFlags:
Add:
"-std=c++17"
```
配置文件示例二:
```yaml
# .clangd 文件的示例
CompileFlags:
# Treat code as C++, use C++17 standard, enable more warnings.
Add: [-xc++, -std=c++17, -Wall, -Wno-missing-prototypes]
# Remove extra warnings specified in compile commands.
# Single value is also acceptable, same as "Remove: [-mabi]"
Remove: -mabi
Diagnostics:
# Tweak Clang-Tidy checks.
ClangTidy:
Add: [performance*, modernize*, readability*]
Remove: [modernize-use-trailing-return-type]
CheckOptions:
readability-identifier-naming.VariableCase: CamelCase
---
# Use Remote Index Service for LLVM.
If:
# Note: This is a regexp, notice '.*' at the end of PathMatch string.
PathMatch: /path/to/llvm/.*
Index:
External:
Server: clangd-index.llvm.org:5900
MountPoint: /path/to/llvm/
# Disable slow background indexing of these files.
# Background: Skip
```
配置文件示例三:
```YAML
CompileFlags:
# Add: [-Wall, -Wextra, -Werror]
Add: [-Wall]
InlayHints:
Enabled: No
Designators: No
---
# Fragment 2: specific to C++ source files
If:
PathMatch: [.*\.cpp$]
CompileFlags:
Add:
- "-std=c++20"
---
# Fragment 3: specific to C source files
If:
PathMatch: [.*\.c$]
CompileFlags:
Add:
- "-std=c11"
```
<br><br>
# 参考资料
# Footnotes
[^1]: [Implementations Language Servers]( https://microsoft.github.io/language-server-protocol/implementors/servers/ )
[^2]: [JSON Compilation Database Format Specification](https://clang.llvm.org/docs/JSONCompilationDatabase.html)
[^3]: [can I use g++ with clangd?](https://www.reddit.com/r/emacs/comments/yv0hj9/help_wanted_can_i_use_g_with_clangd/)
[^4]: [System headers](https://clangd.llvm.org/guides/system-headers)
[^5]: [Clangd Getting Started](https://clangd.llvm.org/installation)
[^6]: [Configuration](https://clangd.llvm.org/config)
[^7]: [--query-driver not having any effect](https://github.com/clangd/clangd/issues/537)
[^8]: [Is there "includePath" option in clangd?](https://stackoverflow.com/questions/61206703/is-there-includepath-option-in-clangd)
[^9]: [Windows下Clang的简单使用](https://zhuanlan.zhihu.com/p/380290758)
[^10]: [Frequently Asked Questions (FAQ)](https://clangd.llvm.org/faq#how-do-i-make-additional-headers-visible-to-clangd)