CMakeLists.txt 完全详解

一、CMake 基础概念

1.1 什么是 CMake?

CMake 是一个跨平台的构建系统生成器,它不直接构建项目,而是生成特定平台的构建文件(如 Makefile、Visual Studio 解决方案等)。

1.2 CMakeLists.txt 的作用

  • 定义项目的构建规则

  • 管理源文件和依赖关系

  • 配置编译和链接选项

  • 支持跨平台构建

二、常用命令速查

命令 用途
add_executable 添加可执行文件
add_library 添加库文件
target_link_libraries 链接库
target_include_directories 添加头文件路径
find_package 查找外部包
add_subdirectory 添加子目录
set 设置变量
message 打印信息

三、CMakeLists.txt 核心语法

3.1 命令调用

基本语法

命令(参数1 参数2 ...)

command(arg1 arg2 arg3)

example:

cmake 复制代码
add_executable(my_app main.cpp)

带引号的参数(支持空格)

command("argument with spaces")

example:

cmake 复制代码
set(DESCRIPTION "This is my project")
message("Hello World")

多行字符串

多行字符串集中等价写法

  1. 方式1:使用多行括号参数

    cmake 复制代码
    command([=[
    This is a multi-line
    string with "quotes"
    ]=])
  2. 方式2:使用普通字符串 + 转义换行(不推荐)

    cmake 复制代码
    command("    This is a multi-line\n    string with "quotes"")
  3. 方式3:使用 ${VAR} 或 set 存储多行文本

cmake 复制代码
set(MY_TEXT [[
This is a multi-line
string with "quotes"
]])
command("${MY_TEXT}")

括号参数规则

  • [=[]=] 之间的内容完全保留原样(包括换行、缩进、引号)
  • 中间可以写任意内容,无需转义
  • = 的数量必须匹配(可以是 0 到任意多个)
cmake 复制代码
# 0 个等号(最常用)
command([[
    multi-line
    text
]])

# 2 个等号
command([==[
    can contain ]=] without closing
]==])

实际 CMake 示例

复制代码
# 生成文件内容
file(WRITE output.txt [=[
    Hello world
    This line has "quotes"
    And ${THIS_IS_NOT_VARIABLE} (no expansion)
]=])

# 执行命令时传递多行字符串
execute_process(
    COMMAND echo [=[
        line1
        line2
    ]=]
)

注意:CMake 的括号参数是原始字符串,不会展开 ${变量}(除非你用普通引号字符串)。

3.2 变量

cmake 复制代码
# 设置变量
set(MY_VARIABLE "value")
set(SOURCES main.cpp util.cpp math.cpp)

# 使用变量(${} 语法)
message(STATUS "Value: ${MY_VARIABLE}")

# 列表操作
set(LIST a b c)
list(APPEND LIST d e)
list(REMOVE_ITEM LIST b)
list(LENGTH LIST len)

# 环境变量
set(ENV{PATH} "/new/path:$ENV{PATH}")
message("PATH: $ENV{PATH}")

3.3 条件判断

cmake 复制代码
# if 语法
if(expression)
    # 真分支
elseif(another_expression)
    # 其他分支
else()
    # 假分支
endif()

# 常用表达式
if(VAR)                      # 变量非空且不为 0/NO/OFF/FALSE
if(NOT VAR)                  # 取反
if(VAR1 AND VAR2)            # 与
if(VAR1 OR VAR2)             # 或
if(DEFINED VAR)              # 变量已定义
if(COMMAND command_name)     # 命令存在
if(TARGET target_name)       # 目标存在
if(EXISTS path)              # 文件或目录存在
if(IS_DIRECTORY path)        # 是目录
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")  # 字符串比较
if(VERSION GREATER 3.10)     # 版本比较

3.4 循环

cmake 复制代码
# foreach 循环
foreach(var IN ITEMS a b c d)
    message("Item: ${var}")
endforeach()

# 范围循环
foreach(i RANGE 10)           # 0-10
foreach(i RANGE 1 10 2)       # 1 3 5 7 9
endforeach()

# 列表循环
set(FILES file1.cpp file2.cpp)
foreach(file ${FILES})
    message("Processing: ${file}")
endforeach()

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

四、项目组织

4.1 目录结构示例

复制代码
project/
├── CMakeLists.txt          # 根 CMakeLists.txt
├── include/                # 公共头文件
│   └── project/
│       └── api.h
├── src/                    # 源文件目录
│   ├── CMakeLists.txt
│   ├── main.cpp
│   └── module/
│       ├── CMakeLists.txt
│       └── impl.cpp
├── tests/                  # 测试目录
│   ├── CMakeLists.txt
│   └── test_main.cpp
├── libs/                   # 第三方库
│   └── external_lib/
└── cmake/                  # CMake 模块
    └── FindMyLib.cmake

4.2 根 CMakeLists.txt 模板

cmake 复制代码
# 版本要求
cmake_minimum_required(VERSION 3.15)

# 项目定义
project(MyProject 
    VERSION 1.0.0
    DESCRIPTION "My awesome project"
    LANGUAGES CXX C
    HOMEPAGE_URL "https://example.com"
)

