如何将clang-tidy集成到CMake和GCC中?

18
我想将clang-tidy集成到我们的C和C++、基于CMake编译的项目中,该项目使用自定义GCC工具链进行编译。我尝试了这个教程,设置了CMAKE_CXX_CLANG_TIDY。我还尝试通过设置CMAKE_EXPORT_COMPILE_COMMANDSON并将run-clang-tidy.py指向其目录来生成编译数据库。在这两种情况下,我都遇到了(相同的)一些错误,这些错误可能与Clang和GCC之间的差异有关:
1. 在CMake文件中启用的某些警告标志在Clang中不受支持,但在GCC中受支持(例如-Wlogical-op)。由于编译器是GCC,因此文件可以正确构建,并且该标志被写入编译数据库,但是clang-tidy会对其进行抱怨。
2. clang-tidy抱怨一些定义和函数不可用,即使代码编译得很好。例如,android-cloexec-open check建议使用O_CLOEXEC来提高安全性并强制关闭文件,但尝试使用此定义会导致未定义的标识符错误(尽管我们的GCC可以编译代码)。一个未找到的函数示例是clock_gettime
我们的代码使用C11标准和C++14标准进行编译,没有使用GNU扩展。
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 14)

set(CMAKE_C_EXTENSIONS OFF)
set(CMAKE_CXX_EXTENSIONS OFF)

定制工具链是一种交叉编译工具链,可在Linux上运行并编译到FreeBSD。

  1. 是否有办法禁用CMake传递给clang-tidy的某些标志?我使用clang-tidy的方式不对吗?
  2. 我怀疑这个问题与禁用GNU扩展、使用交叉编译工具链以及一些功能测试宏有关,该宏在Clang中默认情况下未定义,但在GCC中被定义(例如_GNU_SOURCE/_POSIX_SOURCE)。如果是这样,我该如何检查它?如果不是,我应该以不同的方式使用clang-tidy吗?

编辑

如@pablo285所请求,以下是单个文件的2个警告,然后当我添加了--warnings-as-errors=*时,构建停止:

error: unknown warning option '-Wlogical-op' ; did you mean '-Wlong-long'? [clang-diagnostic-error]

<file path>: error: use of undeclared identifier 'O_CLOEXEC' [clang-diagnostic-error]
O_WRONLY | O_CLOEXEC
           ^

我决定编写一个Python脚本来替换clang-tidy,从CMake接收命令行并编辑以修复各种错误。这是我尝试的命令行修改:
1. 删除非clang编译标志
这有助于解决第一个警告等问题,因为现在我不会传递clang不知道的标志。似乎我无法配置CMake以将不同的标志集传递给GCC和clang-tidy,所以如果有人熟悉这个问题的解决方案,我很乐意听取!
2. 我更改了传递给clang-tidy的包含目录
如帖子中所述,我使用自定义工具链(进行交叉编译)。我使用this post和Python提取标准包含目录列表,并将它们作为-isystem <dir>列表添加到标志列表中。我还添加了-nostdinc,这样clang-tidy就不会尝试查找自己的头文件而不是我的头文件。
这有助于解决上述问题,因为现在工具链头文件中定义了各种定义,例如O_CLOEXEC,但由于我的工具链基于GCC,因此clang无法解析包括对许多编译器内部函数的调用的<type_traits>头文件。
3. 在这种情况下,我不确定最佳方法是什么。
@shycha:感谢您的提示,我将尝试禁用此特定检查,并再次编辑此帖子。

如果您能提供您遇到的错误,那将非常有帮助。另外,您是否尝试使用clang编译代码?Clang-tidy使用相同的前端来生成其AST,因此如果您可以使用clang编译,则在使用clang-tidy时不应该出现任何问题。 - pablo285
由于现在是周末,我会在回到工作时尝试发布错误信息。同时 - 我不使用Clang编译代码,而是使用自定义的GCC工具链。它可能无法使用clang进行编译,因为CMakeLists.txt文件添加了GCC特定的编译标志。 我可能可以使用$<CMAKE_C/CXX_COMPILER_ID>来检查编译器,但是当使用CMAKE_CXX_CLANG_TIDY变量时,据我理解,标志是根据编译器获取的。 - cereagni
@cereagni 我真的很想看到如何在使用GCC进行交叉编译时将clang-tidy与cmake集成。当您使用set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--enable-check-profile;--checks=-*,modernize-use-auto")时,它是否会编译?这应该关闭所有检查,只留下clang-diagnostic-*modernize-use-auto。(不知何故禁用所有检查无效...) - shycha
我有同样的问题,我看到有一个-extra-arg,但我想要的是类似于-ignore-arg的东西,以删除传递给gcc的标志,这些标志不受clang / clang-tidy支持。 - Zitrax
2个回答

