文章目录
前言
最近尝试将 Clang-tidy 加入到项目中,以便加强代码规范。本文纪录在这一过程中积累的关于 Clang-tidy 的知识,涵盖安装、使用、cmake 和 CI/CD 实践等内容。
Clang-tidy 是什么?
Clang-tidy是一个强大的开源工具,用于自动执行C++源代码的静态分析。它是Clang项目的一部分,可以检查代码中的错误、风格问题、性能问题等,并且可以自动修复一些常见问题。
Clang-tidy的一个重要特性是它的可扩展性。你可以编写自己的检查器来检查特定的编程错误或风格问题。这使得Clang-tidy成为一个非常强大的工具,可以定制以适应你的编程风格和需求。
Clang-tidy还可以与其他工具集成,如编辑器和持续集成系统,以自动执行代码检查和修复。这使得它成为提高代码质量和减少编程错误的重要工具。
Clang-tidy 检测流程
Clang-tidy检测一个文件的过程大致如下:
- 预处理:Clang-tidy首先对源代码进行预处理,解析出宏定义、头文件包含等信息。
- 语法分析:然后,Clang-tidy对预处理后的代码进行语法分析,生成抽象语法树(AST)。
- 静态分析:接着,Clang-tidy对AST进行静态分析,检查可能的编程错误、风格问题等。
- 报告和修复:最后,Clang-tidy报告检测到的问题,并尝试自动修复一些问题。
安装
Macos
推荐使用 pip 进行安装:
pip install clang-tidy
Linux,使用 apt 或者 pip
apt-get install clang-tidy
pip install clang-tidy
使用
Clang-tidy 命令
使用 clang-tidy 检测某个文件时需要知道头文件路径等编译信息,这是因为它需要了解代码的上下文环境,以便正确地解析代码并检测潜在的问题。例如,如果代码中使用了某个库的函数,Clang-tidy需要知道该库的头文件路径才能正确地解析函数的声明和定义。如果缺少这些编译信息,Clang-tidy可能会产生误报或漏报的问题。
因此一个 clang-tidy 检查的命令大概是这样的:
clang-tidy --checks='-*' test.cpp -- -I ./src/ -x c++
这个命令的含义如下:
- clang-tidy:调用clang-tidy工具。
- –checks=‘-*’:指定要执行的检查器。这里的-*表示不执行任何检查器,即关闭所有检查器。
- test.cpp:指定要检查的源文件名。
- –:用于分隔clang-tidy的选项和编译器的选项。
- -I ./src/:指定编译器的头文件搜索路径,即将./src/目录添加到头文件搜索路径中。
- -x c++:指定编译器要编译的源文件类型为C++。
在实际使用 clang-tidy 中,搭配上 cmake 将事半功倍。clang-tidy 中有个 -p 参数,-p build-path
参数是用于指定Clang-tidy读取编译命令数据库的路径。编译命令数据库通常是一个名为compile_commands.json的文件,它包含了编译每个源文件所需的完整命令行,包括编译器选项、头文件路径等信息。
例如,如果你使用CMake进行构建,可以通过设置-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
选项来生成compile_commands.json文件。然后,你可以使用-p参数告诉Clang-tidy在哪里找到这个文件,如clang-tidy -p /path/to/build/directory。
如果没有指定-p参数,Clang-tidy会尝试在第一个输入文件的所有父路径中搜compile_commands.json文件。
这个参数的主要目的是让Clang-tidy能够正确解析源代码。因为C++的语法和语义很大程度上取决于编译上下文,如果没有正确的编译信息,Clang-tidy可能无法正确解析源代码,也就无法进行准确的静态分析。
clang-tidy-diff.py 脚本
clang 官方提供了一些脚本,在 clang-tidy 命令行的基础上提供了额外的能力,通过 clang-tidy-diff.py 来检查 git diff 中实际发生修改的代码。你可以这样使用:
git diff branchA master | python clang-tidy.py -path /path/to/compile_commands.json
这个命令的含义如下:
- git diff branchA master:使用git diff命令比较分支branchA和master之间的差异,并将差异输出到标准输出流中。
- |:管道符号,将git diff的输出作为下一个命令的输入。
- python clang-tidy-diff.py:使用Python脚本 clang-tidy-diff.py 来处理git diff的输出。
- -path /path/to/compile_commands.json:指定编译命令数据库的路径为/path/to/compile_commands.json。
实践
检查项
clang-tidy 的检查项非常丰富,这部分可以参考:
如果你不知道检查哪些,那偷个懒直接使用 CLion 的默认配置即可,通过 how-to-dump-clions-default-clang-tidy-configuration 提到的方法生成 .clang-tidy
文件。命令行中使用 --config-file
指定 .clang-tidy
文件即可
clang-tidy --config-file=/path/to/.clang-tidy ...
在检查项中,可以指定 warning-as-error
,例如
---
Checks: '-*,
bugprone-argument-comment,
bugprone-assert-side-effect,
bugprone-bad-signal-to-kill-thread,
bugprone-copy-constructor-init'
WarningsAsErrors: 'bugprone-*'
这样检测到相关问题后直接报错,方便 ci/cd 进行检查
编译中开启 clang-tidy 检查
在CMake中,我们可以通过设置CMAKE_CXX_CLANG_TIDY属性来为整个项目开启Clang-tidy检查。例如:
set(CLANG_TIDY_COMMAND clang-tidy;
-header-filter=^${PROJECT_ROOT_DIR}/src/.*;
-config-file=${PROJECT_ROOT_DIR}/.clang-tidy;)
set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_COMMAND})
在这个例子中,CMAKE_CXX_CLANG_TIDY被设置为一个包含Clang-tidy命令和相关选项的变量。这将导致所有编译的文件都会被Clang-tidy检查。
然而,有时我们可能希望跳过某些文件的检查,比如第三方库的代码。在这种情况下,使用CMAKE_CXX_CLANG_TIDY就不再适用。一个更好的选择是使用set_target_properties命令,只为特定的目标开启Clang-tidy检查。例如:
set_target_properties(myTarget
PROPERTIES
CXX_CLANG_TIDY ${CLANG_TIDY_COMMAND})
在这个例子中,只有myTarget目标的源文件会被Clang-tidy检查,其他没有设置CXX_CLANG_TIDY属性的目标则不会受到影响。
跳过部分代码的检查
可以使用 NOLINT
、NOLINTNEXTLINE
以及 NOLINTBEGIN
+ NOLINTEND
跳过某些代码的静态检测
int a = 10; // NOLINT
// NOLINTNEXTLINE
int b = 20;
// NOLINTBEGIN
void fun()
{
dosomething(); // 跳过 BEGIN 和 END 之间的代码
}
// NOLINTEND
将 clang-tidy 加入 CI/CD
有两种方式将 clang-tidy 加入 CI/CD 中:
- 在 cmake 中设置 CXX_CLANG_TIDY 开启编译时检查
- 使用 clang-tidy-diff.py 检测被修改的文件
对比两种方式各有优劣:
- 开启编译时检查,设置方便,但增加编译耗时,且对现有所有文件进行检查,如果项目文件很多那么修改所有 error 是一个苦力活。
- clang-tidy-diff.py 只检查增量或者被修改的文件,但需要提供 compile_commands.json 文件。
最终选择了方法 2,庆幸的是目前大部分 C/C++ 项目都使用基本使用 cmake 来管理,包括 Android NDK,因此使用起来并无太大麻烦。例如你想在 CI/CD 中检查某个 Android NDK 项目,你可以这么做:
- 使用
./gradlew assembleFullRelease
编译你的项目 - 找到生成的
compile_commands.json
文件所在 - 使用 clang-tidy-diff.py 检查被修改的文件:
git diff A B | python clang-tidy-diff.py -path=/path/compile_commands.json -config-file=/path/.clang-tidy