CMake 学习笔记
目录
快速入门
CMake 工作原理

CMake、构建工具、编译器与最终产物之间的关系。
安装指定版本 CMake(源码编译)
Linux:
wget https://github.com/Kitware/CMake/releases/download/v3.23.1/cmake-3.23.1.tar.gz
tar -xvf cmake-3.23.1.tar.gz
cd cmake-3.23.1
./configure
make -j16
sudo make install
- 默认前缀
/usr/local:可执行文件(cmake/ctest/cpack/ccmake)装到/usr/local/bin,模块/模板数据装到/usr/local/share/cmake-3.23。 /usr/local/bin通常已在 PATH 中,一般无需再 export;若不在,用export PATH=/usr/local/bin:$PATH(不要把share/cmake-3.23加进 PATH,那里没有可执行文件)。
Windows: 官网 https://cmake.org/files/v3.23/ 下载,把 ...\cmake-3.23.1-windows-x86_64\bin 加入 PATH。
out-of-source 构建规范
cmake -S . -B build # 配置:源码在 .,构建产物放 build/
cmake --build build -jN # 构建(-jN 并行)
-S <dir>:源码目录(含顶层CMakeLists.txt的目录,不是 CMakeLists.txt 文件本身)。-B <dir>:构建目录,不存在会自动创建,所有中间文件/生成的 makefile 都放这里。- 选项大小写敏感:是大写
-S/-B,-s/-b无效;注意-S . -B b中间的空格不能少(-S .-B b会把.-B当成-S的值)。 - 好处:产物与源码隔离,清理只需删
build/。
Windows(需先打开 Developer Command Prompt):
cmake -S . -B build -G "NMake Makefiles"
cmake --build build
第一个 CMakeLists.txt
# 指定 cmake 最低版本
cmake_minimum_required(VERSION 3.20)
# 项目名称
project(first_cmake)
# 生成可执行程序
add_executable(first_cmake 101first_cmake.cpp)
配置 + 构建:
cmake -S . -B build # 配置
cmake --build build # 构建
变量与流程控制
变量使用
- 变量引用是值替换,未设置的变量返回空字符串。
- 变量引用可嵌套,从内向外求值。
- 变量名大小写敏感。
message()拼接多个字符串参数时无分隔符。
set(VAR1 "测试变量VAR1的值")
message("VAR1= " ${VAR1})
message("VAR1 in string ${VAR1}")
message("\${VAR1} = ${VAR1}") # 想打印字面 ${} 用 \ 转义
set(VAR2 "VAR1")
message("VAR2= " ${${VAR2}}) # 嵌套访问,取到 VAR1 的值
unset(VAR1) # 取消变量
message("\${VAR1} = ${VAR1}")
# 注意:多参数无分隔符,下面输出为“参数1参数2参数3”,不是带空格的
message("参数1" "参数2" "参数3")

