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

C/C++ Linux 开发教程(GCC + CMake) / CMake 基础(add_executable/target_link)

CMake 基础(add_executable/target_link)

CMake 是目前最流行的跨平台构建系统生成器。它不直接构建项目,而是生成 Makefile、Ninja 文件或 IDE 项目文件。本文从零开始讲解 CMake 的核心概念和使用方法。

CMakeLists.txt 基本结构

最小 CMake 项目

# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyApp VERSION 1.0.0 LANGUAGES C CXX)

add_executable(app main.c)
# 构建步骤
cmake -B build          # 配置(生成构建系统)
cmake --build build     # 构建(编译链接)
./build/app             # 运行

三行核心命令

命令作用必须
cmake_minimum_required指定最低 CMake 版本
project定义项目名称和属性
add_executable / add_library定义构建目标

project() 命令

# 基本用法
project(MyApp)

# 完整用法
project(MyApp
    VERSION 2.1.0           # 版本号
    DESCRIPTION "My Application"  # 描述
    HOMEPAGE_URL "https://example.com"  # 主页
    LANGUAGES C CXX         # 使用的语言
)

# project() 会自动定义以下变量:
# PROJECT_NAME = "MyApp"
# PROJECT_VERSION = "2.1.0"
# MyApp_VERSION = "2.1.0"
# PROJECT_SOURCE_DIR / MyApp_SOURCE_DIR
# PROJECT_BINARY_DIR / MyApp_BINARY_DIR

添加构建目标

add_executable — 可执行文件

# 基本用法
add_executable(app main.c utils.c)

# 使用变量
set(SOURCES main.c utils.c parser.c)
add_executable(app ${SOURCES})

# 使用 file(GLOB) 收集源文件(不推荐用于生产项目)
file(GLOB SOURCES "src/*.c")
add_executable(app ${SOURCES})

# 指定输出目录
set_target_properties(app PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
)

add_library — 库文件

# 静态库(默认)
add_library(mylib STATIC src/mylib.c)

# 动态库(共享库)
add_library(mylib SHARED src/mylib.c)

# 对象库(只编译不链接,适合复用编译结果)
add_library(mylib_obj OBJECT src/mylib.c)

# 接口库(只有头文件,不编译)
add_library(mylib_headers INTERFACE)

# 使用源文件列表
set(LIB_SOURCES
    src/string_utils.c
    src/file_utils.c
    src/math_utils.c
)
add_library(utils STATIC ${LIB_SOURCES})

链接与包含

add_executable(app main.c)

# 链接外部库
target_link_libraries(app PRIVATE m)            # 数学库
target_link_libraries(app PRIVATE pthread)       # 线程库

# 链接自己编译的库
add_library(utils STATIC src/utils.c)
target_link_libraries(app PRIVATE utils)

# 关键字说明:
# PRIVATE — 仅当前目标使用
# INTERFACE — 仅传递给依赖者
# PUBLIC — 当前目标和依赖者都使用

# 链接多个库
target_link_libraries(app
    PRIVATE
        utils
        m
        pthread
        OpenSSL::SSL
)

target_include_directories — 头文件路径

add_library(mylib src/mylib.c)

# 添加公共头文件路径(PUBLIC = 当前目标和依赖者都可用)
target_include_directories(mylib
    PUBLIC
        $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
        $<INSTALL_INTERFACE:include>
    PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/src   # 私有头文件
)

# 使用库时无需手动指定 -I 路径
add_executable(app main.c)
target_link_libraries(app PRIVATE mylib)  # 自动获得 include 路径

target_compile_definitions — 预处理宏

# 添加预处理宏
target_compile_definitions(app PRIVATE
    VERSION="1.0"
    ENABLE_FEATURE_X
    $<$<CONFIG:Debug>:DEBUG_MODE>   # 仅 Debug 模式
)

target_compile_options — 编译选项

# 添加编译选项
target_compile_options(app PRIVATE
    -Wall -Wextra -Wpedantic
    $<$<CONFIG:Debug>:-O0 -g>
    $<$<CONFIG:Release>:-O2 -DNDEBUG>
)

变量与缓存

普通变量

# 设置变量
set(MY_VAR "hello")
set(SOURCES main.c utils.c)
set(CMAKE_C_STANDARD 11)

# 使用变量
message(STATUS "MY_VAR = ${MY_VAR}")

# 列表操作
list(APPEND SOURCES parser.c)
list(REMOVE_ITEM SOURCES utils.c)
list(LENGTH SOURCES SOURCE_COUNT)
list(SORT SOURCES)

# 环境变量
set(ENV{PATH} "/usr/local/bin:$ENV{PATH}")
message(STATUS "HOME = $ENV{HOME}")

缓存变量

# 缓存变量(用户可通过 -D 修改)
set(BUILD_TESTS ON CACHE BOOL "Build test programs")
set(OUTPUT_DIR "bin" CACHE STRING "Output directory")
set(USE_OPENSSL ON CACHE BOOL "Use OpenSSL for TLS")

# 带高级标记(GUI 中默认不显示)
set(INTERNAL_VAR "" CACHE STRING "" INTERNAL)

