CMake跨相邻项目依赖:避免重复构建与冗余编译
问题描述
现有两个相邻仓库分别包含projectA和projectB:
- projectA通过CMake构建3个静态库(lib1、lib2、lib3),单独构建时会在build目录生成各自的
*.a文件 - projectB通过CMake构建可执行文件,仅依赖projectA的lib1和lib3
当前采用add_subdirectory(${CMAKE_SOURCE_DIR}/../projectA projectA)引入依赖,再用target_link_libraries(${PROJECT_NAME} lib1 lib3)链接,但存在两个问题:
- 新增projectC时会重复构建projectA的库
- 构建projectB/C时会编译projectA中未被使用的lib2
需求:让projectA的库仅在自身目录构建,同时projectB/C可按需构建所需的库(仅构建未完成或源码变更的目标)
解决方案
1. 让projectA支持单独构建并生成可复用的安装包
在projectA的CMakeLists.txt中添加安装规则,将库文件、头文件和CMake配置模块统一安装到指定目录(比如项目内的install子目录):
# projectA/CMakeLists.txt cmake_minimum_required(VERSION 3.14) project(projectA) # 构建lib1 add_library(lib1 STATIC src/lib1.cpp) target_include_directories(lib1 PUBLIC include) # 构建lib2 add_library(lib2 STATIC src/lib2.cpp) target_include_directories(lib2 PUBLIC include) # 构建lib3 add_library(lib3 STATIC src/lib3.cpp) target_include_directories(lib3 PUBLIC include) # 配置安装路径与规则 set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/install) # 安装库文件到lib目录 install(TARGETS lib1 lib2 lib3 ARCHIVE DESTINATION lib) # 安装头文件到include目录 install(DIRECTORY include/ DESTINATION include) # 导出CMake目标配置,供其他项目查找 install(EXPORT projectATargets FILE projectATargets.cmake DESTINATION lib/cmake/projectA) # 生成Config.cmake文件,让其他项目能通过find_package识别projectA include(CMakePackageConfigHelpers) configure_package_config_file( ${PROJECT_SOURCE_DIR}/Config.cmake.in ${PROJECT_BINARY_DIR}/projectAConfig.cmake INSTALL_DESTINATION lib/cmake/projectA ) install(FILES ${PROJECT_BINARY_DIR}/projectAConfig.cmake DESTINATION lib/cmake/projectA)
同时在projectA根目录创建Config.cmake.in文件(内容为空即可,后续可按需添加配置逻辑)。
完成配置后,在projectA目录下单独构建并安装:
mkdir build && cd build cmake .. make make install
2. 在projectB/C中通过find_package按需引入依赖
在projectB的CMakeLists.txt中,通过find_package定位projectA的安装包,仅链接自身需要的库:
# projectB/CMakeLists.txt cmake_minimum_required(VERSION 3.14) project(projectB) # 指定projectA的CMake配置文件路径 set(projectA_DIR ${CMAKE_SOURCE_DIR}/../projectA/install/lib/cmake/projectA) # 查找并导入projectA的配置 find_package(projectA REQUIRED) # 构建可执行文件 add_executable(projectB src/main.cpp) # 仅链接需要的lib1和lib3 target_link_libraries(projectB PRIVATE lib1 lib3)
projectC的配置逻辑与projectB一致,只需修改项目名称和需要链接的库即可。
3. 可选:用ExternalProject实现自动按需构建依赖
如果不想手动提前构建projectA,可以借助ExternalProject让projectB/C在需要时自动触发projectA的指定库构建,同时复用已有产物:
# projectB/CMakeLists.txt cmake_minimum_required(VERSION 3.14) project(projectB) include(ExternalProject) ExternalProject_Add( projectA SOURCE_DIR ${CMAKE_SOURCE_DIR}/../projectA BINARY_DIR ${CMAKE_SOURCE_DIR}/../projectA/build CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_SOURCE_DIR}/../projectA/install INSTALL_COMMAND make install BUILD_COMMAND make lib1 lib3 # 仅构建当前需要的库 ) # 导入projectA的库配置 set(projectA_DIR ${CMAKE_SOURCE_DIR}/../projectA/install/lib/cmake/projectA) find_package(projectA REQUIRED) add_executable(projectB src/main.cpp) target_link_libraries(projectB PRIVATE lib1 lib3) # 让projectB的构建依赖projectA的指定目标 add_dependencies(projectB projectA)
这样构建projectB时,只会编译projectA的lib1和lib3,且projectA的构建产物会被所有依赖项目复用,新增projectC时不会重复构建已完成的库。
关键原理
- 通过
install和find_package实现依赖的全局共享,避免每个项目重复构建projectA - 利用CMake的目标依赖机制,仅触发当前项目需要的库构建,跳过未使用的lib2
ExternalProject可自动触发依赖构建,同时支持指定构建目标,减少冗余编译
内容的提问来源于stack exchange,提问作者dazed_and_confused