变量引用与取消变量后的输出示例。
路径相关变量
| 变量 | 含义 |
|---|---|
CMAKE_SOURCE_DIR | 整个构建的顶层源码目录 |
PROJECT_SOURCE_DIR | 当前 project() 的源码目录 |
CMAKE_CURRENT_SOURCE_DIR | 当前 CMakeLists.txt 所在目录 |
CMAKE_CURRENT_LIST_DIR | 当前被处理文件所在目录,包含被 include 的 .cmake 模块 |
作用域区别:
CMAKE_SOURCE_DIR是第一次进入的CMakeLists.txt所在目录,不随add_subdirectory()或子目录的project()改变。- 顶层的
PROJECT_SOURCE_DIR等于CMAKE_SOURCE_DIR;子目录再次调用project()后,在其作用域内变为子项目根。 - 写相对当前
CMakeLists.txt的路径时,使用CMAKE_CURRENT_SOURCE_DIR最稳妥。
用法建议:
# 本模块内的路径用 CMAKE_CURRENT_LIST_DIR(推荐)
${CMAKE_CURRENT_LIST_DIR}/include
# 引用其他模块用 PROJECT_SOURCE_DIR / CMAKE_SOURCE_DIR
${PROJECT_SOURCE_DIR}/modules/parameter/include
${PROJECT_SOURCE_DIR}/3rdpart/tinyxml2
CMake 自带变量
- 提供信息:
PROJECT_NAME(对应project()的名字)。 - 改变行为:
BUILD_SHARED_LIBS(ON 动态库 / OFF 静态库,默认 OFF)。 - 描述系统:
MSVC、WIN32(=1 为 Windows)、UNIX(Unix 或类 Unix,包含 macOS 等,不等于仅 Linux)、CMAKE_SYSTEM_NAME。 - 控制构建:
CMAKE_COLOR_MAKEFILE、输出路径等。
set(BUILD_SHARED_LIBS ON) # 后续 add_library 默认建动态库
add_definitions(-Dxlog_STATIC) # 向 C++ 传宏;现代写法见“Target 与依赖管理”
message(" MSVC = " ${MSVC})
message(" WIN32 = " ${WIN32})
message(" UNIX = " ${UNIX})
message("CMAKE_SYSTEM_NAME = " ${CMAKE_SYSTEM_NAME})
# 把头文件也列进来:作用是让 IDE/工程生成器在项目树里显示(头文件本身不编译)
# 注意:并不是“列了头文件才会在头文件改动时重编”——重编靠编译器/CMake 的依赖扫描自动完成
add_library(${PROJECT_NAME} xlog.cpp xlog.h)

动态库构建输出:libxlog.so。

静态库构建输出:libxlog.a。
if 流程控制
if(<conditional>)
elseif(<conditional>)
else()
endif()
if(<constant>) 常量:
- 真:
ON、YES、TRUE、Y或非零数(含浮点数)。 - 假:
0、OFF、NO、FALSE、N、IGNORE、NOTFOUND、空字符串,或以-NOTFOUND结尾的字符串。
if(<variable>) 变量: 非假值常量为真;未定义变量为假;环境变量总为假。
if(1)
message("1 is true")
endif()
if(0)
message("0 is true")
else()
message("0 is false")
endif()
if(VAR_NOT_DEF) # 未定义变量默认为假
message("VAR_NOT_DEF is true")
else()
message("VAR_NOT_DEF is false")
endif()
if("ON")
message("String ON is true")
endif()

常量、字符串与未定义变量的条件判断示例。
一元 / 二元 / 正则匹配
一元判断:DEFINED(变量是否定义)、COMMAND(是否为已定义的命令/宏/函数)、EXISTS(路径是否存在)。
二元判断:EQUAL(数字相等)、STREQUAL(字符串相等)。
set(VAR1 123)
if(VAR1 EQUAL 123)
message("VAR1 EQUAL 123")
endif()
# 推荐用 STREQUAL
if(VAR1 STREQUAL "123")
message("VAR1 STREQUAL 123")
endif()

数字相等与字符串相等的判断结果对比。
MATCHES 正则:if(<variable|string> MATCHES regex)
set(STR1 "abcd1234")
if(STR1 MATCHES "[a-z]+")
message("MATCHES [a-z]+ true")
endif()
if(STR1 MATCHES "^[a-z]+$")
message("full match")
else()
message("no full match")
endif()

正则匹配对比:包含匹配与使用 ^、$ 限定的完整匹配。
cache 缓存变量
语法:set(<var> <value>... CACHE <type> <docstring> [FORCE])
- type:
BOOL(ON/OFF 勾选)、FILEPATH(选文件)、PATH(选目录)、STRING、INTERNAL(内部,不显示在 GUI)。 - docstring:说明文档。
set(VAR1 "VALUE1-1" CACHE STRING "cache doc")
set(VAR1 "VALUE1-2" CACHE STRING "cache doc") # 缓存变量二次赋值不生效
message("VAR1 = ${VAR1}")
set(VAR1 "VALUE1-3" CACHE STRING "cache doc" FORCE) # FORCE 强制修改

