最近做项目用到ESP32,还顺便尝试换用CLion写STM32,开始不断接触到CMake编译链,感觉挺有意思也挺好用的。所以写一篇文章来简单介绍并记录一下CMake的基础用法。

简单介绍一下吧:

CMake 是个一个开源的跨平台自动化建构系统,用来管理软件建置的程序,并不依赖于某特定编译器,并可支持多层目录、多个应用程序与多个函数库。 CMake 通过使用简单的配置文件 CMakeLists.txt,自动生成不同平台的构建文件(如 Makefile、Ninja 构建文件、Visual Studio 工程文件等),简化了项目的编译和构建过程。 CMake 本身不是构建工具,而是生成构建系统的工具,它生成的构建系统可以使用不同的编译器和工具链。

CMake 的作用和优势:

  • 跨平台支持: CMake 支持多种操作系统和编译器,使得同一份构建配置可以在不同的环境中使用。
  • 简化配置: 通过 CMakeLists.txt 文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本。
  • 自动化构建: CMake 能够自动检测系统上的库和工具,减少手动配置的工作量。
  • 灵活性: 支持多种构建类型和配置(如 Debug、Release),并允许用户自定义构建选项和模块。

构建配置:

  • CMakeLists.txt 文件: CMake 的配置文件,用于定义项目的构建规则、依赖关系、编译选项等。每个 CMake 项目通常包含一个或多个 CMakeLists.txt 文件。
  • 构建目录: 为了保持源代码的整洁,CMake 鼓励使用独立的构建目录(Out-of-source 构建)。这样,构建生成的文件与源代码分开存放

CMake 的基本工作流程:

  1. 编写 CMakeLists.txt 文件: 定义项目的构建规则依赖关系
  2. 生成构建文件: 使用 CMake 生成适合当前平台的构建系统文件(例如 Makefile、Visual Studio 工程文件)。
  3. 执行构建: 使用生成的构建系统文件(如 make、ninja、msbuild)来编译项目

CMake 基础

简单介绍完 CMake ,现在来讲一下 CMake 的基础用法。

CMakeLists.txt 文件:

CMakeLists.txt 是 CMake 的配置文件,用于定义项目的构建规则、依赖关系、编译选项等,是非常重要的文件。

每个 CMake 项目通常都有一个或多个 CMakeLists.txt 文件。

文件结构和基本语法:

CMakeLists.txt 文件使用一系列的 CMake 指令来描述构建过程。常见的指令包括:

1.项目基础设置:
1. cmake_minimum_required

指定 CMake 最低版本要求。

1
cmake_minimum_required(VERSION <version>)

例如:

1
cmake_minimum_required(VERSION 3.15)
2. project

定义项目名称和使用的语言。

1
project(<project_name> [<language>...])

例如:

1
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
2. 生成目标:
1. add_executable

添加可执行文件目标。

1
add_executable(<target> <source_files>...)

例如:

1
add_executable(MyExecutable main.cpp other_file.cpp)
2. add_library

添加库文件目标。

1
add_library(<target> <source_files>...)

例如:

1
2
3
4
5
6
7
8
# 静态库
add_library(my_static_lib STATIC utils.cpp)

# 动态库
add_library(my_shared_lib SHARED utils.cpp)

# 模块库
add_library(my_module_lib MODULE plugin.cpp)
3. add_subdirectory

添加子目录进行构建。

1
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

例如:

1
2
add_subdirectory(src)
add_subdirectory(third_party/glad)
3. 头文件和库链接:
1. target_include_directories

为目标添加包含目录。

1
2
3
4
target_include_directories(TARGET target_name
[BEFORE | AFTER]
[SYSTEM] [PUBLIC | PRIVATE | INTERFACE]
[items1...])

例如:

1
2
3
4
target_include_directories(my_app PRIVATE 
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/third_party
)

PRIVATE - 仅对目标本身可见 PUBLIC - 对目标及其依赖目标可见 INTERFACE - 仅对依赖此目标的其他目标可见

为目标链接库。