7

好的,我认为我有一个解决方案。经过几个晚上的努力,我终于让它成功了。

通常情况下,我会这样编译:

rm -rf build
mkdir build
cd build
cmake -C ../cmake-scripts/clang-tidy-all.cmake .. && make

"cmake-scripts"目录包含:

clang-tidy-all.cmake
toolchain_arm_clang.cmake

以下是需要翻译的内容:

以下是两个重要的文件列表。 但更重要的是,您需要了解如何编译它。

首先,toolchain_arm_clang.cmake 直接从 clang-tidy-all.cmake 引用,通过 set(CMAKE_TOOLCHAIN_FILE ...)。但是,必须从构建目录的角度引用它,因此,如果您使用多个级别的构建目录,例如:build/x86build/armbuild/darwin 等,则必须相应地修改该路径。

其次,set(CONFIG_SCRIPT_PRELOADED ...) 的目的是确保预加载了配置脚本,即 cmake -C ../cmake-scripts/clang-tidy-all.cmake ..。 通常,在您的 CMakeLists.txt 文件中,您会希望在某个位置添加类似以下内容:

message(STATUS "CONFIG_SCRIPT_PRELOADED: ${CONFIG_SCRIPT_PRELOADED}")
if(NOT CONFIG_SCRIPT_PRELOADED)
    message(FATAL_ERROR "Run cmake -C /path/to/cmake.script to preload a config script!")
endif()

第三点,set(CMAKE_LINKER_ARM_COMPAT_STATIC ...)中硬编码了/lib/ld-musl-armhf.so.1;在我使用的开发环境中,它指向/lib/libc.so,因此使用/lib/libc.sh可能是可以的,但我从未尝试过。
第四点,使用set(CMAKE_C_LINK_EXECUTABLE ...)set(CMAKE_LINKER_ARM_COMPAT_STATIC ...)是因为CMake在检查编译器时出现了一些链接问题,即甚至在运行make之前就出现了问题。
第五点,我只编译C++代码,所以如果您需要编译一些C代码,则可能还需要正确配置set(CMAKE_C_CREATE_SHARED_LIBRARY ...),但我不知道是否存在这样的配置选项。
总体建议:不要立即集成它。首先测试一些简单的CMake项目,其中包含一个库(最好是C++),并使其正常工作,然后添加第二个库,但是使用C进行调整。只有在完成这些后,再将其纳入代码库。
工具链:我使用了自定义工具链,其中包括GCC 8.3.0muslC库,因此其他工具链的某些文件位置可能不同。
自定义CMake:一些变量(如已提到的CONFIG_SCRIPT_PRELOADEDEXPORT_PACKAGE_TO_GLOBAL_REGISTRYDO_NOT_BUILD_TESTSDO_NOT_BUILD_BENCHMARKS)不是通用的CMake选项,即我只在我的CMakeLists.txt中使用它们,因此您可以放心地忽略它们。
在每个*.cmake文件的末尾取消设置的变量(例如build_testextra_clang_tidy_unchecks_for_tests_only)不需要出现在项目的主要CMakeLists.txt中。
Clang
$ clang --version
clang version 10.0.0 (https://github.com/llvm/llvm-project.git 4650b2f36949407ef25686440e3d65ac47709deb)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /opt/local/bin

Files

clang-tidy-all.cmake:

set(ALL_CXX_WARNING_FLAGS --all-warnings -Weverything -Wno-c++98-compat -Wno-c++98-c++11-compat -Wno-c++98-c++11-c++14-compat -Wno-padded -Wno-c++98-compat-pedantic)
set(CXX_COMPILE_OPTIONS "-std=c++17;-O3;${ALL_CXX_WARNING_FLAGS}" CACHE INTERNAL "description")


set(CMAKE_CROSSCOMPILING True)
set(CMAKE_TOOLCHAIN_FILE "../cmake-scripts/toolchain_arm_clang.cmake" CACHE FILEPATH "CMake toolchain file")

set(CONFIG_SCRIPT_PRELOADED true CACHE BOOL "Ensures that config script was preloaded")


set(build_test False)

if(build_test)
    message(STATUS "Using test mode clang-tidy checks!")
    set(extra_clang_tidy_unchecks_for_tests_only ",-google-readability-avoid-underscore-in-googletest-name,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-special-member-functions")
endif()

set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--enable-check-profile;--checks=-*,abseil-string-find-startswith,bugprone-*,cert-*,clang-analyzer-*,cppcoreguidelines-*,google-*,hicpp-*,llvm-*,misc-*,modernize-*,-modernize-use-trailing-return-type,performance-*,readability-*,-readability-static-definition-in-anonymous-namespace,-readability-simplify-boolean-expr,portability-*${extra_clang_tidy_unchecks_for_tests_only}" CACHE INTERNAL "clang-tidy")

message(STATUS "build_test: ${build_test}")
message(STATUS "extra_clang_tidy_unchecks_for_tests_only: ${extra_clang_tidy_unchecks_for_tests_only}")
message(STATUS "CMAKE_CXX_CLANG_TIDY: ${CMAKE_CXX_CLANG_TIDY}")

# We want to skip building tests when clang-tidy is run (it takes too much time and serves nothing)
if(DEFINED CMAKE_CXX_CLANG_TIDY AND NOT build_test)
    set(DO_NOT_BUILD_TESTS true CACHE BOOL "Turns OFF building tests")
    set(DO_NOT_BUILD_BENCHMARKS true CACHE BOOL "Turns OFF building benchmarks")
endif()



unset(build_test)
unset(extra_clang_tidy_unchecks_for_tests_only)
set(EXPORT_PACKAGE_TO_GLOBAL_REGISTRY "OFF" CACHE INTERNAL "We don't export clang-tidy-all version to global register")

toolchain_arm_clang.cmake:

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_VERSION 4.14.0)
set(CMAKE_SYSTEM_PROCESSOR arm)