缓存变量取值与强制修改后的输出示例。
不同类型的缓存变量与 option() 示例:
set(VAR_BOOL1 "ON" CACHE BOOL "bool 001")
set(VAR_FILE "filepath" CACHE FILEPATH "var file path")
set(VAR_PATH "path" CACHE PATH "var path")
set(VAR_INTERNAL "internal var" CACHE INTERNAL "internal var")
option(OPT1 "opt1 doc" OFF)
option(OPT2 "opt2 doc" ON)
- Windows:
cmake-gui。 - Linux:先
cmake -S . -B build生成 cache,再ccmake build交互修改。

Windows:在 cmake-gui 中查看和修改缓存变量。

Linux:在 ccmake 中查看和修改缓存变量。
普通变量与缓存变量重名(CACHE 覆盖)
set(normalVar1 "normal var1")
set(normalVar1 "Cache var1" CACHE STRING "cache doc")
message("normalVar1 = ${normalVar1}")
message("CACHE{normalVar1} = $CACHE{normalVar1}")

同名普通变量与缓存变量保留不同取值的示例。
# CMP0126:NEW 保留同名普通变量;OLD 仅在新建缓存项、原缓存项无类型、FORCE/INTERNAL 等条件下移除普通变量
cmake_policy(SET CMP0126 OLD)

设置 CMP0126 OLD 后的输出示例。
用 -D 传递缓存变量
cmake -S . -B b5 -D PARA1=para00001
第二次不带 -D 运行仍会保留 PARA1(已写入 cache),除非再次 -D PARA1=new 覆盖。

通过 -D PARA1=para00001 设置缓存变量。

通过 -D PARA1=new 覆盖已有缓存值。
include 用法
include("cmake/test_cmake.cmake")
include("cmake/test_cmake1.cmake" OPTIONAL) # 找不到不报错
include("cmake/test_cmake1.cmake" OPTIONAL RESULT_VARIABLE ret)
message("ret = ${ret}") # 未找到为 NOTFOUND
include("cmake/test_cmake.cmake" OPTIONAL RESULT_VARIABLE ret)
message("ret = ${ret}") # 找到则为绝对路径

