如何避免CMake主项目的编译器定义及多余包含目录传入第三方子项目?
针对你遇到的两个问题,我分享几个实用的CMake解决方案,都是日常项目中常用的:
一、阻止主项目编译器定义传递给子项目
CMake默认会让add_subdirectory引入的子项目继承父项目的全局编译定义(比如通过add_definitions或CMAKE_CXX_FLAGS设置的),这很容易导致第三方子项目编译失败。这里有两种解决思路:
1. 临时隔离全局编译定义(适合无法修改子项目的情况)
在引入子项目前,先保存当前的全局编译定义,清空后再引入子项目,最后恢复原定义:
# 保存主项目的全局C++编译定义 set(ORIGINAL_CXX_DEFS ${CMAKE_CXX_COMPILE_DEFINITIONS}) # 清空全局编译定义,避免传递给子项目 set(CMAKE_CXX_COMPILE_DEFINITIONS "") # 引入第三方子项目 add_subdirectory(path/to/your/third-party-subproject) # 恢复主项目原来的全局编译定义 set(CMAKE_CXX_COMPILE_DEFINITIONS ${ORIGINAL_CXX_DEFS})
如果子项目同时涉及C语言,记得同步处理CMAKE_C_COMPILE_DEFINITIONS。
2. 用目标级定义替代全局设置(推荐,适合可修改子项目的情况)
如果能修改第三方子项目的CMakeLists.txt,建议把所有编译定义从全局的add_definitions改成目标绑定的target_compile_definitions,比如:
# 子项目中原来的写法(全局定义) add_definitions(-DSOME_GLOBAL_DEF) # 修改为目标级定义(仅作用于子项目自身的目标) target_compile_definitions(third-party-target PRIVATE -DSOME_LOCAL_DEF)
这样主项目的全局定义就不会影响子项目的目标,同时子项目的定义也不会反向污染主项目。
二、避免主项目包含目录传递给子项目
和编译定义的问题类似,全局的include_directories设置会被子项目继承,导致子项目编译时带入大量额外包含目录。解决方法如下:
1. 临时隔离全局包含目录
同样采用保存-清空-恢复的思路:
# 保存主项目的全局包含目录 set(ORIGINAL_INCLUDES ${CMAKE_INCLUDE_DIRECTORIES}) # 清空全局包含目录 set(CMAKE_INCLUDE_DIRECTORIES "") # 引入第三方子项目 add_subdirectory(path/to/your/third-party-subproject) # 恢复主项目原来的全局包含目录 set(CMAKE_INCLUDE_DIRECTORIES ${ORIGINAL_INCLUDES})
2. 子项目使用目标级包含目录
如果能修改子项目,把全局的include_directories改成target_include_directories,绑定到子项目自身的目标上:
# 子项目中原来的写法(全局包含) include_directories(./include ./third-party) # 修改为目标级包含(仅作用于子项目目标) target_include_directories(third-party-target PRIVATE ./internal-include PUBLIC ./public-include )
这样主项目的全局包含目录不会传递给子项目,同时子项目的公开包含目录只会在主项目链接它的目标时才会被引入,更符合现代CMake的最佳实践。
3. 完全隔离的外部项目构建(终极方案)
如果上述方法都不适用,你可以用ExternalProject_Add来单独构建第三方子项目,完全脱离主项目的编译环境:
include(ExternalProject) ExternalProject_Add( third-party-subproject SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/path/to/subproject CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR}/subproject-install BUILD_ALWAYS OFF ) # 之后主项目只需要链接子项目的安装产物即可 include_directories(${CMAKE_CURRENT_BINARY_DIR}/subproject-install/include) link_directories(${CMAKE_CURRENT_BINARY_DIR}/subproject-install/lib) target_link_libraries(main-project PRIVATE third-party-library)
这种方式下,子项目的编译完全独立,不会受到主项目任何全局设置的影响。
总的来说,推荐优先使用目标导向的CMake写法,尽量避免全局设置,这样不仅能解决当前的问题,还能让项目的依赖关系更清晰,减少后续维护的麻烦。
内容的提问来源于stack exchange,提问作者J.Alex

