如何避免CMake未选中后恢复的子目录重新构建?
问题背景
通过选项控制add_subdirectory的调用,关闭选项后重新配置会导致子目录的目标文件失效,重新开启时触发全量编译,ccache仅能部分缓解,仍有对象文件复制和链接耗时问题,希望保留子目录文件有效性,仅暂不使用。
可行方案
1. 始终引入子目录,通过选项控制目标的启用/链接
不要通过条件判断完全跳过add_subdirectory,而是始终执行该命令引入子项目的所有目标,再通过选项控制是否将子目标链接到主程序,或是否包含在默认构建中:
# 始终引入所有子目录,无论选项状态 add_subdirectory(foo) add_subdirectory(bar) # 定义控制选项 option(BUILD_FOO "启用foo组件构建" ON) option(BUILD_BAR "启用bar组件构建" ON) # 控制主目标是否链接子组件 add_executable(main main.cpp) if(BUILD_FOO) target_link_libraries(main PRIVATE foo) endif() if(BUILD_BAR) target_link_libraries(main PRIVATE bar) endif() # 可选:关闭选项时将子目标排除在默认构建外 if(NOT BUILD_FOO) set_target_properties(foo PROPERTIES EXCLUDE_FROM_ALL ON) # 若foo有依赖子目标,需同步设置 set_target_properties(foo_dep PROPERTIES EXCLUDE_FROM_ALL ON) endif()
原理:子目录的目标规则和已生成的对象文件会始终保留在CMake缓存与Ninja构建文件中,关闭选项仅会让子目标不参与默认构建、不被主程序链接,重新开启时只需重新链接主目标,无需重新编译子目标的对象文件。
2. 使用OBJECT目标分离编译与链接
将子目录代码编译为OBJECT目标,主项目根据选项决定是否链接这些预编译的对象文件:
# 子目录CMakeLists.txt中定义OBJECT目标 add_library(foo_obj OBJECT foo.cpp foo.h) # 主项目中控制是否使用 option(USE_FOO "使用foo组件" ON) add_executable(main main.cpp) if(USE_FOO) # CMake 3.12+支持直接链接OBJECT目标 target_link_libraries(main PRIVATE foo_obj) endif()
原理:OBJECT目标的产物是独立的对象文件,即使关闭选项,这些文件也不会被清理,重新开启时直接链接即可,完全避免子组件的重新编译。
3. 指定固定二进制目录隔离子项目构建
如果必须保留条件式add_subdirectory调用,可为每个可选子目录指定固定的二进制目录,避免CMake清理已生成的构建产物:
option(BUILD_FOO "构建foo组件" ON) if(BUILD_FOO) # 指定固定的二进制目录,而非默认的自动生成目录 add_subdirectory(foo ${CMAKE_BINARY_DIR}/foo_build) endif()
原理:默认情况下,add_subdirectory会在二进制目录下创建与子目录同名的文件夹,当移除该调用时,CMake会标记该目录为废弃并可能清理。指定固定目录后,即使暂时关闭选项,目录内的构建产物会被保留,重新开启时CMake会直接复用现有文件,避免全量编译。
原问题根源
当通过条件判断移除add_subdirectory调用时,CMake会从缓存中删除该子目录的所有目标、变量和规则,Ninja构建文件也会同步移除相关依赖配置。重新开启选项时,CMake需要重新解析子目录的CMakeLists.txt并生成全新的构建规则,Ninja会判定这些目标的构建信息为新内容,从而触发全量编译。
内容的提问来源于stack exchange,提问作者PasterOfMuppets

