CMake如何将编译器自动添加的系统包含目录指定为-isystem类型
问题背景
已知通过SYSTEM参数标记包含目录,可实现第三方库头文件的警告过滤,避免开启警告视为错误时第三方库的警告打断编译:
target_include_directories(my_target SYSTEM PRIVATE ${SOME_THIRD_PARTY_LIBRARY_INCLUDES})
开启警告视为错误的配置示例:
list(APPEND CXX_COMPILER_FLAGS_TO_USE "-Werror")
遇到的问题
上述方案无法覆盖CMake自动检测识别的系统包含目录。比如CentOS 7环境下使用devtoolset-10提供的GCC 10时,开启-Wundef选项会触发系统头文件报错:
list(APPEND CXX_COMPILER_FLAGS_TO_USE "-Wundef")
具体报错信息如下:
In file included from /opt/rh/devtoolset-10/root/usr/include/c++/10/x86_64-redhat-linux/bits/c++config.h:2791, from /opt/rh/devtoolset-10/root/usr/include/c++/10/cmath:41, ... /opt/rh/devtoolset-10/root/usr/include/c++/10/pstl/pstl_config.h:47:5: error: "__clang__" is not defined, evaluates to 0 [-Werror=undef] 47 | #if __clang__ | ^~~~~~~~~ compilation terminated due to -Wfatal-errors. cc1plus: all warnings being treated as errors
报错的头文件路径/opt/rh/devtoolset-10/root/usr/include/是CMake自动检测添加,没有在配置文件中手动声明。手动硬编码路径标记为SYSTEM目录可以临时解决,但不具备通用性,也不符合配置规范:
target_include_directories(my_target SYSTEM PRIVATE /opt/rh/devtoolset-10/root/usr/include)
补充说明:编译器是在根目录CMakeLists.txt最开头手动指定,避免调用CentOS自带的低版本GCC 4.8:
SET(CMAKE_CXX_COMPILER "/opt/rh/devtoolset-10/root/usr/bin/g++") SET(CMAKE_C_COMPILER "/opt/rh/devtoolset-10/root/usr/bin/gcc")
解决方案
方案1:使用CMake原生特性(适用CMake 3.26及以上版本)
CMake 3.26版本新增了CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES_IS_SYSTEM和CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES_IS_SYSTEM全局变量,默认值为ON,会自动将编译器的隐式包含目录标记为SYSTEM类型。
如果使用该版本及以上的CMake,无需额外配置即可解决问题。如果特性未生效,可以在CMakeLists.txt的project指令之前手动开启:
set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES_IS_SYSTEM ON) set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES_IS_SYSTEM ON)
方案2:低版本CMake通用兼容方案
如果无法升级CMake版本,可以通过读取编译器隐式包含目录属性批量标记为SYSTEM:
# 替换my_target为你的实际目标名 get_target_property(target_includes my_target INCLUDE_DIRECTORIES) get_property(implicit_cxx_dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY CXX_IMPLICIT_INCLUDE_DIRECTORIES) foreach(dir ${implicit_cxx_dirs}) list(FIND target_includes ${dir} dir_index) if(${dir_index} EQUAL -1) target_include_directories(my_target SYSTEM PRIVATE ${dir}) endif() endforeach()
该方案不需要硬编码路径,自动适配当前编译器的所有隐式系统包含目录,跨环境通用。
方案3:编译选项临时规避
如果仅需要解决指定警告触发的系统头文件报错,可以直接添加GCC/Clang原生支持的忽略系统头文件警告选项:
list(APPEND CXX_COMPILER_FLAGS_TO_USE "-Wundef" "-Wno-system-headers")
-Wno-system-headers会直接忽略所有系统头文件产生的警告,不需要修改包含目录配置,适配所有CMake版本。
内容的提问来源于stack exchange,提问作者Alvaro Palma Aste

