CMake搭建可配置项目 避免每次运行cmake触发全量重编译
你遇到的每次运行cmake就触发全量重编译的核心诱因,是最后添加的两行unset(... CACHE)代码,和全局include_directories的使用没有直接关系。
CMake在生成构建规则时,会为每个目标的编译参数(含头文件搜索路径、宏定义等)计算哈希值作为增量判断依据:你每次配置末尾清空缓存变量后,下次重新配置时变量会经历“不存在→被-D参数赋值→全局注入头文件路径”的过程,全局编译参数的哈希值每次配置都会发生变化,构建系统自然会判定所有目标需要重新编译。
你之前遇到的“注释掉-D参数仍读取旧缓存值”是CMake缓存机制的正常设计,目的是避免重复输入配置参数,用清空缓存的方式处理属于典型的暴力错解。
完全可以对齐Autotools的增量构建行为,不需要自己写缓存值一致性判断逻辑,按以下步骤调整即可:
1. 立即删除错误的unset逻辑
直接删掉CMakeLists.txt末尾的两行unset代码,这是导致全量重编译的直接原因。
2. 规范缓存变量定义,替换全局构建命令
CMake 2.8时代遗留的全局include_directories、link_directories会将属性注入到所有后续构建目标,不仅容易引发头文件冲突,也会放大配置变更的影响范围,3.14版本下完全可以用目标级属性替代。
首先统一变量命名(不要用横杠,CMake语法中横杠与减号语义一致,极易触发逻辑bug,统一使用下划线命名是社区通用约定),明确定义缓存变量的类型和说明:
cmake_minimum_required(VERSION 3.14) project(myProject) # 语言标准等全局配置放这里 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 定义依赖相关缓存变量,给空值作为默认禁用状态 set(SOME_DEPENDENCY "" CACHE PATH "Path to dependencyOne installation root") set(SOME_OTHER_DEPENDENCY "" CACHE PATH "Path to dependencyTwo installation root") option(SOME_FLAG "Enable some build flag" OFF) option(SOME_OTHER_FLAG "Enable another build flag" OFF)
为每个依赖创建独立的IMPORTED目标,将头文件路径、链接路径等属性绑定到依赖目标上,不要做全局注入:
# 处理第一个依赖 if(SOME_DEPENDENCY) # 对齐Autotools的路径校验逻辑 if(NOT IS_DIRECTORY "${SOME_DEPENDENCY}/include") message(FATAL_ERROR "Invalid dependencyOne path: ${SOME_DEPENDENCY}, must contain include/ directory") endif() # 定义导入目标,静态库就把SHARED改成STATIC,后缀对应改成.a add_library(depOne SHARED IMPORTED) set_target_properties(depOne PROPERTIES IMPORTED_LOCATION "${SOME_DEPENDENCY}/lib/libdepone.so" INTERFACE_INCLUDE_DIRECTORIES "${SOME_DEPENDENCY}/include" INTERFACE_LINK_DIRECTORIES "${SOME_DEPENDENCY}/lib" ) endif() # 处理第二个依赖 if(SOME_OTHER_DEPENDENCY) if(NOT IS_DIRECTORY "${SOME_OTHER_DEPENDENCY}/include") message(FATAL_ERROR "Invalid dependencyTwo path: ${SOME_OTHER_DEPENDENCY}, must contain include/ directory") endif() add_library(depTwo SHARED IMPORTED) set_target_properties(depTwo PROPERTIES IMPORTED_LOCATION "${SOME_OTHER_DEPENDENCY}/lib/libdeptwo.so" INTERFACE_INCLUDE_DIRECTORIES "${SOME_OTHER_DEPENDENCY}/include" INTERFACE_LINK_DIRECTORIES "${SOME_OTHER_DEPENDENCY}/lib" ) endif()
后续业务目标需要依赖哪个库,直接用target_link_libraries关联对应导入目标即可,头文件路径、链接路径会自动传递,不需要单独给每个目标加包含路径:
add_executable(your_app src/main.cpp) # 需要用depOne就关联,不需要就去掉,不会影响其他目标 target_link_libraries(your_app PRIVATE depOne)
这种实现下,只有真正关联了对应依赖的目标,才会继承依赖的编译、链接属性,修改单个依赖配置时,只会触发关联目标的重编译,不会出现全量重编的问题。
3. 调整配置脚本逻辑,显式传递配置状态
CMake缓存的设计逻辑是“用户不传参数就保留上次配置”,如果需要实现“注释掉脚本里的依赖路径就自动禁用依赖”的效果,不要让CMake猜逻辑,在bash脚本里显式传值覆盖缓存即可:
#!/bin/bash # 不需要某个依赖就把对应路径行注释掉,或者留空 DEP_ONE_PATH=/home/deps/dependencyOne DEP_TWO_PATH=/home/deps/dependencyTwo args=( -DSOME_FLAG=yes -DSOME_OTHER_FLAG=ok # 建议用-S/-B参数显式指定源码、构建目录,禁止in-source构建 -S . -B build ) # 依赖路径非空就传路径,空值就传OFF显式禁用,覆盖旧缓存 if [ -n "${DEP_ONE_PATH}" ]; then args+=(-DSOME_DEPENDENCY="${DEP_ONE_PATH}") else args+=(-DSOME_DEPENDENCY=OFF) fi if [ -n "${DEP_TWO_PATH}" ]; then args+=(-DSOME_OTHER_DEPENDENCY="${DEP_TWO_PATH}") else args+=(-DSOME_OTHER_DEPENDENCY=OFF) fi cmake "${args[@]}"
- 非必要不要在
set(CACHE)命令中加FORCE参数,效果和unset类似,会强制覆盖缓存值,极易触发不必要的重编译,也会破坏用户在cmake-gui/ccmake中手动修改配置的能力。 - 3.14版本下已经支持
target_link_directories等目标级命令,所有编译、链接属性优先绑定到具体目标,不要用任何全局属性命令,从根源上避免配置变更的影响范围扩散。 - 如果你之前已经在构建目录做过多次错误配置,建议第一次调整完后删掉整个build目录重新配置一次,避免旧的错误缓存残留影响结果。
内容的提问来源于stack exchange,提问作者vkn