# 使用 cache 变量
if(BUILD_TESTS)
    add_subdirectory(tests)
endif()
# 在命令行设置缓存变量
cmake -B build -DBUILD_TESTS=OFF -DOUTPUT_DIR="dist"

CMake 内置变量

变量说明
CMAKE_SOURCE_DIR顶层源码目录
CMAKE_BINARY_DIR顶层构建目录
CMAKE_CURRENT_SOURCE_DIR当前 CMakeLists.txt 所在目录
CMAKE_CURRENT_BINARY_DIR当前构建目录
CMAKE_C_COMPILERC 编译器路径
CMAKE_CXX_COMPILERC++ 编译器路径
CMAKE_BUILD_TYPE构建类型 (Debug/Release/…)
CMAKE_INSTALL_PREFIX安装前缀
CMAKE_SYSTEM_NAME目标系统名
CMAKE_C_FLAGS全局 C 编译标志

message 输出

# 不同类型的 message
message(STATUS "Status message")           # 普通状态信息
message(WARNING "Warning message")         # 警告
message(FATAL_ERROR "Fatal error")         # 致命错误(停止配置)
message(SEND_ERROR "Send error")           # 错误(继续配置)
message(AUTHOR_WARNING "Author warning")   # 作者警告
message(TRACE "Trace message")             # 跟踪信息
message(DEBUG "Debug message")             # 调试信息(CMake 3.15+)

# 打印变量
message(STATUS "Source dir: ${CMAKE_SOURCE_DIR}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "Compiler: ${CMAKE_C_COMPILER_ID}")

# 打印所有变量(调试用)
get_cmake_property(_vars VARIABLES)
foreach(_var ${_vars})
    message(STATUS "${_var} = ${${_var}}")
endforeach()

条件与循环

if/else/endif

# 基本条件判断
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    message(STATUS "Debug build")
    add_definitions(-DDEBUG)
else()
    message(STATUS "Release build")
endif()

# 逻辑运算
if(BUILD_TESTS AND ENABLE_COVERAGE)
    add_subdirectory(tests)
endif()

if(NOT WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux")
    message(STATUS "Non-Windows or Linux")
endif()

# 数值比较
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.20")
    message(STATUS "Modern CMake")
endif()

# 文件/目录检查
if(EXISTS "${CMAKE_SOURCE_DIR}/config.h")
    message(STATUS "config.h exists")
endif()

if(IS_DIRECTORY "${CMAKE_SOURCE_DIR}/third_party")
    add_subdirectory(third_party)
endif()

# 变量检查
if(DEFINED MY_VAR)
    message(STATUS "MY_VAR is defined: ${MY_VAR}")
endif()

if(MY_VAR)
    message(STATUS "MY_VAR is truthy")
endif()

# 字符串匹配
if(MY_STRING MATCHES "^hello.*")
    message(STATUS "Matches pattern")
endif()

foreach/endforeach

# 遍历列表
set(SOURCES main.c utils.c parser.c)
foreach(src ${SOURCES})
    message(STATUS "Processing: ${src}")
endforeach()

# 遍历范围
foreach(i RANGE 1 10)
    message(STATUS "i = ${i}")
endforeach()

# 遍历多个列表
foreach(file IN LISTS HEADERS SOURCES)
    message(STATUS "File: ${file}")
endforeach()

# while 循环
set(counter 0)
while(counter LESS 5)
    math(EXPR counter "${counter} + 1")
    message(STATUS "counter = ${counter}")
endwhile()

构建流程

配置、构建、安装三步走

# 第一步: 配置(生成构建系统)
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local

# 第二步: 构建(编译链接)
cmake --build build --parallel $(nproc)

# 第三步: 安装(可选)
cmake --install build
# 或
sudo cmake --install build  # 安装到系统路径

构建类型

构建类型编译标志说明
Debug-O0 -g调试模式
Release-O3 -DNDEBUG发布模式
RelWithDebInfo-O2 -g -DNDEBUG带调试信息的发布
MinSizeRel-Os -DNDEBUG最小体积
# 指定构建类型
cmake -B build -DCMAKE_BUILD_TYPE=Debug
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo

install 规则

# 安装可执行文件
install(TARGETS app RUNTIME DESTINATION bin)

# 安装库文件
install(TARGETS mylib
    LIBRARY DESTINATION lib     # .so
    ARCHIVE DESTINATION lib     # .a
    RUNTIME DESTINATION bin     # .dll (Windows)
)

# 安装头文件
install(DIRECTORY include/ DESTINATION include)
install(FILES include/mylib.h DESTINATION include)

# 安装配置文件
install(FILES config.ini DESTINATION etc/myapp)

ctest 测试

# 启用测试
enable_testing()

# 添加测试
add_executable(test_utils test/test_utils.c)
target_link_libraries(test_utils PRIVATE utils)

add_test(NAME test_utils COMMAND test_utils)

# 带参数的测试
add_test(NAME test_integration
    COMMAND ${CMAKE_BINARY_DIR}/bin/app --test
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
)

# 设置测试超时(秒)
set_tests_properties(test_utils PROPERTIES TIMEOUT 30)
# 运行测试
cd build
ctest
ctest --verbose
ctest --output-on-failure
ctest -R "test_utils"         # 只运行匹配的测试
ctest -j$(nproc)              # 并行运行

生成器

Makefile 生成器(默认)

# 使用 Makefile 生成器
cmake -G "Unix Makefiles" -B build
cmake --build build --parallel 4

# 手动调用 make
cd build
make -j$(nproc)
make install

Ninja 生成器(推荐)

# 使用 Ninja 生成器(更快)
cmake -G "Ninja" -B build
cmake --build build

# 或使用 ninja
cd build
ninja
ninja install

生成器对比

特性MakefileNinja
并行构建make -j自动并行
增量构建较慢更快
依赖追踪基于时间戳更精确
输出详细简洁
可用性广泛需安装
推荐场景兼容性性能关键

CMake 预设(CMakePresets.json)

基本预设配置

{
    "version": 3,
    "cmakeMinimumRequired": {
        "major": 3,
        "minor": 21,
        "patch": 0
    },
    "configurePresets": [
        {
            "name": "default",
            "displayName": "Default Config",
            "description": "Default build using Ninja",
            "generator": "Ninja",
            "binaryDir": "${sourceDir}/build/${presetName}",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Debug"
            }
        },
        {
            "name": "release",
            "displayName": "Release Build",
            "inherits": "default",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Release"
            }
        },
        {
            "name": "debug-asan",
            "displayName": "Debug with AddressSanitizer",
            "inherits": "default",
            "cacheVariables": {
                "ENABLE_ASAN": "ON"
            }
        }
    ],
    "buildPresets": [
        {
            "name": "default",
            "configurePreset": "default"
        }
    ],
    "testPresets": [
        {
            "name": "default",
            "configurePreset": "default",
            "output": {
                "outputOnFailure": true
            }
        }
    ]
}
# 使用预设
cmake --preset default
cmake --build --preset default
ctest --preset default

