强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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插件系统

这是现代 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 链接 mylib
  • mylibutils(PUBLIC)会传递到 myappmyapp 可以使用 utils
  • mylibfmt(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_11C++11
cxx_std_14C++14
cxx_std_17C++17
cxx_std_20C++20
cxx_std_23C++23
cxx_auto_typeauto 关键字
cxx_lambdasLambda 表达式
cxx_constexprconstexpr
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 章 — 变量系统 →