
1. 项目概述为什么我们需要一个“可复用”的CMake工程实践如果你用C做过稍微大一点的项目或者尝试过整合几个开源库大概率已经和CMake打过交道了。它可能是你项目根目录里那个神秘的CMakeLists.txt文件也可能是你在CLion或VSCode里点击“Configure”后面对满屏红色错误输出时的困惑来源。CMake本身并不直接构建你的代码它是一个“构建系统的构建系统”或者说是一个高级的构建描述生成器。它的核心价值在于你用一套相对简洁的语法描述你的项目结构、依赖和构建目标然后CMake能为你生成对应平台如Windows上的Visual Studio项目、macOS上的Xcode项目、Linux上的Makefile或Ninja文件的原生构建脚本。那么为什么还要专门谈“最佳工程实践”并且强调“可复用”呢我见过太多项目里的CMake脚本了有的把所有源码路径、库路径、编译选项都写成绝对路径换个机器就彻底瘫痪有的一个CMakeLists.txt文件写了上千行逻辑缠绕得像一团乱麻没人敢动还有的完全没做模块化新增一个功能模块就得在五六个地方手动添加文件。这些做法在项目初期或许能跑起来但随着项目规模扩大、团队成员增加、外部依赖变多维护成本会呈指数级上升最终成为阻碍项目发展的技术债。一个“可复用”的CMake工程实践其目标就是建立一套清晰、健壮、可扩展的构建基础设施。它意味着新人友好新成员克隆代码后几条标准命令就能完成环境配置和构建无需深究构建脚本的细节。跨平台一致在Windows、macOS、Linux上构建体验和结果高度一致。依赖管理清晰无论是内部模块还是第三方库依赖关系明确更新和替换依赖的风险可控。结构可扩展新增库、可执行程序、测试用例时有章可循只需在固定位置添加少量配置无需修改核心构建逻辑。与IDE无缝集成生成的工程文件能很好地被CLion、VSCode、Visual Studio等现代IDE识别和利用提供代码补全、跳转、调试等支持。接下来我将拆解一套经过多个中大型C项目验证的CMake工程实践。这套方案不是唯一的真理但它解决了上述大部分痛点你可以直接拿过去作为你下一个项目的起点或者对照优化你现有的项目。2. 工程结构设计与核心思想在动手写第一行CMake代码之前我们先要规划好项目的物理和逻辑结构。一个混乱的目录结构会让再好的CMake脚本也无用武之地。2.1 推荐的目录结构一个清晰的大型项目目录结构通常如下所示MyLargeProject/ ├── CMakeLists.txt # 根CMake文件项目入口 ├── cmake/ # 存放自定义的CMake模块和脚本 │ ├── FindXXX.cmake # 自定义的Find模块如需 │ └── MyProjectConfig.cmake.in # 包配置文件模板 ├── third_party/ # 第三方依赖源码或预编译库 │ ├── googletest/ │ └── json/ ├── src/ # 项目主要源代码 │ ├── core/ # 核心库不依赖项目内其他库 │ │ ├── CMakeLists.txt │ │ ├── include/core/ │ │ └── src/ │ ├── network/ # 网络库可能依赖core │ │ ├── CMakeLists.txt │ │ ├── include/network/ │ │ └── src/ │ └── app/ # 可执行程序入口 │ ├── CMakeLists.txt │ └── main.cpp ├── tests/ # 测试代码 │ ├── unit/ # 单元测试 │ │ ├── CMakeLists.txt │ │ └── test_core.cpp │ └── integration/ # 集成测试 │ ├── CMakeLists.txt │ └── test_integration.cpp ├── tools/ # 构建工具、脚本等 ├── build/ # 构建输出目录通常.gitignore └── README.md这个结构背后的核心思想是“分离”与“聚合”分离将不同职责的代码核心库、功能模块、应用、测试放在不同的目录中每个目录都是一个相对独立的CMake子项目通过add_subdirectory管理。聚合通过顶层的CMakeLists.txt统一管理编译选项、全局变量并组织所有子目录。第三方依赖隔离将第三方代码集中放在third_party下便于管理和清理。构建产物隔离强烈建议使用out-of-source build即在项目根目录下创建一个独立的build目录进行构建避免污染源代码树。这也是为什么我们通常把build/加入.gitignore。2.2 CMake的现代理念Target-Based DesignCMake在3.0版本后大力推广基于“目标Target”的设计模式这是与我们旧习惯直接操作全局变量如CMAKE_CXX_FLAGS、include_directories、link_directories决裂的关键。旧模式命令式不推荐# 全局添加包含目录所有后续目标都会受影响 include_directories(${PROJECT_SOURCE_DIR}/src/core/include) # 全局添加编译选项 add_compile_options(-Wall -Wextra) # 创建可执行文件 add_executable(my_app main.cpp) # 手动为这个目标链接库 target_link_libraries(my_app some_library)新模式声明式推荐# 创建一个库目标 add_library(core_lib STATIC src/core.cpp) # 仅为这个库目标设置属性包含目录、编译选项、链接库 target_include_directories(core_lib PUBLIC include/core) target_compile_options(core_lib PRIVATE -Wall -Wextra) # 创建可执行文件目标 add_executable(my_app src/app/main.cpp) # 声明可执行文件依赖于core_lib。CMake会自动传递必要的包含目录和链接库。 target_link_libraries(my_app PRIVATE core_lib)关键区别与优势作用域精确属性如包含目录、编译选项被关联到具体的目标上而不是全局生效。这避免了无意间的污染和冲突。依赖自动传递使用target_link_libraries建立依赖关系时如果被依赖的目标如core_lib将其包含目录声明为PUBLIC或INTERFACE那么依赖它的目标如my_app会自动获得这些包含目录无需手动再次include_directories。这极大地简化了依赖管理。更好的IDE支持现代IDE能更好地解析基于目标的CMake项目为每个目标提供准确的包含路径和编译定义。实操心得强迫自己从旧模式切换到新模式。初期可能会觉得繁琐但一旦项目模块增多你会发现它的维护性远超旧模式。一个简单的原则尽量不使用include_directories、link_directories、add_compile_options这些全局命令而是使用target_xxx系列命令。3. 根CMakeLists.txt项目的总控中心项目的根CMakeLists.txt文件是构建的起点它负责设定全局规则、引入子模块。一个好的根文件应该清晰、稳定不轻易改动。3.1 基础配置与版本要求# 指定CMake的最低版本要求。使用现代特性如target-based建议至少3.10。 cmake_minimum_required(VERSION 3.10) # 定义项目名称、版本、描述和语言。 # 这里的VERSION选项是CMake 3.0的特性它会自动定义 PROJECT_VERSION, PROJECT_VERSION_MAJOR等变量。 project(MyLargeProject VERSION 1.0.0 DESCRIPTION A large-scale C project demonstrating best CMake practices LANGUAGES CXX) # 明确指定语言为C如果还有C代码则写 C CXX # 设置C标准。这是现代CMake的推荐做法与目标属性绑定。 set(CMAKE_CXX_STANDARD 17) # 或 20 set(CMAKE_CXX_STANDARD_REQUIRED ON) # 要求编译器必须支持该标准否则报错 set(CMAKE_CXX_EXTENSIONS OFF) # 禁用编译器扩展如GNU的-stdgnu17保证代码可移植性。为什么这么设置CMAKE_CXX_STANDARD_REQUIRED ON确保如果编译器不支持C17构建会立即失败而不是静默降级避免跨环境兼容性问题。CMAKE_CXX_EXTENSIONS OFF强制使用标准的ISO C避免依赖GCC或MSVC特有的扩展语法提高代码在不同编译器间的可移植性。3.2 构建类型与编译选项管理# 如果未指定构建类型默认为Debug便于开发调试。 if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING Choose the type of build FORCE) # 可以提供的选项Debug, Release, RelWithDebInfo, MinSizeRel set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release RelWithDebInfo MinSizeRel) endif() # 根据构建类型设置不同的编译选项。 # 注意这里仍然使用了全局的add_compile_options因为它作用于所有目标。 # 更精细的控制可以在每个目标的target_compile_options中设置。 if(CMAKE_BUILD_TYPE STREQUAL Debug) add_compile_options(-g -O0 -DDEBUG) # 调试符号不优化定义DEBUG宏 message(STATUS Build type: Debug) elseif(CMAKE_BUILD_TYPE STREQUAL Release) add_compile_options(-O3 -DNDEBUG) # 最高优化定义NDEBUG宏会影响assert message(STATUS Build type: Release) endif() # 添加一些通用的、与构建类型无关的警告选项GCC/Clang。 if(CMAKE_CXX_COMPILER_ID MATCHES GNU|Clang) add_compile_options(-Wall -Wextra -Wpedantic -Werror) # -Werror将警告视为错误强制代码清洁。 endif() # 对于MSVC if(MSVC) add_compile_options(/W4 /WX) # /W4 高警告等级/WX 将警告视为错误 endif()注意事项-Werror或/WX在团队协作初期可能比较激进因为它要求所有警告必须被解决。但这对于建立高质量的代码基线非常有效。你可以根据团队情况决定是否启用或者仅对CI持续集成环境启用。3.3 引入子目录与依赖管理# 将自定义的CMake模块路径加入搜索列表这样可以使用find_package或include来引入。 list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # 处理第三方依赖。这里以GoogleTest为例演示两种方式。 option(BUILD_TESTING Build the testing tree ON) # 提供一个选项允许用户关闭测试构建 if(BUILD_TESTING) # 方式一使用FetchContentCMake 3.11直接从网络获取依赖源码并编译。 # 优点无需预装版本可控完全离线构建。 include(FetchContent) FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.12.1 # 指定一个稳定版本标签 ) FetchContent_MakeAvailable(googletest) # 下载、配置并构建googletest # 现在你就可以使用 GTest::gtest 和 GTest::gmain 等目标了。 # 方式二如果第三方库已预编译或通过系统包管理器安装使用find_package。 # find_package(GTest REQUIRED) endif() # 引入项目自身的源代码子目录。 add_subdirectory(src/core) add_subdirectory(src/network) add_subdirectory(src/app) # 引入测试目录通常放在最后因为它依赖前面的库。 if(BUILD_TESTING) add_subdirectory(tests/unit) add_subdirectory(tests/integration) endif()关于第三方依赖管理的选择FetchContent适合管理那些你希望与项目一起编译、版本锁定的纯头文件库或小型源码库如googletest, nlohmann/json。它简化了流程但会增加项目的配置时间。find_package适合查找系统中已安装的库如OpenCV, Boost。你需要确保目标机器上已正确安装这些库。可以配合Conan或vcpkg等C包管理器使用它们能帮你解决复杂的依赖安装问题。将源码放入third_party对于一些修改过的、或网络获取不便的库可以直接将源码放入third_party目录然后用add_subdirectory(third_party/libxx)引入。注意处理好可能的编译选项冲突。4. 库与可执行文件的CMakeLists.txt编写现在我们深入到具体的模块中看看src/core/CMakeLists.txt和src/app/CMakeLists.txt应该如何编写。4.1 静态库/动态库的配置以src/core为例# 首先收集本模块的所有源文件。避免手动列举使用通配符但要注意其缺点。 # 方法A通配符简单但CMake不会自动检测新增文件需要重新运行cmake file(GLOB_RECURSE CORE_SOURCES src/*.cpp src/*.c) file(GLOB_RECURSE CORE_HEADERS include/core/*.hpp include/core/*.h) # 方法B手动列举繁琐但可靠是很多大型项目的选择 # set(CORE_SOURCES src/core_impl.cpp src/utils.cpp) # set(CORE_HEADERS include/core/core.hpp include/core/utils.hpp) # 创建一个库目标。STATIC表示静态库SHARED表示动态库。 add_library(core_lib STATIC ${CORE_SOURCES}) # 为库目标设置属性。 # 包含目录PUBLIC表示本目标使用且链接本目标的其他目标也需要。 target_include_directories(core_lib PUBLIC $BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include # 构建时使用 $INSTALL_INTERFACE:include # 安装后其他项目通过find_package找到时使用 PRIVATE . # 私有头文件目录仅本目标编译时需要 ) # 编译定义例如可以定义一个导出宏如果制作动态库。 target_compile_definitions(core_lib PRIVATE CORE_LIB_COMPILATION) # 链接其他库如果core_lib依赖第三方库如Threads。 find_package(Threads REQUIRED) target_link_libraries(core_lib PUBLIC Threads::Threads) # PUBLIC传递依赖 # 设置目标属性例如C标准虽然根目录设置了这里显式指定更清晰。 set_target_properties(core_lib PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF ) # 可选安装规则。使得这个库可以被系统或其他项目使用。 install(TARGETS core_lib EXPORT MyLargeProjectTargets # 导出目标用于生成配置文件 ARCHIVE DESTINATION lib # 静态库安装到 lib/ LIBRARY DESTINATION lib # 动态库安装到 lib/ RUNTIME DESTINATION bin # Windows的DLL安装到 bin/ INCLUDES DESTINATION include # 头文件安装到 include/ ) install(DIRECTORY include/core DESTINATION include) # 安装头文件关键点解析$BUILD_INTERFACE:...和$INSTALL_INTERFACE:...这是生成器表达式Generator Expressions是CMake中非常强大的功能。它允许你根据上下文是在构建本项目还是其他项目通过find_package找到了已安装的本项目来提供不同的路径。这是实现“可复用”库的关键。PUBLIC, PRIVATE, INTERFACE这三个关键字用于指定属性的传播范围。PRIVATE仅用于当前目标的编译。INTERFACE当前目标不直接使用但依赖它的目标需要常用于头文件库或接口类。PUBLICPRIVATEINTERFACE当前目标自己用也传递给依赖者。安装Install如果你的项目希望被其他项目作为库使用安装规则是必须的。它定义了当你运行make install或cmake --install .时项目的目标文件、头文件等被复制到系统目录如/usr/local的规则。4.2 可执行文件的配置以src/app为例可执行文件的配置相对简单因为它主要是消费库。# 收集应用源文件 file(GLOB APP_SOURCES *.cpp) # 创建可执行文件目标 add_executable(my_app ${APP_SOURCES}) # 链接项目内部的库。这里使用PRIVATE因为my_app依赖core_lib和network_lib # 但my_app本身不是一个库没有下游消费者所以依赖无需传递。 target_link_libraries(my_app PRIVATE core_lib network_lib) # 如果可执行文件有自己私有的包含目录或编译定义 target_include_directories(my_app PRIVATE ./private_headers) target_compile_definitions(my_app PRIVATE APP_MODE1) # 同样可以设置目标属性 set_target_properties(my_app PROPERTIES CXX_STANDARD 17 RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin # 统一输出到构建目录的bin文件夹 ) # 安装可执行文件 install(TARGETS my_app DESTINATION bin)4.3 测试模块的配置以tests/unit为例测试模块的配置核心是链接被测试的库和测试框架如GoogleTest。# 确保测试开关已打开 if(BUILD_TESTING AND TARGET GTest::gtest) # 检查googletest目标是否存在 # 收集所有测试源文件 file(GLOB TEST_SOURCES *.cpp) # 为每个测试文件创建一个可执行文件GoogleTest的常见用法 foreach(test_source ${TEST_SOURCES}) get_filename_component(test_name ${test_source} NAME_WE) # 提取不带扩展名的文件名 add_executable(${test_name}_test ${test_source}) # 链接被测试的库和gtest target_link_libraries(${test_name}_test PRIVATE core_lib GTest::gtest GTest::gmain) # 添加测试用例使得ctest命令可以运行它 add_test(NAME ${test_name} COMMAND ${test_name}_test) # 设置测试可执行文件的输出目录 set_target_properties(${test_name}_test PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests ) endforeach() endif()5. 高级主题与最佳实践补充5.1 使用configure_file管理版本和配置你可以在根目录创建一个config.h.in文件// config.h.in #define PROJECT_NAME PROJECT_NAME #define PROJECT_VERSION PROJECT_VERSION #define PROJECT_VERSION_MAJOR PROJECT_VERSION_MAJOR #define PROJECT_VERSION_MINOR PROJECT_VERSION_MINOR #define PROJECT_VERSION_PATCH PROJECT_VERSION_PATCH // 由CMake选项定义的宏 #cmakedefine ENABLE_FEATURE_X在根CMakeLists.txt中# 定义一个选项让用户在配置时决定 option(ENABLE_FEATURE_X Enable the experimental feature X OFF) # 将模板文件config.h.in转换为config.h替换其中的变量。 configure_file(config.h.in config.h ONLY) # 将生成的config.h所在目录加入包含路径 target_include_directories(core_lib PUBLIC ${CMAKE_CURRENT_BINARY_DIR})这样在你的C代码中就可以#include config.h并使用PROJECT_VERSION等宏了。ENABLE_FEATURE_X会根据CMake配置时用户的选择被定义为1或未定义。5.2 生成可复用的包配置文件Config.cmake为了让其他CMake项目能通过find_package(MyLargeProject)找到并使用你安装的库你需要生成一个包配置文件。这通常放在cmake/目录下。创建cmake/MyLargeProjectConfig.cmake.in:PACKAGE_INIT # CMake提供的宏用于初始化 include(${CMAKE_CURRENT_LIST_DIR}/MyLargeProjectTargets.cmake) # 导入目标 # 提供版本信息 set(MyLargeProject_VERSION PROJECT_VERSION)在根CMakeLists.txt中安装配置:include(CMakePackageConfigHelpers) # 生成配置文件 configure_package_config_file( cmake/MyLargeProjectConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/MyLargeProjectConfig.cmake INSTALL_DESTINATION lib/cmake/MyLargeProject ) # 生成版本文件 write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/MyLargeProjectConfigVersion.cmake VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion # 主版本相同即兼容 ) # 安装配置文件和目标导出文件 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/MyLargeProjectConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/MyLargeProjectConfigVersion.cmake DESTINATION lib/cmake/MyLargeProject ) install(EXPORT MyLargeProjectTargets FILE MyLargeProjectTargets.cmake NAMESPACE MyLargeProject:: DESTINATION lib/cmake/MyLargeProject )完成这些后其他项目在安装目录下就能通过find_package(MyLargeProject REQUIRED)找到你并使用MyLargeProject::core_lib这样的命名空间目标了。5.3 处理跨平台差异CMake的一大优势是处理跨平台问题。以下是一些常见技巧路径分隔符始终使用/CMake会在Windows上自动转换。平台特定代码使用if(WIN32)、if(APPLE)、if(UNIX)进行条件判断。平台特定链接库target_link_libraries(my_app PRIVATE core_lib $$PLATFORM_ID:Windows:ws2_32 # Windows下链接Winsock库 $$PLATFORM_ID:Linux:pthread # Linux下链接pthread库 )输出文件后缀CMake通常会自动处理如Windows下.exeLinux下无后缀。5.4 与IDE的协作VSCode与CLionVSCode安装CMake Tools扩展。打开项目文件夹后它会自动检测顶层的CMakeLists.txt。你可以从底部状态栏选择工具链Kit、构建类型Build Type和目标Target。CMake: Configure和CMake: Build命令是核心。确保你的settings.json中配置了正确的CMake路径和生成器如cmake.generator: Ninja。CLion作为JetBrains的C IDE它对CMake的支持是原生的。打开项目根目录CLion会自动识别并加载CMake项目。你可以在Settings/Preferences | Build, Execution, Deployment | CMake中配置构建目录、生成器、CMake选项等。CLion能完美解析基于目标的属性提供准确的代码洞察。常见问题有时IDE尤其是VSCode的IntelliSense可能找不到头文件。这通常是因为CMake没有生成正确的compile_commands.json文件或者IDE的CMake扩展没有正确加载包含目录。确保在CMakeLists.txt中正确使用target_include_directories。在CMake配置时传递-DCMAKE_EXPORT_COMPILE_COMMANDSON参数。在VSCode的settings.json中设置C_Cpp.default.compileCommands: ${workspaceFolder}/build/compile_commands.json假设构建目录是build。6. 常见问题排查与调试技巧即使遵循了最佳实践CMake的报错信息有时也令人费解。这里记录一些常见错误和排查思路。6.1 典型错误与解决方案速查表错误信息/现象可能原因排查步骤与解决方案CMake Error: CMake_C_COMPILER not set, after EnableLanguageCMake找不到C编译器。1. 检查系统是否安装了GCC/Clang/MSVC。2. 对于Windows确保Visual Studio或MinGW已安装且环境变量正确。3. 尝试指定编译器路径cmake -DCMAKE_C_COMPILER/usr/bin/gcc ..Could NOT find XXX (missing: XXX_LIBRARY XXX_INCLUDE_DIR)find_package找不到指定的包。1. 确认该库已正确安装在系统标准路径或通过包管理器如vcpkg, conan安装。2. 使用-DXXX_ROOT/path/to/lib参数为CMake提示库的根目录。3. 考虑改用FetchContent或源码集成。target_link_libraries链接时报“未定义的引用”1. 链接顺序错误。2. 依赖库本身未正确编译。3. C名称修饰mangling问题如C库未用extern C。1. 确保target_link_libraries中依赖库的顺序符合依赖关系被依赖的放后面。现代CMake基于目标的管理已很大程度上解决了此问题。2. 检查依赖库的目标是否成功创建add_library。3. 如果是C库在头文件中使用#ifdef __cplusplus extern C { #endif。头文件找不到但路径明明配置了1.target_include_directories作用域PUBLIC/PRIVATE用错。2. 路径是相对路径且参照系不对。3. IDE未刷新CMake配置。1. 检查包含目录是否对当前目标可见。库的包含目录应对消费者设为PUBLIC或INTERFACE。2. 使用${CMAKE_CURRENT_SOURCE_DIR}或${CMAKE_CURRENT_LIST_DIR}作为相对路径的基准。3. 在IDE中重新运行CMake: Configure。修改了CMakeLists.txt但IDE没反应IDE的CMake扩展可能没有自动侦测文件变化。手动触发重新配置。VSCode中按CtrlShiftP运行CMake: Delete Cache and Reconfigure。CLion中点击Reload CMake Project按钮。构建类型Debug/Release不生效未在配置时指定-DCMAKE_BUILD_TYPE且未在脚本中设置默认值。1. 在命令行明确指定cmake -DCMAKE_BUILD_TYPEDebug ..。2. 或在根CMakeLists.txt开头添加设置默认值的逻辑如前文所示。3. 多配置生成器如Visual Studio不支持单一的CMAKE_BUILD_TYPE需要在IDE中选择配置。6.2 调试CMake消息打印与变量检查CMake本身是一个脚本语言调试它的主要手段是打印消息。# 打印普通信息 message(STATUS This is a status message: ${PROJECT_NAME}) # 打印警告黄色 message(WARNING This is a warning: ${SOME_VARIABLE}) # 打印错误红色并停止处理 # message(FATAL_ERROR This is a fatal error) # 打印一个列表的所有元素 list(JOIN MY_LIST MY_LIST_STR) message(STATUS MY_LIST contains: ${MY_LIST_STR}) # 打印一个目标的所有属性 get_target_property(INC_DIRS my_target INCLUDE_DIRECTORIES) message(STATUS my_target include dirs: ${INC_DIRS})一个非常实用的技巧查看CMake缓存构建目录下的CMakeCache.txt文件记录了所有缓存的变量和它们的值。当行为不符合预期时查看这个文件是第一步。你也可以使用cmake -L ..来列出所有缓存变量。6.3 关于生成器Generator的选择CMake支持多种后端生成器。常见的有Unix MakefilesLinux/macOS上的默认选择生成Makefile。Ninja一个专注于速度的小型构建系统。通常比Make更快。使用-G Ninja指定。Visual Studio 17 2022在Windows上生成Visual Studio的.sln解决方案文件。Xcode在macOS上生成Xcode项目。选择取决于你的平台和工作流。对于命令行构建Ninja通常是性能最好的选择。对于IDE用户生成对应的项目文件更方便。在项目根目录创建一个presets.json文件可以标准化配置这是CMake 3.19引入的功能能极大简化命令行操作。{ version: 3, configurePresets: [ { name: default-debug, generator: Ninja, binaryDir: ${sourceDir}/build/debug, cacheVariables: { CMAKE_BUILD_TYPE: Debug, CMAKE_EXPORT_COMPILE_COMMANDS: ON } }, { name: default-release, generator: Ninja, binaryDir: ${sourceDir}/build/release, cacheVariables: { CMAKE_BUILD_TYPE: Release } } ] }之后你只需要运行cmake --presetdefault-debug即可完成配置无需记忆长长的命令行参数。构建一个可复用、易维护的C大型项目CMake工程实践是地基。从清晰的目录结构出发拥抱现代的基于目标Target的依赖管理在根文件中统一定义全局规则和引入依赖在各个子模块中精确地声明目标及其属性最后辅以安装规则和包配置来达成真正的“可复用”。这个过程初期需要投入学习成本并可能需要对旧项目进行一些重构但从长期来看它带来的团队协作效率提升、构建环境稳定性和项目可维护性的收益是巨大的。当你发现新增一个模块只需要在对应目录写几行简单的CMake代码而无需担心破坏其他部分时你会觉得这一切都是值得的。