RESULT_VARIABLE 返回未找到标记或模块绝对路径的示例。
括号注释
3.0 之后支持括号注释 #[[ ... ]],可跨行、可行内:
message("参数1" #[[中间注释]] "参数2") # 输出:参数1参数2
属性
属性是作用域为特定对象(全局/目录/目标/源文件/测试/缓存)的变量。
set_property/get_property:设置 / 读取属性。define_property:定义自定义属性。
set_property(GLOBAL PROPERTY TEST_GLOBAL "test global 001")
set_property(SOURCE myfile.cpp PROPERTY COMPILE_FLAGS "-Wall")
get_property(result TARGET my_target PROPERTY CXX_STANDARD)

属性作用域类型及 SOURCE 附加选项说明。
作用域类型:GLOBAL(全局)、DIRECTORY [<dir>](目录)、TARGET <target>(目标)、SOURCE <src>(源文件)、INSTALL <file>、TEST <test>、CACHE <entry>、VARIABLE。
构建配置与日志
编译类型与 C++ 标准
CMAKE_BUILD_TYPE:Debug/Release/RelWithDebInfo/MinSizeRel。单配置生成器(Unix Makefiles、Ninja)需显式设置才生效;嵌入式常用MinSizeRel(→-Os)。- C++ 标准优先用标准变量或 target 级命令,而非手写
-std=:
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 或 target 级:target_compile_features(app PUBLIC cxx_std_11)
追加编译选项
注意:给
CMAKE_CXX_FLAGS追加内容时必须加引号,否则会存成分号列表(如"-O2;-g"),传给编译器时成为畸形单参数,原值非空时会编译失败。
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g")
# 更稳妥的等价写法
string(APPEND CMAKE_CXX_FLAGS " -g")
# 正确处理列表语义
add_compile_options(-g)
其它示例中出现的变量:
cmake_minimum_required(VERSION 3.0)
project(chat)
# 最终可执行文件输出路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 头文件搜索路径(目录作用域,可被后续 add_subdirectory 的子目录继承;不会递归搜索磁盘上的子目录)
include_directories(${PROJECT_SOURCE_DIR}/include)
收集源文件
aux_source_directory:收集目录下所有源文件,语法为 aux_source_directory(<dir> <var>)。
aux_source_directory("./src" LIB_SRCS)
file(GLOB ...):按通配收集。语法 file(<operation> <output_variable> <args...>),常用 operation:GLOB(收集)、GLOB_RECURSE(递归收集)、COPY、REMOVE、WRITE。
file(GLOB SRC2 ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp)
file(GLOB H_FILE "${INCLUDE_PATH}/xcpp/*.h")
常见错误:
file(GLOB SRC2 FILE ...)中的FILE不是合法关键字,会被当成匹配不到的 glob 模式静默吞掉,应删去。
file(GLOB) 合法选项只有 LIST_DIRECTORIES / RELATIVE / CONFIGURE_DEPENDS。
注意:GLOB 结果在配置阶段固定,增删文件不会自动重配(除非用
CONFIGURE_DEPENDS);单个已知源文件建议直接set(SRC ...)。
输出路径设置
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/lib") # .so
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/bin") # 可执行/dll/pdb
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/lib") # .a 静态库
生成 compile_commands.json
供 clangd / VS Code 等做代码索引:
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
VS Code CMake Tools 也可通过 .vscode/settings.json 配置:
{
"cmake.configureArgs": [
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
]
}
分步编译与清理指定 target
cmake --build . --target help # 查看所有目标
cmake --build . --target 101first_cmake.i # 预处理
cmake --build . --target 101first_cmake.s # 编译到汇编
cmake --build . --target 101first_cmake.o # 汇编到目标文件
cmake --build . --target clean # 清理

--target help 列出清理、预处理、汇编等可用目标。
调试:打印生成的具体指令
set(CMAKE_VERBOSE_MAKEFILE ON) # 默认 OFF
- 或
cmake --build . -v(第一次生成就要加-v,否则日志不完整,可清理后重生成)。
message 用法
日志级别
| 级别 | 说明 | 输出流 |
|---|---|---|
FATAL_ERROR | 终止 CMake 过程 | stderr |
SEND_ERROR | 进程继续但最终不生成(不会执行 add_executable/add_library),打印行号 | stderr |
WARNING | 打印代码路径和行号 | stderr |
无关键字(NOTICE) | 等同于不加级别 message("...") | stderr |
STATUS | 加前缀 --,用户可能感兴趣 | stdout |
VERBOSE | 默认不显示的详细信息 | stdout |
DEBUG | 加前缀 -- | stdout |
TRACE | 加前缀 -- | stdout |
message(FATAL_ERROR "TEST FATAL ERROR") # 终止;其后不执行
message(SEND_ERROR "TEST SEND ERROR") # 继续但不生成目标
message(WARNING "TEST WARNING")
message("TEST NOTICE") # 等同无级别
message(STATUS "TEST STATUS")
message(VERBOSE "TEST VERBOSE") # 需 --log-level=VERBOSE 才显示

WARNING 输出示例:包含文件位置与警告内容。

NOTICE 与 STATUS 输出对比:STATUS 带有 -- 前缀。
设置显示级别与重定向:
cmake -S . -B build --log-level=VERBOSE
cmake -S . -B build --log-level=VERBOSE > log.txt # 仅 stdout 重定向到文件
cmake -S . -B build --log-level=VERBOSE > log.txt 2>&1 # stdout 和 stderr 都写入 log.txt
常见错误:重定向应写成
> log.txt 2>&1,不要在2>&1前再加一个>。

使用 --log-level=VERBOSE 后显示详细日志。
查找库日志(CHECK_START / CHECK_PASS / CHECK_FAIL)
message(CHECK_START "查找 xcpp")
set(CMAKE_MESSAGE_INDENT "--") # 嵌套查找的缩进
message(CHECK_START "查找 xlog")
message(CHECK_PASS "成功")
message(CHECK_START "查找 xtheadpool")
message(CHECK_FAIL "失败")
set(CMAKE_MESSAGE_INDENT "") # 取消缩进
message(CHECK_FAIL "失败")

嵌套查找日志:展示缩进及成功、失败状态。
让 message 输出颜色
# \033[显示方式;前景色;背景色m ... \033[0m
# 显示方式:0-默认 1-高亮 2-低亮 3-下划线 4-闪烁 5-慢闪 6-快闪 7-反白 8-隐藏 9-划掉
string(ASCII 27 Esc)
set(Red "${Esc}[0;31m") # 红色
set(Blue "${Esc}[1;34m") # 蓝色高亮
set(RedB "${Esc}[1;31;40m") # 红字黑底
set(End "${Esc}[0m")
message("${Red} 红色内容${End}")
message("${Blue} 蓝色内容${End}")
message("${RedB} 红色+背景内容${End}")
前景色与背景色对照:
| 颜色 | 前景色 | 背景色 |
|---|---|---|
| 黑色 | 30 | 40 |
| 红色 | 31 | 41 |
| 绿色 | 32 | 42 |
| 黄色 | 33 | 43 |
| 蓝色 | 34 | 44 |
| 紫红色 | 35 | 45 |
| 青蓝色 | 36 | 46 |
| 白色 | 37 | 47 |
常见错误:黄色的背景色代码为
43,不要误写成34。

终端中的文字颜色与背景色效果。
Target 与依赖管理
现代 CMake 提倡以「目标(target)」为中心组织依赖,而非全局的 include_directories / add_definitions / link_libraries。
add_library(创建库目标)
add_library() 可以从源码创建库目标,也可以用 IMPORTED 引用已编译的外部库。下面先介绍从源码创建库的语法,实际编译在 cmake --build 时执行。
add_library(<name> [STATIC|SHARED|MODULE] <sources>...)
<name>是项目内唯一的目标名,例如xlog;链接该目标时也使用这个名字。<sources>是源文件列表,例如src/xlog.cpp。Linux 默认会为库文件添加lib前缀及相应扩展名,目标名无需写成libxlog.a。
静态库、动态库与插件库
| 类型 | 用途 | Linux 默认产物(目标名为 xlog) |
|---|---|---|
STATIC | 静态库,在链接时把需要的目标代码纳入使用它的程序或库 | libxlog.a |
SHARED | 动态库,可被其他目标链接,并在运行时加载 | libxlog.so |
MODULE | 插件库,通常通过 dlopen() 等方式加载,不能作为其他目标的链接依赖 | libxlog.so |
分别创建静态库和动态库时,使用不同的目标名:
add_library(xlog_static STATIC src/xlog.cpp) # libxlog_static.a
add_library(xlog_shared SHARED src/xlog.cpp) # libxlog_shared.so
库类型及命名规则参见 add_library 官方文档。
省略类型与 BUILD_SHARED_LIBS
省略类型时,BUILD_SHARED_LIBS 为真则创建 SHARED,否则创建 STATIC。CMake 本身不预设这个变量;未定义时按静态库处理。
通常在顶层、创建库目标之前提供一个开关:
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
add_library(xlog src/xlog.cpp)
配置时切换为动态库:
cmake -S . -B build-shared -DBUILD_SHARED_LIBS=ON
cmake --build build-shared
注意:这个开关只影响省略类型的
add_library()调用;显式写了STATIC或SHARED的目标不受影响。参见 BUILD_SHARED_LIBS 官方文档。
示例:创建库并链接到程序
假设已有以下源文件,main.cpp 通过 xlog.h 调用 xlog.cpp 中的函数:
library_demo/
├── CMakeLists.txt
├── include/
│ └── xlog.h
├── src/
│ └── xlog.cpp
└── main.cpp
CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(library_demo LANGUAGES CXX)
add_library(xlog STATIC
src/xlog.cpp
)
target_include_directories(xlog PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/include"
)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE xlog)
PUBLIC 使 xlog 和使用它的 app 都能找到 include/ 下的头文件;链接目标 xlog 也建立了构建依赖,无需手写 .a 文件路径。参见 CMake 目标与依赖说明及 target_include_directories 官方文档。
在项目根目录执行:
cmake -S . -B build
cmake --build build
./build/app
在 Linux 的 Unix Makefiles 生成器下,默认得到 build/libxlog.a 和 build/app。把示例中的 STATIC 改为 SHARED,即可构建动态库 build/libxlog.so。
如果在 Linux 中将静态库继续链接进动态库,通常需要在创建静态库后为它开启 PIC:
set_target_properties(xlog PROPERTIES POSITION_INDEPENDENT_CODE ON)
SHARED、MODULE 目标默认开启 PIC。参见 POSITION_INDEPENDENT_CODE 官方文档。
导入外部库:IMPORTED
已有第三方提供的 .a 或 .so 时,可以用 IMPORTED 将它注册为 CMake 目标,再通过目标名链接。CMake 不会为该库生成编译规则;库文件和头文件需要事先准备好。
add_library(<name> <type> IMPORTED [GLOBAL])
| 参数 | 含义 |
|---|---|
<name> | 导入目标名,例如 xlog::xlog,不必与库文件名相同 |
<type> | 导入 .a 使用 STATIC,导入 Linux .so 使用 SHARED |
GLOBAL | 可选;默认仅在创建目标的目录及其子目录可见,加上后整个项目可见 |
导入静态库
假设 main.cpp 通过 xlog.h 调用外部库中的函数,目录如下:
imported_library_demo/
├── CMakeLists.txt
├── main.cpp
└── third_party/
└── xlog/
├── include/
│ └── xlog.h
└── lib/
└── libxlog.a
CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(imported_library_demo LANGUAGES CXX)
set(XLOG_ROOT "${CMAKE_CURRENT_LIST_DIR}/third_party/xlog")
add_library(xlog::xlog STATIC IMPORTED)
set_target_properties(xlog::xlog PROPERTIES
IMPORTED_LOCATION "${XLOG_ROOT}/lib/libxlog.a"
INTERFACE_INCLUDE_DIRECTORIES "${XLOG_ROOT}/include"
)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE xlog::xlog)
| 属性 | 作用 |
|---|---|
IMPORTED_LOCATION | 库文件的完整路径,应指向 libxlog.a 文件,而非 lib/ 目录 |
INTERFACE_INCLUDE_DIRECTORIES | 使用该库的目标所需的头文件目录;app 链接 xlog::xlog 后会自动获得它 |
头文件目录也可以通过 target_include_directories(xlog::xlog INTERFACE "${XLOG_ROOT}/include") 设置,用来替代上面的 INTERFACE_INCLUDE_DIRECTORIES 属性。参见 target_include_directories 官方文档。
构建并运行:
cmake -S . -B build
cmake --build build
./build/app
导入动态库
准备好 third_party/xlog/lib/libxlog.so 后,用下面的声明和属性设置替换静态库示例中的对应部分;创建 app 和链接目标的写法保持一致:
add_library(xlog::xlog SHARED IMPORTED)
set_target_properties(xlog::xlog PROPERTIES
IMPORTED_LOCATION "${XLOG_ROOT}/lib/libxlog.so"
INTERFACE_INCLUDE_DIRECTORIES "${XLOG_ROOT}/include"
)
动态库在运行时需要由加载器找到。部署程序时,还要部署所需的 .so 及其依赖,并配置相应的运行时搜索路径。参见 CMake 动态库说明。
声明外部库的额外依赖
如果外部库还依赖其他库,应把这些依赖记录到导入目标的接口中。例如,xlog 需要线程库时,在创建 xlog::xlog 后添加:
find_package(Threads REQUIRED)
target_link_libraries(xlog::xlog INTERFACE Threads::Threads)
这样 app 仍只需链接 xlog::xlog,就会同时获得它声明的线程依赖。参见 INTERFACE_LINK_LIBRARIES 官方文档。
导入目标与 GLOBAL 的完整规则参见 CMake 导入库指南;库文件路径要求参见 IMPORTED_LOCATION 官方文档。
其他常用形式
| 形式 | 声明示例 | 用途 |
|---|---|---|
OBJECT | add_library(xlog_obj OBJECT src/xlog.cpp) | 只编译出目标文件,不打包成 .a 或链接成 .so,供其他目标复用 |
INTERFACE | add_library(xlog_headers INTERFACE) | 不编译、不生成库文件,用于传递头文件目录、宏等使用要求,常用于纯头文件库 |
IMPORTED | add_library(vendor STATIC IMPORTED) | 声明已有的预编译库,不重新编译;还需设置库文件位置等属性 |
ALIAS | add_library(demo::xlog ALIAS xlog) | 为已有的 xlog 目标创建别名;配置属性时使用原目标 |
这些形式使用各自的参数格式,参见 add_library 官方文档。IMPORTED 的完整示例见本节导入外部库:IMPORTED,库文件的搜索方式见链接预编译的第三方库。
target_include_directories
为特定目标设置头文件搜索目录,支持三种作用域:
target_include_directories(<target> [PRIVATE|PUBLIC|INTERFACE] <dirs>)
PRIVATE:仅当前目标可见,下游不继承。PUBLIC:当前目标 + 依赖它的下游都可见。INTERFACE:当前目标不用,只有下游用。
target_link_libraries(依赖传播)
组织多库交叉编译工程的核心,PUBLIC/PRIVATE/INTERFACE 在这里真正发挥作用——它会自动传播被链接目标的 usage requirements(头文件目录、宏、编译/链接选项等):
target_link_libraries(app PRIVATE foo) # foo 仅 app 自己用
target_link_libraries(app PUBLIC foo) # app 及其下游都继承 foo 的 usage requirements
target_link_libraries(app INTERFACE foo) # app 自己不用,只有下游用
同理,用 target_compile_options / target_compile_definitions / target_compile_features 取代全局的 add_definitions / CMAKE_CXX_FLAGS。
链接预编译的第三方库(.a / .so)
先创建可执行目标并设置头文件路径,再从下面两种方式中选择一种链接库。
公共配置:
cmake_minimum_required(VERSION 3.20)
project(test_xlog)
add_executable(test_xlog test_log.cpp)
include_directories("../xlog") # 头文件路径
方式一:find_library 搜索后链接
find_library(FDK fdk-aac PATHS ${CMAKE_CURRENT_LIST_DIR}/lib)
target_link_libraries(test_xlog PRIVATE ${FDK})
方式二:使用导入目标
IMPORTED_LOCATION 指定库在磁盘上的位置;包含头文件配置和动态库导入的完整示例见导入外部库:IMPORTED。
add_library(fdkaac STATIC IMPORTED)
set_target_properties(fdkaac PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_LIST_DIR}/lib/libfdk-aac.a
)
target_link_libraries(test_xlog PRIVATE fdkaac)