1
2
3
target_link_libraries(<target> [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> <item1> [<item2>...]
<INTERFACE|PUBLIC|PRIVATE> <item3> [<item4>...] ...)

例如:

1
2
3
4
5
6
7
target_link_libraries(my_app PRIVATE mylib)
target_link_libraries(my_app PUBLIC Threads::Threads)
target_link_libraries(my_app PRIVATE
mylib1
mylib2
Threads::Threads
)
4. 变量和属性
1. set

设置变量。

1
2
set(<variable> <value> [CACHE <type> <docstring> [FORCE]])
set(<variable> <value> PARENT_SCOPE)

例如:

1
2
3
4
set(SOURCE_FILES main.cpp utils.cpp)           # 普通变量
set(CMAKE_CXX_STANDARD 17) # CMake 变量
set(MY_VAR "value" CACHE STRING "Description") # 缓存变量(用户可修改)
set(EXTRA_LIBS lib1 lib2 lib3 PARENT_SCOPE) # 传递给父目录
2. set_target_properties

为目标设置属性。

1
2
3
set_target_properties(<target> [<target>...]
PROPERTIES prop1 value1
[prop2 value2] ...)

例如:

1
2
3
4
5
6
set_target_properties(mylib PROPERTIES
VERSION 1.2.0 # 库版本号
SOVERSION 1 # 符号版本号
OUTPUT_NAME "custom_lib_name" # 自定义输出名
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" # 输出目录
)
5. 查找包
1. find_package

查找依赖包。

1
2
3
find_package(<PackageName> [version] [EXACT] [QUIET] [REQUIRED]
[[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS [components...]])

例如:

1
2
3
find_package(Boost 1.70 REQUIRED COMPONENTS filesystem system)  # 查找 Boost
find_package(OpenCV REQUIRED) # 查找 OpenCV
find_package(Threads REQUIRED) # 查找线程库
2. pkg_check_modules
1
2
pkg_check_modules(<prefix> [REQUIRED] [QUIET]
<module> [<module>...])

例如:

1
2
3
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
pkg_check_modules(OPENSSL libssl libcrypto)
# 使用结果: ${GTK3_INCLUDE_DIRS}, ${GTK3_LIBRARIES}, ${GTK3_CFLAGS_OTHER}
6. 条件控制
1. option

定义用户可配置的选项,值为 ON 或 OFF,可在 cmake-gui 或 cmake 命令行中修改。

1
option(<option_variable> "description" [initial_value])

例如:

1
2
3
option(BUILD_TESTS "Build test programs" ON)       # 是否构建测试
option(ENABLE_LOGGING "Enable logging" OFF) # 是否启用日志
option(USE_FEATURE_X "Use feature X" FALSE) # 是否使用某功能
2. 条件语句

根据条件执行不同的 CMake 命令,支持多种条件判断。

1
2
3
4
5
6
7
if(<condition>)
<commands>
elseif(<condition>)
<commands>
else(<condition>)
<commands>
endif(<condition>)

例如:

1
2
3
4
5
6
7
8
9
if(WIN32)
message(STATUS "Windows system")
set(PLATFORM_LIBS winmm.lib)
elseif(UNIX AND NOT APPLE)
message(STATUS "Linux system")
set(PLATFORM_LIBS m)
elseif(APPLE)
message(STATUS "macOS system")
endif()
7. 循环结构
1. foreach

遍历列表或数字范围,对每个元素执行一组命令。

1
2
3
4
5
6
7
foreach(<loop_var> <items>)
<commands>
endforeach()

foreach(<loop_var> RANGE <start> <stop> [<step>])
<commands>
endforeach()

例如:

1
2
3
4
5
6
7
8
set(SOURCES file1.cpp file2.cpp file3.cpp)
foreach(src ${SOURCES})
message(STATUS "Processing: ${src}")
endforeach()

foreach(i RANGE 0 10 2)
message(STATUS "i = ${i}") # 0, 2, 4, 6, 8, 10
endforeach()
2. while

当条件为真时循环执行一组命令。

1
2
3
while(<condition>)
<commands>
endwhile()

例如:

1
2
3
4
5
set(CNT 0)
while(CNT LESS 5)
message(STATUS "Count: ${CNT}")
math(EXPR CNT "${CNT} + 1")
endwhile()
8. 文件和路径操作
1. file

执行文件系统操作,如读写文件、创建目录、搜索文件等。

1
2
3
4
5
6
7
file(READ <filename> <out-var> [...])
file(WRITE <filename> <content>...)
file(APPEND <filename> <content>...)
file(GLOB <out-var> [CONFIGURE_DEPENDS] [<globbing-expressions>...])
file(GLOB_RECURSE <out-var> [CONFIGURE_DEPENDS] [<globbing-expressions>...])
file(MAKE_DIRECTORY [<directories>...])
file(COPY <files>... DESTINATION <dir>)

例如:

1
2
3
4
5
file(READ "version.txt" PROJECT_VERSION)           # 读取文件内容
file(WRITE "${CMAKE_BINARY_DIR}/out.txt" "text") # 写入文件
file(GLOB SOURCES "src/*.cpp") # 匹配源文件
file(GLOB_RECURSE ALL_SOURCES "src/*") # 递归匹配
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/output) # 创建目录
2. configure_file

复制文件并替换其中以 @VAR@${VAR} 格式的变量。

1
2
3
configure_file(<input> <output>
[COPYONLY] [ESCAPE_QUOTES] [@ONLY]
[NEWLINE_STYLE [UNIX|DOS] ])

例如:

1
2
3
4
5
# config.h.in 内容: #define VERSION "@PROJECT_VERSION@"
configure_file(
${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
${CMAKE_CURRENT_BINARY_DIR}/config.h
)
9. 消息输出
1. message

向用户输出信息,用于调试、状态显示或错误提示。

1
message([<mode>] "message text")
Mode 功能
STATUS 一般状态信息,前缀 --
WARNING 警告信息,继续执行
AUTHOR_WARNING 开发者警告
SEND_ERROR 错误信息,继续执行
FATAL_ERROR 致命错误,终止配置
DEBUG 调试信息

例如:

1
2
3
message(STATUS "Configuring project...")     # 输出状态
message(WARNING "Deprecated feature!") # 输出警告
message(FATAL_ERROR "Configuration failed!") # 致命错误
10. 测试和安装
1. enable_testing

启用当前目录及子目录的测试功能,使 add_test 可以添加测试。

1
enable_testing()

例如:

1
2
enable_testing()
add_subdirectory(tests)
2. add_test

注册一个测试用例,指定名称和要执行的命令。

1
add_test(NAME <name> COMMAND <command> [<args>...])

例如:

1
2
add_test(NAME MyTest COMMAND my_test_executable)
add_test(NAME OtherTest COMMAND python3 ${CMAKE_SOURCE_DIR}/run_test.py)
3. install

定义文件、目标、目录的安装规则,指定安装位置。

1
2
3
4
5
6
7
8
9
install(TARGETS <target>... 
[RUNTIME DESTINATION <dir>]
[LIBRARY DESTINATION <dir>]
[ARCHIVE DESTINATION <dir>]
[INCLUDES DESTINATION <dir>])

install(DIRECTORY <dir>... DESTINATION <dir>)
install(FILES <file>... DESTINATION <dir>)
install(EXPORT <export-name> DESTINATION <dir>)

例如:

1
2
3
4
5
install(TARGETS my_app RUNTIME DESTINATION bin)              # 安装可执行文件
install(TARGETS mylib LIBRARY DESTINATION lib) # 安装共享库
install(DIRECTORY include/ DESTINATION include/myapp) # 安装目录
install(FILES config.conf DESTINATION etc) # 安装文件
install(EXPORT myTargets DESTINATION cmake) # 导出目标
11. 其他常用指令
1. target_compile_definitions

为目标添加预处理宏定义,编译时相当于 -D 选项。

1
2
3
target_compile_definitions(<target>
<INTERFACE|PUBLIC|PRIVATE> [items1...]
<INTERFACE|PUBLIC|PRIVATE> [items2...] ...)

例如:

1
2
target_compile_definitions(my_app PRIVATE DEBUG ENABLE_LOGGING)
target_compile_definitions(mylib PUBLIC USE_FEATURE_X)
2. target_compile_options

为目标添加编译选项,传递给编译器的额外flags。

1
2
target_compile_options(<target> [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [options]...)

例如:

1
target_compile_options(my_app PRIVATE -Wall -Wextra -Wpedantic)
3. target_sources

为已存在的目标追加源文件。

1
2
target_sources(<target> [BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [<file>...])

例如:

1
2
3
4
target_sources(my_lib PUBLIC 
${CMAKE_CURRENT_SOURCE_DIR}/src/a.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/b.cpp
)
4. include

加载并执行另一个 CMake 文件,类似于 C 语言的 #include

1
include(<file> [OPTIONAL] [RESULT_VARIABLE <var>])

例如:

1
2
include(${CMAKE_SOURCE_DIR}/cmake/FindPackage.cmake)       # 包含文件
include(${CMAKE_SOURCE_DIR}/cmake/utils.cmake OPTIONAL) # 可选包含
5. function

定义一个函数,创建新的 CMake 命令,支持参数传递。

1
2
3
function(<name> [<arg1> <arg2> ...])
<commands>
endfunction()

例如:

1
2
3
4
5
6
function(add_my_executable name)
add_executable(${name} ${ARGN})
target_link_libraries(${name} PRIVATE Threads::Threads)
endfunction()

add_my_executable(my_app main.cpp utils.cpp)
6. macro

定义一个宏,与函数类似但不在独立作用域执行,变量直接操作调用者作用域。

1
2
3
macro(<name> [<arg1> <arg2> ...])
<commands>
endmacro()

例如:

1
2
3
4
5
6
macro(add_simple_target name)
add_executable(${name} ${ARGN})
message(STATUS "Created target: ${name}")
endmacro()

add_simple_target(my_app main.cpp)
12. 常用变量速查
变量 说明
CMAKE_SOURCE_DIR 项目根目录(包含顶层 CMakeLists.txt)
CMAKE_BINARY_DIR 构建目录(执行 cmake 的目录)
CMAKE_CURRENT_SOURCE_DIR 当前处理的 CMakeLists.txt 所在目录
CMAKE_CURRENT_BINARY_DIR 当前构建目录
CMAKE_PROJECT_NAME 最顶层项目的名称
PROJECT_NAME 当前 project() 定义的项目名称
PROJECT_VERSION 当前项目的版本号
CMAKE_CXX_STANDARD C++ 标准版本(如 11, 14, 17)
CMAKE_CXX_COMPILER C++ 编译器路径
CMAKE_CURRENT_LIST_FILE 当前处理的 CMake 文件路径
CMAKE_CURRENT_LIST_LINE 当前处理的行号
CMAKE_VERSION CMake 版本号
CMAKE_BUILD_TYPE 构建类型:Debug 或 Release
CMAKE_SOURCE_DIR 项目根目录