如何扁平化CMake超级构建?实现单一构建系统简化复杂度
我太懂你这种超级构建带来的混乱感了——之前折腾跨平台项目时,也被双层构建文件搞得头大:一边是超级构建的Makefile/解决方案,另一边是实际项目的构建文件,找个目标都要翻半天。你用ExternalProject_Add搞定了跨平台依赖拉取的核心需求,这点已经很棒了,下面给你几个方案来砍掉双层结构,只用一套构建系统:
优先选:用FetchContent替代ExternalProject_Add(CMake 3.11+)
FetchContent是CMake官方后来推出的工具,专门解决ExternalProject_Add的超级构建分层问题——它能把依赖的构建直接嵌入到主项目的构建系统里,不会生成单独的超级构建流程。
针对你的googletest依赖,替换后的代码大概是这样:
include(FetchContent) # 声明要拉取的依赖 FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG 718fd88d8f145c63b8cc134cf8fed92743cc112f ) # 配置gtest的编译选项,和你之前的参数对应 set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # 禁止把gtest安装到系统目录,避免污染 set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) # 拉取、构建并导入依赖到主项目 FetchContent_MakeAvailable(googletest)
这样一来,googletest的构建目标会直接加入到你主项目的构建系统中,所有构建文件都统一放在CMAKE_BINARY_DIR下,完全没有双层结构的困扰,同时还保留了自动拉取依赖、跨平台兼容的优点,亲测在Linux和Windows上都能正常跑。
退而求其次:优化ExternalProject_Add的集成方式
如果你暂时不想切换到FetchContent,也可以调整ExternalProject_Add的配置,让它的构建结果更紧密地集成到主项目里,减少混乱:
- 把依赖的安装目录设置到主项目的依赖文件夹,比如你原来的
${CMAKE_BINARY_DIR}/Dependencies/googletest - 在主CMakeLists.txt里,通过
find_package导入已安装的依赖,并把ExternalProject的目标设为主项目目标的依赖:
# 先定义ExternalProject_Add(保留你原来的配置,注意修复引号语法错误) ExternalProject_Add(googletest PREFIX "${CMAKE_BINARY_DIR}/Downloads/googletest" GIT_REPOSITORY "https://github.com/google/googletest.git" GIT_TAG 718fd88d8f145c63b8cc134cf8fed92743cc112f BINARY_DIR "${CMAKE_BINARY_DIR}/Downloads/googletest/${CMAKE_CFG_INTDIR}/build" CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/Dependencies/googletest" "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" "-DCMAKE_DEBUG_POSTFIX=''" "-Dgtest_force_shared_crt=ON" ) # 导入安装好的gtest find_package(GTest REQUIRED PATHS "${CMAKE_BINARY_DIR}/Dependencies/googletest") # 把googletest设为主项目目标的依赖,确保主构建前先构建依赖 add_dependencies(your_main_target googletest)
这种方式虽然还是会有双层构建文件,但至少主项目的构建流程会自动触发依赖构建,不用手动先跑超级构建,稍微减少一些混乱。
手动控版本:Git子模块+本地CMake集成
如果你希望完全掌控依赖的代码版本,甚至需要修改依赖源码,Git子模块是个不错的选择:
- 先添加子模块到你的项目:
git submodule add https://github.com/google/googletest.git Dependencies/googletest # 拉取子模块代码 git submodule update --init --recursive
- 然后在主CMakeLists.txt里直接导入子模块的CMake配置:
add_subdirectory(Dependencies/googletest) # 配置gtest的编译选项 set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
这样依赖的构建会完全融入主项目的构建系统,没有任何双层结构。缺点是需要手动管理子模块的版本更新,新克隆项目的人也要记得拉取子模块,但胜在直观,依赖代码就在本地,调试起来很方便。
结合你的项目CMakeLists.txt,只需要把原来的ExternalProject_Add相关代码替换成上面任意一种方案,就能实现单一构建系统的目标了。
内容的提问来源于stack exchange,提问作者Brent