# 设置策略
cmake_policy(SET CMP0077 NEW)

# 构建类型
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE "Release" CACHE STRING 
        "Build type" FORCE)
endif()

# C++ 标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# 编译选项
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
    set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
    set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
endif()

# 输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)

# 包含 CMake 模块路径
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")

# 添加子目录
add_subdirectory(src)
if(BUILD_TESTING)
    enable_testing()
    add_subdirectory(tests)
endif()

# 安装配置
include(GNUInstallDirs)
install(TARGETS my_target
    EXPORT MyProjectTargets
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

五、目标详解

5.1 目标类型

cmake 复制代码
# 可执行文件
add_executable(app main.cpp)

# 静态库
add_library(mylib STATIC lib.cpp)

# 动态库/共享库
add_library(myshared SHARED lib.cpp)

# 对象库(不链接,只编译)
add_library(myobj OBJECT obj.cpp)

# 接口库(纯头文件库)
add_library(header_only INTERFACE)
target_include_directories(header_only INTERFACE include/)

# 导入库(已存在的库)
add_library(external_lib UNKNOWN IMPORTED)
set_target_properties(external_lib PROPERTIES
    IMPORTED_LOCATION "/path/to/lib.so"
)

5.2 目标属性

cmake 复制代码
# 设置属性
set_target_properties(target_name PROPERTIES
    CXX_STANDARD 17
    OUTPUT_NAME "custom_name"
    VERSION 1.2.3
    SOVERSION 1
    POSITION_INDEPENDENT_CODE ON
)

# 获取属性
get_target_property(prop target_name PROPERTY_NAME)

# 目标包含目录
target_include_directories(target
    PRIVATE src           # 仅目标自身使用
    PUBLIC include        # 目标和使用者都使用
    INTERFACE api         # 仅使用者使用
)

# 目标编译定义
target_compile_definitions(target
    PRIVATE DEBUG_MODE=1
    PUBLIC USE_FEATURE
)

# 目标编译选项
target_compile_options(target
    PRIVATE -Wall -Wextra
    PUBLIC -pthread
)

# 目标链接库
target_link_libraries(target
    PRIVATE my_internal_lib
    PUBLIC external_lib
    INTERFACE header_only_lib
)

# 链接选项
target_link_options(target
    PRIVATE -static-libstdc++
)

六、依赖管理

6.1 find_package

cmake 复制代码
# 查找包
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)

# 使用包
if(Boost_FOUND)
    target_link_libraries(myapp ${Boost_LIBRARIES})
    target_include_directories(myapp PRIVATE ${Boost_INCLUDE_DIRS})
endif()

# 现代 CMake 方式(使用导入目标)
find_package(OpenCV REQUIRED)
target_link_libraries(myapp PRIVATE opencv::core opencv::imgproc)

6.2 FetchContent(CMake 3.11+)

cmake 复制代码
include(FetchContent)

FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG release-1.11.0
)

FetchContent_MakeAvailable(googletest)

target_link_libraries(my_test PRIVATE gtest_main)

6.3 ExternalProject

cmake 复制代码
include(ExternalProject)

ExternalProject_Add(
    project_zlib
    URL https://zlib.net/zlib-1.2.11.tar.gz
    CONFIGURE_COMMAND ./configure --prefix=${CMAKE_BINARY_DIR}/external
    BUILD_COMMAND make
    INSTALL_COMMAND make install
)

七、高级特性

7.1 生成器表达式

cmake 复制代码
# 条件编译
target_compile_definitions(app PRIVATE 
    $<$<CONFIG:Debug>:DEBUG_MODE>
    $<$<PLATFORM_ID:Windows>:_WIN32>
)

# 包含目录
target_include_directories(lib PRIVATE
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

# 链接库条件
target_link_libraries(app PRIVATE
    $<$<CXX_COMPILER_ID:GNU>:stdc++fs>
    $<$<CXX_COMPILER_ID:Clang>:c++fs>
)

7.2 自定义命令和目标

cmake 复制代码
# 添加自定义命令
add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/generated.h
    COMMAND ${CMAKE_COMMAND} -E echo "Generating file..."
    COMMAND python generate.py ${CMAKE_CURRENT_SOURCE_DIR}/input.txt
    DEPENDS generate.py input.txt
    COMMENT "Generating header file"
)

# 添加自定义目标
add_custom_target(generate_files ALL
    DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/generated.h
)

# 依赖关系
add_dependencies(app generate_files)

7.3 配置文件生成

cmake 复制代码
# 创建 config.h.in
configure_file(
    ${CMAKE_SOURCE_DIR}/config.h.in
    ${CMAKE_BINARY_DIR}/config.h
)

# config.h.in 内容
#cmakedefine HAVE_FEATURE
#cmakedefine VERSION "@PROJECT_VERSION@"

7.4 导出和安装

cmake 复制代码
# 导出目标
export(TARGETS mylib
    FILE ${CMAKE_BINARY_DIR}/MyLibTargets.cmake
    NAMESPACE MyLib::
)

