多子目录单可执行文件CMake配置:编译慢与宏定义问题排查
一、编译速度慢的优化方案
1. 将共享库改为静态库或对象库
当前子模块均使用SHARED库,每个库单独编译、链接会带来额外开销。对于单可执行文件场景,改用**静态库(STATIC)或对象库(OBJECT)**可大幅减少编译链接成本:
- 静态库会打包所有目标文件,最终直接链接到可执行文件;
- 对象库直接生成目标文件,最后合并到可执行文件,省去库打包步骤。
修改子目录的add_library命令,例如src/control/CMakeLists.txt:
# 替换原SHARED为STATIC或OBJECT add_library(control STATIC ${control_source_files})
2. 启用并行编译
通过CMake开启多线程并行编译,利用CPU多核缩短总编译时间:
- 在顶层
src/CMakeLists.txt开头添加:
# 根据CPU核心数自动设置并行编译线程数,也可手动指定数字(如8) set(CMAKE_BUILD_PARALLEL_LEVEL ${CMAKE_PROCESSOR_COUNT})
- 或构建时通过命令行指定:
cmake --build . -j8
3. 修复依赖传递错误并优化头文件包含
现有配置存在target名称错误(如control模块中误用robot_control、first模块中误用motor作为target名),导致include目录无法正确传递,引发重复包含或头文件解析冗余。修正后通过PUBLIC/INTERFACE正确传递依赖,避免重复配置:
修正src/control/CMakeLists.txt的include目录:
# 将robot_control改为实际库名control target_include_directories(control PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../second ${CMAKE_CURRENT_SOURCE_DIR}/../third )
修正src/first/CMakeLists.txt的include目录:
# 将motor改为实际库名first target_include_directories(first PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../second ${CMAKE_CURRENT_SOURCE_DIR}/../logger ${CMAKE_CURRENT_SOURCE_DIR}/../../3rd_party/date/include/ )
修正后,可执行文件链接库时会自动继承其include目录,无需重复设置。
4. 第三方库优化:禁用不必要特性
针对spdlog,禁用示例、测试等非必要功能以减少编译时间:
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../3rd_party/spdlog ${CMAKE_CURRENT_BINARY_DIR}/spdlog) # 禁用spdlog冗余构建目标 set(SPDLOG_BUILD_EXAMPLES OFF CACHE BOOL "") set(SPDLOG_BUILD_TESTS OFF CACHE BOOL "") set(SPDLOG_BUILD_BENCH OFF CACHE BOOL "")
二、宏定义(DEBUG/RELEASE)不生效的解决方案
1. 基于构建类型自动设置宏
利用CMake的CMAKE_BUILD_TYPE配合生成器表达式,自动为不同构建类型定义宏,确保所有子模块都能获取到宏定义:
在顶层src/CMakeLists.txt添加:
# 根据构建类型自动定义对应宏 add_compile_definitions( $<$<CONFIG:Debug>:DEBUG> $<$<CONFIG:Release>:RELEASE> $<$<CONFIG:RelWithDebInfo>:RELEASE> )
构建时指定类型即可自动生效,例如:
- Debug模式:
cmake -DCMAKE_BUILD_TYPE=Debug ..,自动定义DEBUG宏 - Release模式:
cmake -DCMAKE_BUILD_TYPE=Release ..,自动定义RELEASE宏
2. 确保宏定义传递到所有子库
若需手动定义宏,不要仅给可执行文件设置,需通过全局编译定义或子库传递:
- 全局设置(所有目标继承):
add_compile_definitions(RELEASE)
- 子库单独设置(以control模块为例):
target_compile_definitions(control PUBLIC RELEASE)
3. 修正原有宏定义的无效问题
原配置中仅给可执行文件设置target_compile_definitions(${PROJECT_NAME} PUBLIC RELEASE),但子库是单独编译的,无法继承该定义。通过上述两种方法,可确保宏定义在子库编译阶段生效。
三、优化后的顶层src/CMakeLists.txt示例
cmake_minimum_required(VERSION 3.18) project(integration LANGUAGES CXX) # 启用并行编译 set(CMAKE_BUILD_PARALLEL_LEVEL ${CMAKE_PROCESSOR_COUNT}) # 基于构建类型自动设置宏 add_compile_definitions( $<$<CONFIG:Debug>:DEBUG> $<$<CONFIG:Release>:RELEASE> ) # 配置spdlog构建选项 set(SPDLOG_BUILD_EXAMPLES OFF CACHE BOOL "") set(SPDLOG_BUILD_TESTS OFF CACHE BOOL "") set(SPDLOG_BUILD_BENCH OFF CACHE BOOL "") add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../3rd_party/spdlog ${CMAKE_CURRENT_BINARY_DIR}/spdlog) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) # 添加子模块 add_subdirectory(first) add_subdirectory(second) add_subdirectory(third) add_subdirectory(control) add_subdirectory(logger) set(src_main main.cpp) add_executable(${PROJECT_NAME} ${src_main}) target_link_libraries(${PROJECT_NAME} PRIVATE control first second third logger Threads::Threads spdlog::spdlog )
四、关键注意事项
- 所有子模块的
target_include_directories必须与add_library定义的库名一致,否则依赖传递失效; - 优先使用
PUBLIC/INTERFACE/PRIVATE控制依赖范围,避免重复配置; - 构建时明确指定
CMAKE_BUILD_TYPE,确保宏定义与编译优化策略正确生效。
内容的提问来源于stack exchange,提问作者user2424097

