VSCode Clangd 插件配置实战
前言:许多程序员青睐 Visual Studio 或者 Jetbrains IDEs,主要是因为这些开发环境有着很良好的代码提示、代码补全、代码检查、语法高亮。这篇文章将揭开“智能提示”,并给出 VSCode 环境下开发 C/C++ 程序的最佳实践。
1. 编译系统·
很多初学者可能只用过单文件编译,比如把所有的函数都写在一个文件里,然后通过 main
作为 entry 进行调用。后面再做复杂项目的时候,就已经用上了 IDE,因此对于 IDE 做了哪些工作以及为什么要做这些工作,程序员缺乏理解。
一般我们认为编译系统由下面四个部分组成:
- 前处理(PreTask)
- 编译前需要拉取最新代码,比如 build-root 编译系统中需要拉取 package 对应的最新代码库或指定哈希值的提交版本。
- 编译链接(Task)
- 后处理(PostTask)
- 某个目标编译完成,需要将之移动(move)到某个特定目录以便于执行下一步操作。
- 改变某个文件的权限标记。
- 可选的打包与安装
- packaging
- install
1.1 现有的编译组织总结·
- 第 0 代,(shell) 直接将编译指令编写到 shell 脚本。弊端:
- 前面的指令出错,后面的指令也会出错
- 无法按需编译
- 尽管第 0 代方案可以通过编写复杂的 shell 脚本实现高级方案的某些功能,但是难度较大
- 第 1 代,(makefile) 定义 target 和 dependency. 弊端:
- 需要手写依赖
- 依旧需要写编译指令
- 无法跨平台
- 第 2 代,(qmake) 以目标为中心进行描述,需要描述目标之间的依赖关系。弊端:
- 大工程多模块之间的依赖关系需要手动声明
- 被链接的模块无法导出宏定义、头文件路径、需要链接的库,导致二次开发很困难,链接工作经常出错。
- 第 3 代,(cmake) 以模块为中心进行描述
- cmake 通过
PUBLIC
和PRIVATE
等修饰符,可以给 module 添加属性,并且属性的传递性可在 cmake 中定义。
- cmake 通过
1 | add_library(module STATIC) |
第三代编译系统以模块为中心,可以 export 路径和库,依赖关系不需要声明。
- 源代码:顾名思义,就是所需要的代码
- 导出路径:我构建了一个模块(一堆
.h
和.c
文件),如果别的模块需要引用本模块的代码、数据结构,则本模块需要将路径导出。 - 引用路径
- 公共依赖
- 私有依赖
每个条目对应的语句如下表所示。
含义 | Cmake | gn |
---|---|---|
源代码 | sources |
sources |
导出路径 | include_directories(PUBLIC) |
public_configs>include_dirs |
公共依赖 | link_libraries(PUBLIC) |
public_deps |
私有依赖 | link_libraries(PRIVATE) |
deps |
引用路径 | include_directories(PRIVATE) |
include_dirs |
宏定义 | compile_definitions |
defines |
编译选项 | compile_options |
cflags_cc |
链接选项 | link_options |
ldflags |
1.2 现代 C/C++ 开发需要 IDE·
在裸文本编辑器环境下开发是非常困难的,VSCode 能得以快速发展,主要得益于能给用户体提供开箱即用的良好开发体验。
一般认为,良好的编辑器对某一门语言要提供如下功能:
- 语义级别的语法高亮
- 基于上下文的代码提示
- 基于上下文与关键字的自动补全
- 对大项目、复杂项目的快速响应
微软 VSCode 自带的 IntelliSense 能实现代码补全,但是在大型 C++ 项目中它的表现不够好,相对比较卡顿,影响使用体验。因此我一般在 settings.json
中添加一句
1 | "C_Cpp.intelliSenseEngine": "disabled" |
以屏蔽 C/C++ 项目下微软 intelliSense 功能。
1.3 clangd
介绍·
关闭了微软 IntelliSense 之后,我们该如何使用 C/C++ 的代码提示、语法高亮?答案就是 clangd. 下面摘录一段来自 LLVM 官方网站的介绍:
clangd understands your C++ code and adds smart features to your editor: code completion, compile errors, go-to-definition and more. 翻译:
clangd
理解你的 C++ 代码同时可以给你的编辑器添加如下功能:代码补全、编译报错提示、定义跳转等。
1.4 clangd
配置·
clangd 提供代码高亮、关键字跳转等服务并非基于 AI,而是基于一套机械化的编译过程的中间产物:compile_commands.json
. 在使用 cmake 生成 ninja/make 编译脚本 build.ninja
/Makefile
的时候,cmake 会顺带生成一个 compile_commands.json
,该文件事无巨细地将如何编译整个项目记录了下来,clangd
会根据这个文件的指示去理解源码里的关键字、上下文,而非简单地进行关键字匹配。
注意:一般来说,cmake 在生成 build 目录时会同时生成
compile_commands.json
文件,但有时候也不会生产。使用cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 .
命令可以强制生成该文件。
如果你的项目只有 Makefile
而没有 CMakeLists.txt
,可以通过 compiledb
命令进行转化:
1 | # 安装 |
随后即可在文件夹里看到 compile_commands.json
.
使用 cmake 编译 C/C++ 项目时,一般会先新建一个名为 build
的文件夹,然后所有的中间产物、target 都生成在这个文件夹里,compile_commands.json
亦不例外。为了防止 clangd
漫无目的地搜索该文件,我们需要指定它去 ${workspaceFolder}/build
目录里搜索。可通过编辑项目根目录下的 .vscode/settings.json
文件实现此目的。
1 | // filename: ${workspaceFolder}/.vscode/settings.json |
2. Linux 源码阅读与编译·
Linux 内核源代码作为大型开源项目,比较适合拿来检测 clangd
的分析速度。
2.1 拉取源代码·
如果你的网络质量比较好,机器性能比较高,可直接通过 git 下载最新的源代码:
1 | git clone https://github.com/torvalds/linux.git |
注意,直接在 linux git 仓库上操作开销很大,因为源码非常庞大,git checkout
一次都会有非常大的时间开销。因此,对于绝大多数不在意最新 commits 的用户,建议从 kernel.org 下载某个特定发行版的压缩包并解压到本地。
对于 zsh 用户,如果你 clone 了整个 linux 仓库,那么最好关闭 zsh git 插件。具体命令如下,该操作只会对这一个仓库生效,配置保存在 ${linux}/.git/config
中,不会对本机的其他 git 文件夹造成影响。--add
参数指的是添加配置项时新起一行,而且不对现在的既有配置做任何更改,因此这项操作比较安全。
1 | git config --add oh-my-zsh.hide-status 1 |
2.2 生成 compile_commands.json
·
Linux 内核作为一个很完善的项目,在其 scripts/clang-tools
目录里提供了生成 compile_commands.json
的 Python 脚本:gen_compile_commands.py
. 在使用 clang 编译完内核之后,就可以直接执行该脚本,随后将在项目根目录得到 compile_commands.json
.
1 | # 切换到最新的发行版 |
随后,将该项目的 VSCode clangd
配置改为如下:
1 | // filename: ${workspaceFolder}/.vscode/settings.json |
现在即可开始使用!
注意:截至 2023 年 8 月 27 日,VSCode 自动安装的 clangd 是 16 版,debian 12 上游 apt 库中最新的
clangd
是 14 版本 (亦可通过apt install clangd-15
安装更新的版本)。可通过下述命令安装 debian 上游提供的 15 版本,优点是与 apt 管理紧密集成。
1 | sudo apt install llvm-15 clangd-15 |
2.3 阅读源码·
Just enjoy it.
总结·
clangd
是现在各大编辑器 Language Server 的首选工具,在开源发展中起到了巨大的作用。本文抛砖引玉地介绍了 clangd
的基础用法,希望读者能快速上手。VSCode 虽然能开箱即用、快速上手,但是要精通其配置可谓是困难重重,笔者日常也经常感叹微软何能构建如此复杂的编辑器?唯有坚持探索,才能不断提升对软件、代码的理解力。