【问题标题】:How to integrate clang-tidy to CMake and GCC?如何将 clang-tidy 集成到 CMake 和 GCC?
【发布时间】:2019-11-26 15:47:20
【问题描述】:

我想将 clang-tidy 集成到我们的 C 和 C++、基于 CMake 的项目中,该项目是使用自定义 GCC 工具链编译的。

我试过关注this tutorial,设置CMAKE_CXX_CLANG_TIDY。我还尝试通过将CMAKE_EXPORT_COMPILE_COMMANDS 设置为ON 并将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 &lt;dir&gt; 的列表添加到标志列表中。我还添加了-nostdinc,这样 clang-tidy 就不会尝试查看他自己的标题而不是我的
      • 这有助于解决上述问题,因为现在在工具链的标头中定义了各种定义,例如 O_CLOEXEC,但由于我的工具链基于 GCC,clang 无法解析包含对许多调用的 &lt;type_traits&gt; 标头编译器内在函数
    • 我不确定在这种情况下最好的方法是什么

@shycha:感谢您的提示,我将尝试禁用此特定检查,然后再次编辑此帖子

【问题讨论】:

  • 如果您添加了您遇到的错误,将会非常有帮助。你也试过用clang编译代码吗? Clang-tidy 使用相同的前端来生成它的 AST,所以如果你可以用 clang 编译,你应该不会有 clang-tidy 的问题。
  • 因为这是周末,我会在回到工作岗位时尝试发布错误。同时 - 我不使用 Clang 编译代码,而是使用自定义 GCC 工具链。它可能不会用 clang 编译,因为 CMakeLists.txt 文件添加了 GCC 特定的编译标志。我可能可以使用$&lt;CMAKE_C/CXX_COMPILER_ID&gt; 来检查编译器,但是当使用CMAKE_CXX_CLANG_TIDY 变量时,根据我的理解,标志是根据编译器获取的
  • @cereagni 我真的很想看看在与 GCC 交叉编译时如何将 clang-tidy 与 cmake 集成。使用set(CMAKE_CXX_CLANG_TIDY "clang-tidy;--enable-check-profile;--checks=-*,modernize-use-auto") 时是否编译?这应该会关闭所有检查,只留下clang-diagnostic-*modernize-use-auto。 (不知何故禁用所有检查不起作用......)
  • 我有同样的问题,我看到有一个 -extra-arg 但我想要的是像 -ignore-arg 这样的东西来删除传递给 gcc 的标志,该标志不受 clang/clang 支持-整洁。

标签: c++ gcc cmake clang clang-tidy


【解决方案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}
)

这将为 clang-tidy 检查创建一个单独的构建目标 (make lint)。 clang-tidy 我的项目需要很长时间,所以我不想在每次构建时都运行它; make lint 可以根据需要手动运行,并且在每次推送到 repo 后也会在 CI 作业中执行(以某种方式使 CI 管道失败,阻止合并,如果有任何发现)。

make lint 的输出是包含尽可能少上下文的整洁发现列表。完整的输出(包括发现的上下文)在 lint.out 中,错误消息在 lint.err 中,我将它们保存为 CI 工件。

checks.txt 是项目根目录中的一个文本文件,用于定义要激活的 clang-tidy 检查,如下所示:

*
-altera-id-dependent-backward-branch
-altera-struct-pack-align
-altera-unroll-loops
-android-*

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

当然只能在类 Unix 系统中工作。

【讨论】:

    【解决方案2】:

    好的,我想我有一个解决方案。几个晚上后,我能够让它工作。

    一般我是这样编译的

    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()
    

    第三,/lib/ld-musl-armhf.so.1硬编码在set(CMAKE_LINKER_ARM_COMPAT_STATIC ...)中;在我使用的开发框上,它指向/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 ...),但我不知道是否有这样的配置选项.

    一般建议

    不要立即集成它。首先使用一个库(最好是 C++ 一个)测试一些简单的 CMake 项目并使其工作,然后添加第二个库,但在 C 中,再次调整它。之后才将其合并到代码库中。

    工具链

    我使用了带有GCC 8.3.0musl C 库的自定义工具链,因此某些文件的位置可能与其他工具链不同。

    自定义 CMake

    一些变量,例如(已经提到的)CONFIG_SCRIPT_PRELOADEDEXPORT_PACKAGE_TO_GLOBAL_REGISTRYDO_NOT_BUILD_TESTSDO_NOT_BUILD_BENCHMARKS不是通用 CMake 选项,即我只在我的 @987654353 中使用它们@,所以你可以放心地忽略它们。

    在每个 *.cmake 文件末尾未设置的变量,例如 build_testextra_clang_tidy_unchecks_for_tests_only,不需要出现在项目的主 CMakeLists.txt 中。

    叮当

    $ 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
    

    文件

    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)
    

    【讨论】:

      猜你喜欢
      • 2018-03-30
      • 1970-01-01
      • 2020-07-26
      • 1970-01-01
      • 2018-09-10
      • 1970-01-01
      • 2021-09-06
      • 2017-01-24
      • 2011-10-25
      相关资源
      最近更新 更多