set(gcc_version 8.3.0)
set(x_tools "/opt/zynq/xtl")

set(CMAKE_C_COMPILER "clang" CACHE INTERNAL STRING)
set(CMAKE_CXX_COMPILER "clang++" CACHE INTERNAL STRING)
set(CMAKE_RANLIB "llvm-ranlib" CACHE INTERNAL STRING)
set(CMAKE_AR "llvm-ar" CACHE INTERNAL STRING)
set(CMAKE_AS "llvm-as" CACHE INTERNAL STRING)
set(CMAKE_LINKER "ld.lld" CACHE INTERNAL STRING)

execute_process(
    COMMAND bash -c "dirname `whereis ${CMAKE_LINKER} | tr -s ' ' '\n' | grep ${CMAKE_LINKER}`"
    OUTPUT_VARIABLE cmake_linker_dir
)
string(REGEX REPLACE "\n$" "" cmake_linker_dir "${cmake_linker_dir}")
set(cmake_linker_with_dir "${cmake_linker_dir}/${CMAKE_LINKER}" CACHE INTERNAL STRING)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -iwithsysroot /include/c++/${gcc_version} -iwithsysroot /include/c++/${gcc_version}/arm-linux-musleabihf" CACHE INTERNAL STRING)


set(CMAKE_SYSROOT ${x_tools}/arm-linux-musleabihf)
set(CMAKE_FIND_ROOT_PATH ${x_tools}/arm-linux-musleabihf)
set(CMAKE_INSTALL_PREFIX ${x_tools}/arm-linux-musleabihf)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE NEVER)



set(triple arm-linux-musleabihf)
set(CMAKE_LIBRARY_ARCHITECTURE ${triple})
set(CMAKE_C_COMPILER_TARGET ${triple})
set(CMAKE_CXX_COMPILER_TARGET ${triple})


set(lib_path_arm ${x_tools}/arm-linux-musleabihf/lib)

## Bootstrap library stuff:
set(Scrt1_o ${lib_path_arm}/Scrt1.o)
set(crti_o ${lib_path_arm}/crti.o)
set(crtn_o ${lib_path_arm}/crtn.o)

set(lib_path_gcc ${x_tools}/lib/gcc/${triple}/${gcc_version})
set(crtbeginS_o ${lib_path_gcc}/crtbeginS.o)
set(crtendS_o ${lib_path_gcc}/crtendS.o)