头文件路径未配置正确时的错误示例:找不到 xlog.h。
优先用
target_link_libraries而非全局的link_directories()/link_libraries()。
set_target_properties
设置目标(target)属性的命令,目标可以是可执行文件、库、自定义目标,也可以是 IMPORTED 目标。它的作用是修改一个已创建目标的各项属性,从而控制编译、链接、输出路径、使用要求等行为
set_target_properties(<目标1> <目标2> ...
PROPERTIES
<属性1> <值1>
<属性2> <值2>
)
- 必须在目标创建之后(
add_executable/add_library之后)才能设置。
常用属性:
IMPORTED_LOCATION(导入目标在磁盘的位置)、VERSION / SOVERSION(.so 版本与 soname)、OUTPUT_NAME 等。
find_package 与导入目标
find_package(Threads REQUIRED)
target_link_libraries(app PRIVATE Threads::Threads) # 导入目标自带 usage requirements
生成器表达式基础
在生成阶段(而非配置阶段)求值,用于按配置/构建期或安装期切换:
target_compile_options(app PRIVATE $<$<CONFIG:Debug>:-O0 -g>)
target_include_directories(lib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
install
install(TARGETS app
RUNTIME DESTINATION bin # 可执行文件
LIBRARY DESTINATION lib # .so
ARCHIVE DESTINATION lib) # .a
配合 CMAKE_INSTALL_PREFIX(交叉编译常指向 sysroot / rootfs 打包目录)。
交叉编译与 Toolchain
CMake toolchain 文件
用 -DCMAKE_TOOLCHAIN_FILE 指定,是让 CMake「真正做交叉编译」的关键:
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=arm.cmake
# arm.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER arm-mol-linux-uclibcgnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-mol-linux-uclibcgnueabihf-g++)
set(CMAKE_SYSROOT /path/to/arm-mol-linux-uclibcgnueabihf/sysroot)
set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
# 让 find_* 只在 sysroot 里找库/头,程序仍用主机的
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
Makefile 交叉编译要点
- 用
CROSS_COMPILE ?= arm-mol-linux-uclibcgnueabihf-+CC=$(CROSS_COMPILE)gcc,?=便于命令行覆盖切换工具链。 - 带
gnueabihf后缀的工具链默认硬浮点 ABI,通常无需再显式加-mfloat-abi=hard(与工具链默认不符反而会报错)。soft/hard-float ABI 不匹配会在链接或运行期报错。 - 前缀式交叉 gcc 通常已内置 sysroot,无需显式
--sysroot;确需覆盖时再加--sysroot=<path>。
pkg-config 交叉编译
设 PKG_CONFIG_SYSROOT_DIR + PKG_CONFIG_LIBDIR/PKG_CONFIG_PATH 指向 sysroot,否则 pkg-config 会吐出主机的 -I/-L。
产物校验
file a.out # 确认目标架构
arm-...-readelf -h a.out # 架构、ABI
arm-...-readelf -d a.out # NEEDED 动态库依赖、RPATH
工具链目录结构
MIPS 工具链(mips-gcc720-uclibc229)
mips-linux-uclibc/
├── include/c++/ # C++ 标准库头文件
├── libc/ # 默认 sysroot 根目录 ⭐
│ ├── usr/include/ # 系统头文件
│ ├── usr/lib/ # 系统库文件
│ ├── glibc/ # glibc 变体
│ ├── soft-float/ # 软浮点变体
│ └── mfp32/ # mfp32 浮点变体
默认 sysroot:mips-linux-uclibc/libc/
ARM 工具链(molchipv500-armgcc-uclibc)
arm-mol-linux-uclibcgnueabihf/
├── bin/
├── include/ # 工具链头文件
├── lib/ # 工具链库文件
├── debug-root/
└── sysroot/ # 默认 sysroot 根目录 ⭐
├── lib/ # 系统库文件
├── sbin/
└── usr/
├── include/ # 系统头文件
└── lib/ # 系统库文件
默认 sysroot:arm-mol-linux-uclibcgnueabihf/sysroot/
- MIPS 工具链使用 multilib 架构,支持多种 C 库变体,sysroot 在
libc/子目录。 - ARM 工具链使用标准 sysroot 架构,所有系统文件在
sysroot/子目录。
待确认:MIPS multilib 各浮点/ C 库变体(
soft-float/、mfp32/、glibc/)对应的具体选择标志(如-mfloat-abi/-march/-mfp32组合)与该工具链的实际 multilib 映射有关,需按本人工具链gcc -print-multi-lib输出确认后再补。