CMake 从入门到精通:完整教程 / 第 3 章:基础入门
第 3 章:基础入门
3.1 你的第一个 CMake 项目
3.1.1 最小 CMakeLists.txt
创建一个最简单的 CMake 项目,只需要 3 行:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(HelloWorld)
add_executable(hello main.cpp)
对应的 main.cpp:
// main.cpp
#include <iostream>
int main() {
std::cout << "Hello, CMake!" << std::endl;
return 0;
}
3.1.2 构建与运行
# 目录结构
# hello/
# ├── CMakeLists.txt
# └── main.cpp
# 1. 配置
mkdir build && cd build
cmake ..
# 或使用现代方式
cmake -S . -B build
# 2. 构建
cmake --build build
# 或
cmake --build .
# 或在 build 目录中
make
# 3. 运行
./build/hello
# 或在 build 目录中
./hello
3.1.3 out-of-source 构建(推荐)
项目目录(源码) 构建目录(产物)
hello/ build/
├── CMakeLists.txt ├── CMakeCache.txt
├── main.cpp ├── Makefile
└── src/ ├── CMakeFiles/
└── utils.cpp └── hello ← 可执行文件
⚠️ 重要:始终使用 out-of-source 构建(在单独的 build 目录中构建)。不要在源码目录中直接运行 cmake。
3.2 cmake_minimum_required
# 必须是 CMakeLists.txt 的第一个命令(注释除外)
cmake_minimum_required(VERSION 3.16)
# 推荐:同时指定策略版本范围
cmake_minimum_required(VERSION 3.16...3.28)
| 参数 | 含义 |
|---|---|
VERSION 3.16 | 最低要求 CMake 3.16 |
VERSION 3.16...3.28 | 最低 3.16,策略行为对齐 3.28 |
为什么需要这个命令?
- 确保使用者的 CMake 版本足够新
- 设置 CMake 策略(Policy)行为
- 没有它,CMake 会产生警告
3.3 project 命令
project() 命令定义项目名称并初始化构建系统:
# 最简形式
project(MyProject)
# 完整形式
project(MyProject
VERSION 1.2.3
DESCRIPTION "A sample CMake project"
LANGUAGES CXX C
)
参数说明
| 参数 | 说明 | 示例 |
|---|---|---|
<PROJECT-NAME> | 项目名称(必选) | MyProject |
VERSION | 版本号,最多 4 段 | 1.2.3.4 |
DESCRIPTION | 项目描述 | "My awesome project" |
HOMEPAGE_URL | 项目主页 | "https://example.com" |
LANGUAGES | 使用的编程语言 | CXX C CUDA |
project 自动设置的变量
project(MyApp VERSION 2.0.0 LANGUAGES CXX)
# 以下变量自动可用:
message("项目名称: ${PROJECT_NAME}") # MyApp
message("项目版本: ${PROJECT_VERSION}") # 2.0.0
message("主版本号: ${PROJECT_VERSION_MAJOR}") # 2
message("次版本号: ${PROJECT_VERSION_MINOR}") # 0
message("补丁版本: ${PROJECT_VERSION_PATCH}") # 0
message("源码目录: ${PROJECT_SOURCE_DIR}") # 绝对路径
message("构建目录: ${PROJECT_BINARY_DIR}") # 绝对路径
多语言项目
# C 和 C++ 项目
project(MyMixed LANGUAGES C CXX)
# CUDA 项目
project(MyCuda LANGUAGES CXX CUDA)
# 检查语言是否可用
include(CheckLanguage)
check_language(CUDA)
if(CMAKE_CUDA_COMPILER)
enable_language(CUDA)
endif()
3.4 add_executable
定义一个可执行文件目标:
# 基本形式
add_executable(myapp main.cpp)
# 多个源文件
add_executable(myapp
main.cpp
src/utils.cpp
src/network.cpp
)
# WIN32 子系统(Windows 下隐藏控制台窗口)
add_executable(myapp WIN32 main.cpp)
# macOS Bundle
add_executable(myapp MACOSX_BUNDLE main.cpp)
# 从 IMPORTED 目标创建(高级用法)
add_executable(myapp IMPORTED)
set_target_properties(myapp PROPERTIES IMPORTED_LOCATION "/path/to/binary")
变量收集源文件
# 方式一:显式列出(推荐,小项目)
set(SOURCES
main.cpp
src/app.cpp
src/utils.cpp
)
add_executable(myapp ${SOURCES})
# 方式二:使用通配符(不推荐,大项目可能有性能问题)
file(GLOB SOURCES "src/*.cpp")
add_executable(myapp ${SOURCES})
# 方式三:使用 GLOB_RECURSE(包含子目录)
file(GLOB_RECURSE SOURCES "src/*.cpp")
add_executable(myapp ${SOURCES})
⚠️ 注意:
file(GLOB)不会自动检测新增/删除文件。CMake 官方建议手动列出源文件,或在添加新文件后重新运行 cmake。
3.5 add_library
定义一个库目标:
# 静态库(默认)
add_library(mylib STATIC src/mylib.cpp)
# 动态库(共享库)
add_library(mylib SHARED src/mylib.cpp)
# 对象库(不产生文件,仅编译为目标文件)
add_library(mylib OBJECT src/mylib.cpp)
# 接口库(不编译,仅传递属性)
add_library(mylib INTERFACE)
# 模块库(插件,运行时加载)
add_library(mylib MODULE src/plugin.cpp)
库类型对比
| 类型 | 关键字 | 产物 | 使用场景 |
|---|---|---|---|
| 静态库 | STATIC | .a / .lib | 内嵌到可执行文件 |
| 动态库 | SHARED | .so / .dll / .dylib | 运行时加载 |
| 对象库 | OBJECT | .o / .obj 文件 | 避免重复编译 |
| 接口库 | INTERFACE | 无物理文件 | 纯头文件库、抽象 |
| 模块库 | MODULE | .so / .dll | 插件系统 |
3.6 target_link_libraries
这是现代 CMake 最重要的命令之一,用于指定目标的依赖关系:
# 基本用法
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib)
# 链接多个库
target_link_libraries(myapp
PRIVATE mylib
PRIVATE fmt::fmt
PRIVATE Threads::Threads
)
# 对库目标指定依赖
add_library(mylib src/mylib.cpp)
target_link_libraries(mylib PUBLIC utils)
target_link_libraries(mylib PRIVATE fmt::fmt)
链接关键字
| 关键字 | 含义 | 使用场景 |
|---|---|---|
PRIVATE | 仅自己使用,不传递给依赖者 | 内部实现依赖 |
PUBLIC | 自己使用,同时传递给依赖者 | 公共 API 依赖 |
INTERFACE | 仅传递给依赖者,自己不使用 | 纯头文件库 |
传递性示例
myapp → mylib → utils (PUBLIC) → zlib
→ fmt (PRIVATE)
myapp链接mylibmylib的utils(PUBLIC)会传递到myapp→myapp可以使用utilsmylib的fmt(PRIVATE)不会传递 →myapp不能直接使用fmt
3.7 完整的多文件项目示例
项目结构
myproject/
├── CMakeLists.txt # 顶层 CMakeLists.txt
├── include/
│ └── myproject/
│ └── utils.h # 公共头文件
├── src/
│ ├── CMakeLists.txt # src 子目录的 CMakeLists.txt
│ ├── app.cpp
│ └── utils.cpp
└── main.cpp
顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.16...3.28)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# 添加子目录
add_subdirectory(src)
# 创建可执行文件
add_executable(myapp main.cpp)
# 链接库
target_link_libraries(myapp PRIVATE mylib)
src/CMakeLists.txt
# 创建库
add_library(mylib
app.cpp
utils.cpp
)
# 设置头文件搜索路径
target_include_directories(mylib
PUBLIC ${PROJECT_SOURCE_DIR}/include
)
# 设置编译特性
target_compile_features(mylib PUBLIC cxx_std_17)
include/myproject/utils.h
#pragma once
#include <string>
namespace myproject {
std::string greet(const std::string& name);
int add(int a, int b);
}
src/utils.cpp
#include <myproject/utils.h>
namespace myproject {
std::string greet(const std::string& name) {
return "Hello, " + name + "!";
}
int add(int a, int b) {
return a + b;
}
}
main.cpp
#include <myproject/utils.h>
#include <iostream>
int main() {
std::cout << myproject::greet("CMake") << std::endl;
std::cout << "2 + 3 = " << myproject::add(2, 3) << std::endl;
return 0;
}
构建和运行
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build --parallel
./build/myapp
# 输出:
# Hello, CMake!
# 2 + 3 = 5
3.8 target_include_directories
设置头文件搜索路径:
add_library(mylib src/mylib.cpp)
# PUBLIC:自己和依赖者都使用
target_include_directories(mylib PUBLIC
${PROJECT_SOURCE_DIR}/include
)
# PRIVATE:仅自己使用
target_include_directories(mylib PRIVATE
${PROJECT_SOURCE_DIR}/src/internal
)
# INTERFACE:仅依赖者使用(适用于纯头文件库)
target_include_directories(mylib INTERFACE
${PROJECT_SOURCE_DIR}/include
)
路径生成器表达式
# 构建时和安装时使用不同路径
target_include_directories(mylib PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> # 构建时
$<INSTALL_INTERFACE:include> # 安装时
)
3.9 target_compile_features
指定编译所需的语言特性:
# 要求 C++17
target_compile_features(myapp PRIVATE cxx_std_17)
# 要求特定特性
target_compile_features(myapp PRIVATE cxx_std_17 cxx_static_assert)
# 为库设置公共要求
target_compile_features(mylib PUBLIC cxx_std_17)
# 使用 mylib 的目标也会自动要求 C++17
常用特性:
| 特性 | 对应标准 |
|---|---|
cxx_std_11 | C++11 |
cxx_std_14 | C++14 |
cxx_std_17 | C++17 |
cxx_std_20 | C++20 |
cxx_std_23 | C++23 |
cxx_auto_type | auto 关键字 |
cxx_lambdas | Lambda 表达式 |
cxx_constexpr | constexpr |
cxx_range_for | 范围 for 循环 |
3.10 target_compile_definitions
添加预处理宏定义:
# 等价于 -DDEBUG_MODE 和 -DVERSION="1.0"
target_compile_definitions(myapp PRIVATE
DEBUG_MODE
VERSION="1.0"
)
# 带保护的定义
target_compile_definitions(myapp PRIVATE
$<$<CONFIG:Debug>:DEBUG_MODE>
# 等价于:只在 Debug 配置时定义 DEBUG_MODE
)
3.11 target_compile_options
设置编译选项:
# 添加编译选项
target_compile_options(myapp PRIVATE
-Wall -Wextra -Wpedantic
)
# 跨平台选项
target_compile_options(myapp PRIVATE
$<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra>
$<$<CXX_COMPILER_ID:MSVC>:/W4>
)
3.12 构建类型(Build Type)
| 构建类型 | 说明 | 优化 | 调试信息 |
|---|---|---|---|
Debug | 调试模式 | -O0 | -g |
Release | 发布模式 | -O3 | 无 |
RelWithDebInfo | 带调试信息的发布 | -O2 | -g |
MinSizeRel | 最小体积发布 | -Os | 无 |
# 设置构建类型
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
# Multi-Config 生成器(Visual Studio, Xcode)中不需要设置
# 而是在构建时指定:
cmake --build build --config Release
3.13 message 命令
用于输出信息,常用于调试:
# 不同类型的消息
message("普通消息")
message(STATUS "状态消息") # 前缀 "--"
message(WARNING "警告消息") # 黄色警告
message(FATAL_ERROR "致命错误") # 终止配置
message(SEND_ERROR "发送错误") # 继续配置但无法生成
message(AUTHOR_WARNING "作者警告") # 开发者警告
# 条件输出
if(NOT DEFINED CMAKE_BUILD_TYPE)
message(WARNING "CMAKE_BUILD_TYPE 未设置,默认使用 Debug")
endif()
3.14 构建流程总结
1. 编写 CMakeLists.txt
│
▼
2. cmake -S . -B build ← 配置阶段
│ 检测编译器
│ 处理 find_package
│ 生成 CMakeCache.txt
│ 生成构建文件
▼
3. cmake --build build ← 构建阶段
│ 编译源文件
│ 链接目标
▼
4. ctest --test-dir build ← 测试阶段(可选)
│
▼
5. cmake --install build ← 安装阶段(可选)
3.15 注意事项
| 问题 | 说明 |
|---|---|
cmake_minimum_required 放错位置 | 必须在 project() 之前 |
| 源码目录内构建 | 始终使用 out-of-source 构建 |
| 忘记链接库 | 出现 undefined reference 错误 |
| 大小写错误 | CMake 变量名区分大小写 |
| 路径中包含空格 | 使用引号包裹路径 |
| file(GLOB) 不自动更新 | 新增/删除文件后需重新 cmake |
3.16 扩展阅读
上一章:第 2 章 — 安装与环境配置 | 下一章:第 4 章 — 变量系统 →