36、代码格式化:集成clang-format,添加自定义target
代码格式化这件事,说实话,我以前也觉得可有可无。直到有一次团队合并代码,Git 上密密麻麻全是空格和缩进的差异,真正改动的逻辑反而被淹没了。嗯,从那以后,我就把 clang-format 集成到了 CMake 里,作为每个项目的标配。
这一讲,我们就来聊聊怎么在 CMake 中集成 clang-format,并且添加一个自定义 target,让你可以一键格式化所有代码。
为什么要在 CMake 里做这件事?
你可能会想:「我 IDE 里不是有格式化插件吗?手动按一下快捷键不就行了?」
没错,个人开发没问题。但团队协作时,每个人的 IDE 配置可能不一样。有人用 4 空格缩进,有人用 Tab,还有人喜欢大括号换行风格。结果就是——代码风格五花八门。
我的做法是:在 CMake 里加一个 format 的 target,所有人都能通过 make format 或 cmake --build . --target format 来统一格式化。谁都不用记快捷键,也不用担心配置不一致。
准备工作:安装 clang-format
首先,你得确保系统里有 clang-format。不同平台安装方式略有不同:
| 平台 | 安装命令 |
|---|---|
| Ubuntu/Debian | sudo apt install clang-format |
| macOS | brew install clang-format |
| Windows | 通过 LLVM 安装包或 choco install llvm |
安装完后,可以用 clang-format --version 验证一下。我个人习惯用 clang-format 14 或更高版本,因为对 C++20 的支持更好。
第一步:准备 .clang-format 配置文件
clang-format 的行为由 .clang-format 文件控制。你可以放在项目根目录,这样所有子目录都会继承。
这里给出一份我常用的配置,适合大多数 C++ 项目:
# .clang-format
BasedOnStyle: Google
IndentWidth: 4
UseTab: Never
ColumnLimit: 100
AllowShortFunctionsOnASingleLine: None
BreakBeforeBraces: Attach
NamespaceIndentation: All
PointerAlignment: Left
简单解释一下:
- BasedOnStyle: Google —— 以 Google 风格为基础,省得从头配
- IndentWidth: 4 —— 我个人喜欢 4 空格缩进,比 2 空格更清晰
- ColumnLimit: 100 —— 100 字符换行,兼顾宽屏和可读性
- BreakBeforeBraces: Attach —— 大括号不换行,节省行数
当然,你可以根据团队规范调整。关键是要把这个文件提交到 Git 仓库,让所有人都用同一套规则。
第二步:在 CMakeLists.txt 中集成 clang-format
核心思路是:用 add_custom_target 创建一个 target,然后调用 clang-format 命令格式化所有源文件。
来看一个完整的示例:
# 查找 clang-format 可执行文件
find_program(CLANG_FORMAT clang-format)
if(CLANG_FORMAT)
# 收集所有需要格式化的源文件
file(GLOB_RECURSE ALL_SOURCE_FILES
${CMAKE_SOURCE_DIR}/src/*.cpp
${CMAKE_SOURCE_DIR}/src/*.h
${CMAKE_SOURCE_DIR}/include/*.h
${CMAKE_SOURCE_DIR}/tests/*.cpp
${CMAKE_SOURCE_DIR}/tests/*.h
)
# 添加自定义 target:format
add_custom_target(format
COMMAND ${CLANG_FORMAT}
-i
-style=file
${ALL_SOURCE_FILES}
COMMENT "Formatting all source files with clang-format..."
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
# 添加自定义 target:check-format(只检查不修改)
add_custom_target(check-format
COMMAND ${CLANG_FORMAT}
--dry-run
--Werror
-style=file
${ALL_SOURCE_FILES}
COMMENT "Checking code formatting..."
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
)
else()
message(WARNING "clang-format not found. Skipping format targets.")
endif()
这里我做了两个 target:
- format —— 直接格式化所有源文件(
-i表示原地修改) - check-format —— 只检查,不修改。如果格式不对,返回非零退出码,CI 里可以用这个来卡点
file(GLOB_RECURSE) 收集文件时,注意不要包含第三方库的目录。可以在 GLOB 时排除掉 third_party 或 build 目录,或者单独指定源文件路径。
第三步:使用自定义 target
配置好之后,用法很简单:
# 格式化代码
cmake --build . --target format
# 只检查格式
cmake --build . --target check-format
如果你用的是 Makefile 生成器,也可以直接:
make format
make check-format
嗯,就是这么简单。团队里每个人都能用同一行命令格式化代码,再也不用为缩进吵架了。
进阶:在 CI 中自动检查格式
我曾经在一个项目里吃过亏——有人提交了格式混乱的代码,review 时没发现,合并后整个文件 diff 变得惨不忍睹。后来我就在 CI 里加了一步:
# GitHub Actions 示例
- name: Check code formatting
run: cmake --build build --target check-format
如果格式不对,CI 直接报红,开发者必须修复后才能合并。这招很管用,建议你也试试。
知识体系图
下面这张图帮你理清整个流程:
避坑指南
file(GLOB_RECURSE) 时,如果项目很大,每次 CMake 重新配置都会扫描所有文件,速度会变慢。我的建议是:如果源文件目录结构固定,可以手动列出关键目录,或者用 file(GLOB) 配合 list(APPEND) 分批次添加。
另外,clang-format 的 --style=file 选项会从当前目录向上查找 .clang-format 文件。如果你把配置文件放在项目根目录,而 WORKING_DIRECTORY 也设成了根目录,那就没问题。否则 clang-format 可能找不到配置文件,使用默认风格——那效果就完全不一样了。
总结
这一讲的内容其实不复杂,但很实用。核心就三步:
- 安装 clang-format
- 编写
.clang-format配置文件 - 在 CMake 中用
add_custom_target创建 format 和 check-format 两个 target
我个人觉得,代码格式化这件事,越早集成到项目里越好。等代码写到几万行再回头格式化,那改动量就太大了,review 起来也痛苦。不如一开始就养成习惯,每次提交前跑一下 make format,干净利落。
好了,这一讲就到这里。如果你在集成过程中遇到什么问题,欢迎交流。
公众号:蓝海资料掘金营,微信 deep3321