%% # 纲要 > 主干纲要、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: > ![image-20231217161509222|341](_attachment/05-工具/GNU%20工具/clangd%20工具.assets/IMG-clangd%20工具-60798812239D03C9ACFA9066BACAE52C.png) > windows 上通过 `clang -v -c -xc++ nul` 查看具体的系统库头文件搜索路径: > ![image-20231217161528714|936](_attachment/05-工具/GNU%20工具/clangd%20工具.assets/IMG-clangd%20工具-16C630DC6EFFA550F22F1A0A246EA6FF.png) > 可见,**默认使用 MSVC 编译工具链,系统库文件搜索路径为 MSVC 下的路径**。 > > 如果加上 `--target` 选项指定使用 MinGW 编译工具链,使用命令:`clang --target=x86_x64-pc-windows-gnu -v -c -xc++ nul`,则显示信息如下: > ![image-20231217162516945|961](_attachment/05-工具/GNU%20工具/clangd%20工具.assets/IMG-clangd%20工具-5B11410C4CAF38DE6AD3E36AA5BB40ED.png) > 可见,系统库文件搜索路径变为了 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。 > > ![image-20231213203120237|563](_attachment/05-工具/GNU%20工具/clangd%20工具.assets/IMG-clangd%20工具-2DF628250E4F9FECCCF6ADF6B8710BCA.png) > > <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` 也没有作用。 > > ![image-20231217205557269|998](_attachment/05-工具/GNU%20工具/clangd%20工具.assets/IMG-clangd%20工具-F0AF5FE2CC68B3C8153685D7988EE569.png) > > 生成的 `compile_commands.json` 中的编译器命令没有问题, > > ![image-20231217205653162|988](_attachment/05-工具/GNU%20工具/clangd%20工具.assets/IMG-clangd%20工具-A3398227DBAD5C66B1EB474B6D594D85.png) > > 直接使用上述编译器命令,运行结果如下: > > ![image-20231217172341845|1163](_attachment/05-工具/GNU%20工具/clangd%20工具.assets/IMG-clangd%20工具-0CC7F20D3BB57197BA51E94BD0345304.png) > > `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 > ``` > > ![image-20231216183825898|864](_attachment/05-工具/GNU%20工具/clangd%20工具.assets/IMG-clangd%20工具-16C630DC6EFFA550F22F1A0A246EA6FF-2.png) > > 上图 `#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)