系统性全面的讲解软件开发cmake,以及列出详细的步骤和分析和包含内容和要点和案例
下午1:17
下面是一份系统性、全面的 CMake 学习与实战指南,涵盖基础到进阶概念、项目组织、常见模式、跨平台与构建类型、第三方库集成、测试/打包/部署、性能与诊断、最佳实践,以及完整案例。你可以把它当作一个从入门到生产级的路线图。
目录
-
- CMake 是什么与适用场景
- 基本概念与术语
- 最小工作示例与基本命令
- 目录结构与工程组织
- 构建类型、生成器与多配置
- 目标(target)与可见性(PUBLIC/PRIVATE/INTERFACE)
- 变量、缓存与工具链
- 查找与集成第三方库(find_package/FetchContent/ExternalProject)
- 配置、生成与安装(configure_file/install)
- 测试、覆盖率与持续集成(CTest)
- 打包与发布(CPack)
- 跨平台与跨编译(toolchain/平台差异)
- 现代 CMake 最佳实践
- 常见问题与排错思路
- 完整案例:多目标项目(库+可执行+测试+安装+打包)
- CMake 是什么与适用场景
- 定义:CMake 是跨平台构建系统的元构建工具(build system generator)。它读取 CMakeLists.txt,生成实际构建系统(如 Ninja、Makefiles、Visual Studio、Xcode)。
- 适用:
- 跨平台 C/C++ 项目(亦支持 Fortran、CUDA、Objective-C 等)。
- 多组件、多库工程,复杂依赖、插件化项目。
- 需要统一编译选项、测试、安装、打包的工程。
- 基本概念与术语
- 源(out-of-source)构建:推荐将构建产物放在独立 build 目录,避免污染源码树。
- 生成器(Generator):Ninja、Unix Makefiles、Visual Studio、Xcode 等。
- 目标(Target):add_library/add_executable 创建的构建实体。
- 属性(Property):与目标、目录、源文件等绑定的元数据,如 INCLUDE_DIRECTORIES、COMPILE_DEFINITIONS、POSITION_INDEPENDENT_CODE。
- 接口(Usage Requirements):通过 target_link_libraries 的 PUBLIC/PRIVATE/INTERFACE 传播编译选项与包含路径。
- 包(Package):通过 find_package 查找的已安装库(Config 或 Module 模式)。
- 预设(Presets):CMakePresets.json 管理构建配置/工具链/缓存变量。
- 最小工作示例与基本命令
最小示例
- 目录结构
- CMakeLists.txt
- src/main.cpp
CMakeLists.txt:
- cmake_minimum_required(VERSION 3.20)
- project(hello VERSION 1.0 LANGUAGES CXX)
- add_executable(hello src/main.cpp)
- set_target_properties(hello PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED YES)
- 构建
- mkdir -p build && cd build
- cmake -S .. -B . -G Ninja -DCMAKE_BUILD_TYPE=Release
- cmake --build . --config Release
- ./hello
常用命令速览
- project(name VERSION x.y LANGUAGES C CXX)
- add_library(name [STATIC|SHARED|OBJECT|INTERFACE] ...) / add_executable(name ...)
- target_link_libraries(tgt [PRIVATE|PUBLIC|INTERFACE] deps...)
- target_include_directories(tgt [PRIVATE|PUBLIC|INTERFACE] dirs...)
- target_compile_features(tgt PUBLIC cxx_std_20)
- target_compile_definitions(tgt PRIVATE FOO=1)
- set(var value [CACHE TYPE "doc"])
- option(ENABLE_X "desc" ON)
- message(STATUS|WARNING|FATAL_ERROR "text")
- if()/elseif()/else()/endif()
- foreach()/while()/function()/macro()
- file()/string()/list()/math() 工具命令
- configure_file(in out [@ONLY])
- install(TARGETS ...)/install(FILES ...)/install(DIRECTORY ...)
- find_package(Pkg CONFIG REQUIRED)
- include(GNUInstallDirs)
- enable_testing()/add_test()/ctest
- include(FetchContent)/FetchContent_MakeAvailable
- export()/install(EXPORT ...) 生成可被 find_package(CONFIG) 使用的包
- 目录结构与工程组织
推荐结构(现代 CMake)
- CMakeLists.txt
- cmake/ 模块与工具
- src/ 源码
- include/ 公共头文件(供外部使用)
- tests/ 单元测试
- examples/ 示例程序
- third_party/ 外部依赖(如使用 FetchContent 可省略)
- CMakePresets.json 构建预设
- package/ 打包相关文件(CPack、LICENSE、README)
顶层 CMakeLists.txt 组织
- cmake_minimum_required
- project
- 选项与工具链检测
- add_subdirectory(src)
- add_subdirectory(tests) 可通过选项开启
- 包与安装配置(GNUInstallDirs、version、config)
- 构建类型、生成器与多配置
- 单配置生成器(Ninja、Unix Makefiles):使用 -DCMAKE_BUILD_TYPE=Debug/Release/RelWithDebInfo/MinSizeRel
- 多配置生成器(Visual Studio、Xcode、Ninja Multi-Config):构建阶段指定 --config Release
- 通用编译选项:
- target_compile_options 而不是全局 add_compile_options
- 针对不同配置:generator expressions $CONFIG:Debug 或 target_compile_definitions(tgt PRIVATE $<$CONFIG:Debug:DEBUG_BUILD>)
- LTO/IPO:set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) 或 target_link_options
- 目标与可见性(核心)
- PRIVATE:仅影响当前目标
- PUBLIC:影响目标自身,并传播给链接该目标的下游
- INTERFACE:不影响当前(接口库没有源码),只作为传播给下游的“使用要求”
- 示例:
- target_link_libraries(app PRIVATE mylib) 不传播
- target_include_directories(mylib PUBLIC include) 下游会继承 include
- 接口库:
- add_library(mylib INTERFACE)
- target_compile_features(mylib INTERFACE cxx_std_20)
- 变量、缓存与工具链
- 普通变量:set(VAR value)
- 缓存变量:set(VAR value CACHE TYPE "doc") 可通过 -DVAR=value 从命令行设置,并持久化到 CMakeCache.txt
- 选项:option(ENABLE_SSE "enable sse" ON)
- 工具链文件:用于交叉编译或指定编译器/SDK
- cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=cmake/toolchain.cmake
- 设定 CMAKE_SYSTEM_NAME、CMAKE_C_COMPILER、CMAKE_CXX_COMPILER、CMAKE_SYSROOT 等
- 预设(CMakePresets.json):
- 配置 presets: 指定 generator、toolchain、cacheVars
- 构建 presets: 指定目标、并发、配置
- 调试 presets: 结合 IDE/调试器
- 查找与集成第三方库
- find_package 的两种模式:
- Config 模式:寻找 FooConfig.cmake(推荐,现代包通常提供)
- Module 模式:使用 CMake 自带或自定义 FindFoo.cmake
- 使用方法:
- find_package(fmt CONFIG REQUIRED)
- target_link_libraries(app PRIVATE fmt::fmt)
- FetchContent(拉取源码并作为子项目构建,适合可复现构建)
- include(FetchContent)
- FetchContent_Declare(fmt GIT_REPOSITORY https://github.com/fmtlib/fmt.git GIT_TAG 10.2.1)
- FetchContent_MakeAvailable(fmt)
- target_link_libraries(app PRIVATE fmt::fmt)
- ExternalProject_Add(外部独立构建,隔离性更强)
- pkg-config(find_package(PkgConfig) + pkg_check_modules)
- 优先级建议:
- Config 模式(已安装或本地包)
- FetchContent(锁版本、可复现)
- Module/FindXXX 自定义
- ExternalProject(必要时)
- 配置、生成与安装
- 配置头文件:
- configure_file(config.h.in config.h)
- 在 config.h.in 使用 #cmakedefine 或 @PROJECT_VERSION@
- 安装:
- include(GNUInstallDirs) 获取标准路径(CMAKE_INSTALL_LIBDIR、INCLUDEDIR)
- install(TARGETS mylib EXPORT mylib-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) - install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
- install(EXPORT mylib-targets NAMESPACE myproj:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myproj)
- 写一个 myprojConfig.cmake 与 myprojConfigVersion.cmake,使外部项目 find_package(myproj CONFIG) 即可
- 版本文件:
- include(CMakePackageConfigHelpers)
- write_basic_package_version_file(...)
- 测试、覆盖率与持续集成
- 启用测试:
- enable_testing()
- add_executable(tests tests/main.cpp)
- add_test(NAME unit COMMAND tests)
- 与 GoogleTest 集成(推荐 FetchContent)
- 代码覆盖率:
- Debug 配置下添加编译选项 -O0 -g --coverage(gcc/clang)
- 使用 lcov/gcovr 生成报告,可通过 custom target 集成
- CTest:
- ctest -j 8 --output-on-failure
- 结合 CDash 做测试仪表盘
- 打包与发布(CPack)
- include(CPack)
- 设置基础信息:
- set(CPACK_PACKAGE_NAME "myproj")
- set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
- set(CPACK_GENERATOR "TGZ;ZIP;DEB;RPM")
- 打包:
- cpack -G TGZ -C Release
- 对 DEB/RPM 设置依赖与元数据(CPACK_DEBIAN_PACKAGE_DEPENDS 等)
- 跨平台与跨编译
- 平台差异管理:
- if(WIN32) if(APPLE) if(UNIX) if(MSVC) if(MINGW) if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
- 生成器表达式处理差异:target_compile_definitions(tgt PRIVATE $<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS>)
- Windows:
- 静态/动态运行库:CMAKE_MSVC_RUNTIME_LIBRARY(MultiThreaded/MD/MTd 等,3.15+)
- Android/iOS/嵌入式:
- 使用官方/NDK 工具链文件
- CUDA、OpenMP、SSE/AVX:
- enable_language(CUDA) 或 target_compile_options 条件开启
- Position Independent Code:
- set(CMAKE_POSITION_INDEPENDENT_CODE ON) 对静态库开启 PIC
- 现代 CMake 最佳实践
- 面向目标而非全局:尽量用 target_* 命令管理编译/链接/包含
- 避免全局变量污染:使用作用域(function)和目标属性
- 使用 INTERFACE 目标导出使用需求
- 尽量用 find_package(CONFIG) + 别名目标(foo::foo)
- 最小可见性:PRIVATE 默认,必要时 PUBLIC/INTERFACE
- 生成器表达式代替手写 if 分支
- 使用预设与工具链锁定环境
- 用 FetchContent 锁定第三方版本,必要时导出/安装其 targets
- 对安装与导出保持一致性,提供 Config 文件,便于下游集成
- 保持 CMake 最低版本合理(例如 3.20+),同时善用新特性(Presets、MSVC 运行库、IPO)
- 常见问题与排错思路
- 头文件找不到:
- 检查 target_include_directories 作用域
- 确认消费端是否通过 target_link_libraries 链接产生传播
- 链接错误/符号未定义:
- 链接顺序(对旧版链接器)、漏链静态库、或可见性不当
- 对 SHARED 库在 Windows 可能需要定义导出符号(WINDOWS_EXPORT_ALL_SYMBOLS 或生成导出头)
- 多配置/单配置混淆:
- Ninja/Makefiles 需要 CMAKE_BUILD_TYPE,MSVC 需要 --config
- find_package 找不到:
- CMAKE_PREFIX_PATH、CMAKE_MODULE_PATH 设置
- 查找日志:cmake --log-level=VERBOSE 或 message(DEBUG)
- 交叉编译失败:
- 工具链文件未设定 CMAKE_SYSTEM_NAME/COMPILER
- sysroot/SDK 路径
- 选项从缓存无法更新:
- 修改后需要清理缓存或使用 -U/--fresh(Presets 支持 clean)
- 完整案例:多目标项目(库 + 可执行 + 测试 + 安装 + 打包)
目录结构
- CMakeLists.txt
- include/myproj/myproj.h
- src/myproj.cpp
- src/main.cpp
- tests/test_main.cpp
- cmake/PreventInSourceBuild.cmake
- CMakePresets.json
- package/LICENSE, README.md
示例代码
include/myproj/myproj.h
- #pragma once
- #include <string>
- namespace myproj { std::string greet(std::string name); }
src/myproj.cpp
- #include "myproj/myproj.h"
- #include <fmt/core.h>
- namespace myproj {
std::string greet(std::string name) { return fmt::format("Hello, {}!", name); }
}
src/main.cpp
- #include "myproj/myproj.h"
- #include <iostream>
- int main(int argc, char** argv) {
std::string name = argc > 1 ? argv[1] : "World";
std::cout << myproj::greet(name) << std::endl;
return 0;
}
tests/test_main.cpp
- #include "myproj/myproj.h"
- #include <cassert>
- int main() {
auto s = myproj::greet("CMake");
assert(s == "Hello, CMake!");
return 0;
}
cmake/PreventInSourceBuild.cmake
- if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
message(FATAL_ERROR "In-source build is not allowed. Use a separate build directory.")
endif()
顶层 CMakeLists.txt
- cmake_minimum_required(VERSION 3.20)
- include(cmake/PreventInSourceBuild.cmake)
- project(myproj VERSION 1.2.3 LANGUAGES CXX)
- option(MYPROJ_BUILD_TESTS "Build tests" ON)
- set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
- include(GNUInstallDirs)
- FetchContent 获取 fmt
- include(FetchContent)
- FetchContent_Declare(fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt.git
GIT_TAG 10.2.1) - FetchContent_MakeAvailable(fmt)
- add_library(myproj src/myproj.cpp)
- add_library(myproj::myproj ALIAS myproj)
- target_include_directories(myproj
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>) - target_link_libraries(myproj
PUBLIC fmt::fmt) - target_compile_features(myproj PUBLIC cxx_std_17)
- add_executable(myproj_cli src/main.cpp)
- target_link_libraries(myproj_cli PRIVATE myproj::myproj)
- 安装
- install(TARGETS myproj myproj_cli
EXPORT myproj-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) - install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
- 导出与包配置
- include(CMakePackageConfigHelpers)
- write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/myprojConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion) - configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/myprojConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/myprojConfig.cmake"
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myproj) - install(EXPORT myproj-targets
NAMESPACE myproj::
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myproj) - install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/myprojConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/myprojConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/myproj) - 测试
- if(MYPROJ_BUILD_TESTS)
enable_testing()
add_executable(myproj_tests tests/test_main.cpp)
target_link_libraries(myproj_tests PRIVATE myproj::myproj)
add_test(NAME greet_test COMMAND myproj_tests)
endif() - 打包
- set(CPACK_PACKAGE_NAME "myproj")
- set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
- set(CPACK_GENERATOR "TGZ;ZIP")
- include(CPack)
cmake/myprojConfig.cmake.in
- @PACKAGE_INIT@
- include(CMakeFindDependencyMacro)
- 如需依赖,可使用 find_dependency(fmt CONFIG)
- include("${CMAKE_CURRENT_LIST_DIR}/myproj-targets.cmake")
CMakePresets.json
- {
"version": 5,
"cmakeMinimumRequired": { "major": 3, "minor": 20 },
"configurePresets": [
{ "name": "debug", "displayName": "Debug-Ninja", "generator": "Ninja",
"binaryDir": "build/debug",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Debug", "MYPROJ_BUILD_TESTS": true } },
{ "name": "release", "displayName": "Release-Ninja", "generator": "Ninja",
"binaryDir": "build/release",
"cacheVariables": { "CMAKE_BUILD_TYPE": "Release", "MYPROJ_BUILD_TESTS": false } }
],
"buildPresets": [
{ "name": "debug", "configurePreset": "debug" },
{ "name": "release", "configurePreset": "release" }
],
"testPresets": [
{ "name": "debug", "configurePreset": "debug", "output": { "outputOnFailure": true } }
]
}
构建与运行
- cmake --preset debug
- cmake --build --preset debug -j
- ctest --preset debug
- cmake --install build/release --prefix /tmp/myproj
- cpack --config build/release/CPackConfig.cmake -G TGZ
要点总结
- 优先使用面向目标的“现代 CMake”写法
- 用 PUBLIC/PRIVATE/INTERFACE 精准传播“使用要求”
- 使用 FetchContent 或 find_package(CONFIG) 管理第三方依赖
- 提供安装与导出配置,便于下游项目 find_package 使用
- 使用 CTest/CPack 打通测试与发布
- 用 Presets 固化环境,减少“这台机器能编译那台不行”的问题
如果你提供你的具体项目结构或需求(语言标准、目标平台、依赖列表、是否需要安装/打包),我可以给出一份完全贴合的 CMakeLists.txt。

263

被折叠的 条评论
为什么被折叠?