# 查看可用预设
cmake --list-presets
cmake --list-presets --workflow

实际项目结构

多目录项目

my_project/
├── CMakeLists.txt          # 顶层 CMakeLists
├── src/
│   ├── CMakeLists.txt
│   ├── main.c
│   └── app.c
├── include/
│   └── app/
│       ├── app.h
│       └── utils.h
├── lib/
│   ├── CMakeLists.txt
│   ├── stringlib.c
│   └── iolib.c
└── tests/
    ├── CMakeLists.txt
    └── test_app.c
# 顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(MyProject VERSION 1.0.0 LANGUAGES C)

# 全局设置
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)  # 生成 compile_commands.json

# 添加子目录
add_subdirectory(lib)
add_subdirectory(src)
add_subdirectory(tests)
# lib/CMakeLists.txt
add_library(utils STATIC stringlib.c iolib.c)
target_include_directories(utils
    PUBLIC ${PROJECT_SOURCE_DIR}/include
)
target_compile_options(utils PRIVATE -Wall -Wextra)
# src/CMakeLists.txt
add_executable(app main.c app.c)
target_link_libraries(app PRIVATE utils pthread)
# tests/CMakeLists.txt
enable_testing()

add_executable(test_app test_app.c)
target_link_libraries(test_app PRIVATE utils)

add_test(NAME test_app COMMAND test_app)

⚠️ 注意点

  1. 版本要求:始终在文件开头指定 cmake_minimum_required
  2. 使用 target_*:避免使用全局的 include_directories() / add_definitions() / link_libraries(),使用 target 版本
  3. file(GLOB):不推荐收集源文件——新增/删除文件时 CMake 不会自动重新配置
  4. 构建目录:永远在源码外部构建(out-of-source build),不要在源码目录内运行 cmake
  5. 变量展开:引用变量用 ${VAR},但在 if() 中可以直接用变量名

💡 提示

  1. 生成 compile_commands.jsonset(CMAKE_EXPORT_COMPILE_COMMANDS ON),配合 clangd 使用
  2. 并行构建cmake --build build -j$(nproc) 加速编译
  3. verbose 构建cmake --build build --verbose 查看完整编译命令
  4. 清除缓存:删除 build/CMakeCache.txt 可重置所有配置
  5. 查看构建命令cmake --build build -- VERBOSE=1 (Makefile) 或 -v (Ninja)

工程场景

场景 1:CMake 管理依赖库

# 查找系统库
find_package(OpenSSL REQUIRED)
find_package(ZLIB REQUIRED)

target_link_libraries(app PRIVATE
    OpenSSL::SSL
    OpenSSL::Crypto
    ZLIB::ZLIB
)

场景 2:切换编译器

# 使用 Clang
cmake -B build -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++

# 使用交叉编译工具链
cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchain-arm.cmake

# 使用 ccache 加速
cmake -B build -DCMAKE_C_COMPILER_LAUNCHER=ccache

场景 3:调试 CMake 问题

# 查看所有缓存变量
cmake -B build -LAH

# 开启调试输出
cmake -B build --log-level=VERBOSE

# 追踪 CMake 执行
cmake -B build --trace --trace-expand 2>&1 | tee cmake_trace.log

扩展阅读