# 安装导出
install(EXPORT MyLibTargets
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
    NAMESPACE MyLib::
)

# 创建包配置
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
    ${CMAKE_CURRENT_BINARY_DIR}/MyLibConfigVersion.cmake
    VERSION ${PROJECT_VERSION}
    COMPATIBILITY SameMajorVersion
)

configure_package_config_file(
    ${CMAKE_SOURCE_DIR}/cmake/MyLibConfig.cmake.in
    ${CMAKE_CURRENT_BINARY_DIR}/MyLibConfig.cmake
    INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/MyLib
)

八、调试和诊断

8.1 消息输出

cmake 复制代码
# 消息级别
message(STATUS "Info message")     # 正常信息
message(WARNING "Warning")         # 警告
message(AUTHOR_WARNING "Dev warning") # 开发警告
message(SEND_ERROR "Error")        # 错误但不终止
message(FATAL_ERROR "Fatal")       # 致命错误,终止

8.2 变量调试

cmake 复制代码
# 打印所有变量
get_cmake_property(vars VARIABLES)
foreach(var ${vars})
    message("${var}=${${var}}")
endforeach()

# 打印缓存变量
get_cmake_property(cache_vars CACHE_VARIABLES)
foreach(var ${cache_vars})
    message("${var}=${${var}}")
endforeach()

九、最佳实践

9.1 现代 CMake 建议

  1. 使用目标导向:优先使用 target_* 命令而非全局 *_DIRECTORIES
  2. 明确可见性:始终指定 PRIVATE/PUBLIC/INTERFACE
  3. 避免全局变量:尽量减少 include_directorieslink_directories
  4. 使用生成器表达式:处理条件依赖和平台差异
  5. 模块化:使用 add_subdirectory 组织代码

9.2 性能优化

cmake 复制代码
# 避免使用 file(GLOB)
# 不推荐
file(GLOB SOURCES "src/*.cpp")

# 推荐:显式列出
set(SOURCES
    src/main.cpp
    src/feature1.cpp
    src/feature2.cpp
)

# 使用 Ninja 生成器
# cmake -G Ninja -B build

9.3 常见陷阱

cmake 复制代码
# 错误:变量作用域
set(VAR "global")
function(myfunc)
    set(VAR "local")  # 局部变量
endfunction()

# 正确:使用 PARENT_SCOPE
function(myfunc)
    set(VAR "local" PARENT_SCOPE)
endfunction()

# 缓存变量
set(MY_CACHE "value" CACHE STRING "Description")

十、完整实战示例

cmake 复制代码
# 根 CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(Calculator VERSION 1.0.0)

# 构建配置
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# 选项
option(BUILD_TESTS "Build tests" ON)
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)

# 包含子目录
add_subdirectory(src)
if(BUILD_TESTS)
    enable_testing()
    add_subdirectory(tests)
endif()

# 安装
include(GNUInstallDirs)
install(TARGETS calculator
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
cmake 复制代码
# src/CMakeLists.txt
add_library(calc_lib STATIC
    calculator.cpp
    operations.cpp
)

target_include_directories(calc_lib
    PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
    PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
)

target_compile_definitions(calc_lib
    PRIVATE $<$<CONFIG:Debug>:DEBUG>
)

add_executable(calculator main.cpp)
target_link_libraries(calculator PRIVATE calc_lib)

# 版本信息
set_target_properties(calc_lib PROPERTIES
    VERSION ${PROJECT_VERSION}
    SOVERSION 1
)

十一、构建命令

11.1 配置项目(生成构建系统)

bash 复制代码
# 基础用法(推荐)
cmake -B build -S .

# 指定生成器
cmake -B build -G "Ninja"

# 指定构建类型(单配置生成器)
cmake -B build -DCMAKE_BUILD_TYPE=Release

# 多配置生成器(如VS)
cmake -B build -DCMAKE_CONFIGURATION_TYPES="Debug;Release"

# 设置安装路径
cmake -B build -DCMAKE_INSTALL_PREFIX=/usr/local

# 传递自定义变量
cmake -B build -DMY_VARIABLE=ON

# 查看所有配置选项
cmake -B build -LAH

11.2 构建项目

bash 复制代码
# 基础构建
cmake --build build

# 指定构建配置
cmake --build build --config Release

# 并行构建(指定线程数)
cmake --build build --parallel 8
# 或简写
cmake --build build -j 8

# 指定目标
cmake --build build --target my_target

# 清理(清空构建产物)
cmake --build build --target clean

# 显示详细编译命令
cmake --build build --verbose
# 或
cmake --build build -v
相关推荐
PyGata5 个月前
CMake学习笔记(一)
学习笔记·cmake·cmakelists
左直拳1 年前
C++程序从windows移植到linux后cmake脚本CMakeLists.txt的修改
linux·c++·windows·cmake·cmakelists·移植到linux
浮梦终焉1 年前
单片机工程使用链接优化-flto找不到定义_链接静态库
单片机·链接·c/c++·cmakelists