# Clang as linker
# --no-pie disable position independent executable, which is required when building
# statically linked executables.
set(CMAKE_CXX_LINK_EXECUTABLE "clang++ --target=${triple} -Wl,--no-pie --sysroot=${CMAKE_SYSROOT} ${CMAKE_CXX_FLAGS} -fuse-ld=${cmake_linker_with_dir} <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o  <TARGET> ")
set(CMAKE_CXX_CREATE_SHARED_LIBRARY "clang++ -Wl, --target=${triple} --sysroot=${CMAKE_SYSROOT} ${CMAKE_CXX_FLAGS} -fuse-ld=${cmake_linker_with_dir} -shared <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o  <TARGET> ")
#
# Do not use CMAKE_CXX_CREATE_STATIC_LIBRARY -- it is created automatically
# by cmake using ar and ranlib
#
#set(CMAKE_CXX_CREATE_STATIC_LIBRARY "clang++ -Wl,--no-pie,--no-export-dynamic,-v -v --target=${triple} --sysroot=${CMAKE_SYSROOT} ${CMAKE_CXX_FLAGS} -fuse-ld=ld.lld <CMAKE_CXX_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o  <TARGET> ")


## Linker as linker
set(CMAKE_LINKER_ARM_COMPAT_STATIC "-pie -EL -z relro -X --hash-style=gnu --eh-frame-hdr -m armelf_linux_eabi -dynamic-linker /lib/ld-musl-armhf.so.1 ${Scrt1_o} ${crti_o} ${crtbeginS_o} -lstdc++ -lm -lgcc_s -lgcc -lc ${crtendS_o} ${crtn_o}")
set(CMAKE_C_LINK_EXECUTABLE "${CMAKE_LINKER} ${CMAKE_LINKER_ARM_COMPAT_STATIC} <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES> <OBJECTS> -o  <TARGET>")


# Debian bug 708744(?)
#include_directories("${CMAKE_SYSROOT}/usr/include/")
#include_directories("${CMAKE_SYSROOT}/usr/include/c++/${gcc_version}")
#include_directories("${CMAKE_SYSROOT}/usr/include/c++/${gcc_version}/${triple}")

## Clang workarounds:
set(toolchain_lib_dir_0 "${CMAKE_SYSROOT}/lib")
set(toolchain_lib_dir_1 "${CMAKE_SYSROOT}/../lib")
set(toolchain_lib_dir_2 "${CMAKE_SYSROOT}/../lib/gcc/${triple}/${gcc_version}")
set(CMAKE_TOOLCHAIN_LINK_FLAGS "-L${toolchain_lib_dir_0} -L${toolchain_lib_dir_1} -L${toolchain_lib_dir_2}")

## CMake workarounds
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_TOOLCHAIN_LINK_FLAGS} CACHE INTERNAL "exe link flags")
set(CMAKE_MODULE_LINKER_FLAGS ${CMAKE_TOOLCHAIN_LINK_FLAGS} CACHE INTERNAL "module link flags")
set(CMAKE_SHARED_LINKER_FLAGS ${CMAKE_TOOLCHAIN_LINK_FLAGS} CACHE INTERNAL "shared link flags")



unset(cmake_linker_with_dir)
unset(cmake_linker_dir)

1
也许不完全符合您的要求,但我在CMakeLists.txt中使用了以下内容:
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_custom_target(lint
    COMMAND sh -c "run-clang-tidy -header-filter=.* -checks=`tr '\\n' , <${CMAKE_SOURCE_DIR}/checks.txt` >lint.out 2>lint.err"
    COMMAND sh -c "grep warning: lint.out || true"
    COMMAND ls -lh ${CMAKE_BINARY_DIR}/lint.out
    VERBATIM
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

这将创建一个单独的构建目标(make lint),用于进行clang-tidy检查。由于clang-tidy在我的项目中需要很长时间,因此我不想在每次构建时都运行它;如果需要,可以手动运行make lint,并且在每次推送到仓库后也会在CI作业中执行它(以使CI管道失败,阻止合并,如果有任何发现)。 make lint的输出是尽可能少上下文的clang-tidy发现列表。包括发现上下文的完整输出在lint.out中,错误消息在lint.err中,我将它们保存为CI工件。 checks.txt是位于项目根目录中的文本文件,定义要激活哪些clang-tidy检查,如下所示:
*
-altera-id-dependent-backward-branch
-altera-struct-pack-align
-altera-unroll-loops
-android-*

第一行启用了所有可用的检查,而其他行禁用了我不想要的检查。

当然,这只适用于类Unix系统。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接