cmake 是构建系统生成器,非编译器或构建工具;它根据 cmakelists.txt 生成平台特定构建文件,核心是“一次描述,多端生成”。

CMake 是什么,不是什么
CMake 不是编译器,也不是构建工具本身(比如 make 或 ninja),它是一个「构建系统生成器」。你写 CMakeLists.txt 描述项目结构和依赖,CMake 根据当前平台和你选的生成器(如 Unix Makefiles、Ninja、Xcode、Visual Studio 17 2022)产出对应平台能直接运行的构建文件。
常见误解是把它当 Make 的替代品——其实它在 Make 之上再加了一层抽象,目标是「一次描述,多端生成」。跨平台的关键不在于 CMake 多聪明,而在于你写的 CMakeLists.txt 是否规避了平台硬编码。
怎么写一个最小但可移植的 CMakeLists.txt
从 cmake_minimum_required(VERSION 3.10) 开始就踩过坑:版本太低,find_package() 找不到现代库;太高,老 CI 环境或同事本地环境报错。3.10 是个较安全的下限,支持 target_compile_features 和基础 find_package(... CONFIG)。
-
project(MyApp LANGUAGES CXX)必须有,且建议显式声明语言,避免某些平台默认只开 C - 不要用
include_directories()全局污染;改用target_include_directories(my_target PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) - 链接第三方库时,优先用
find_package(Boost REQUIRED COMPONENTS system filesystem)而非硬写/usr/local/lib/libboost_system.a - 条件判断用
if(WIN32)、if(APPLE)、if(UNIX AND NOT APPLE),别用if(CMAKE_SYSTEM_NAME STREQUAL "Linux")——Android NDK 下CMAKE_SYSTEM_NAME是Android,但UNIX仍为TRUE
为什么 add_executable() 后链接失败,但 Windows 上却正常?
典型现象:undefined reference to 'pthread_create' 在 Linux/macOS 报错,Windows 无事发生。这不是 CMake bug,是链接顺序和平台默认行为差异导致的。
立即学习“C++免费学习笔记(深入)”;
Linux/macOS 链接器严格按命令行顺序解析符号,target_link_libraries(myapp PRIVATE Threads::Threads) 必须写在 add_executable() 之后、且所有依赖目标都已声明。而 MSVC 链接器会回扫,所以“侥幸”通过。
- 确保
target_link_libraries()总是放在对应add_executable()或add_library()之后 - 用
Threads::Threads这种 imported target,而非-lpthread字符串——前者由 CMake 自动处理平台差异 - 检查是否漏了
set(CMAKE_CXX_STANDARD 17):某些 STL 特性(如std::filesystem)在旧标准下不自动链接stdc++fs
构建目录污染和 Ninja vs Make 的实际影响
build/ 目录必须是空的或全新创建的。CMake 不会自动清理旧缓存中的过期变量或已删除的源文件引用,导致 cmake .. && ninja 看似成功,实则链接了旧对象或跳过应编译的新文件。
Ninja 比 Make 更严格:它默认不重建未显式声明依赖的头文件变动。如果你没用 target_include_directories(... PRIVATE) 或忘了 set_property(SOURCE xxx.cpp PROPERTY INCLUDE_DIRECTORIES ...),Ninja 可能完全不感知头文件修改。
- 每次切换构建配置(Debug/Release)或生成器,务必新建 build 子目录,例如
mkdir build-ninja && cd build-ninja && cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug .. - 用
cmake --build . --verbose看真实调用的编译命令,确认包含的头路径和宏定义是否符合预期 - CI 中禁用
ccache前先清理 CMake 缓存,否则 ccache 可能命中旧编译产物,掩盖头文件变更问题
跨平台真正的难点不在语法,而在每个 if() 分支里是否真理解对应平台的工具链行为——比如 macOS 的 dylib 加载路径、Windows 的 CRT 静态/动态链接一致性、Android 的 ABI 分割。这些细节不会报错,但会让程序在某台机器上静默崩溃。










