CMake进阶: 路径处理指令join_paths和cmake_path
目录
1.join_paths
1.1.简介
1.2.函数参数解析
1.3.核心逻辑分步拆解
1.4.常用示例
1.5.注意事项
2.cmake_path
2.1.简介
2.2.常用子命令与实战示例
2.3.与旧方法的对比(优势)
2.4.注意事项
2.5.常见使用场景总结
相关链接
1.join_paths
1.1.简介
JoinPaths.cmake:
function(join_paths joined_path first_path_segment)set(temp_path "${first_path_segment}")foreach(current_segment IN LISTS ARGN)if(NOT ("${current_segment}" STREQUAL ""))if(IS_ABSOLUTE "${current_segment}")set(temp_path "${current_segment}")else()set(temp_path "${temp_path}/${current_segment}")endif()endif()endforeach()set(${joined_path} "${temp_path}" PARENT_SCOPE)
endfunction()
join_paths
是一个自定义路径拼接工具,用于将多个路径片段(如 a
、b/c
、d
)拼接成一个完整的路径,同时处理绝对路径覆盖和空片段过滤逻辑,确保跨平台路径格式的兼容性(CMake 会自动处理 Windows 的 \
和 Unix 的 /
)。
输入多个路径片段(如 first_path_segment
+ 可变参数 ARGN
),输出拼接后的完整路径,核心规则:
- 从第一个路径片段开始初始化拼接结果;
- 后续片段若为绝对路径,则直接覆盖之前的拼接结果(绝对路径优先级最高);
- 后续片段若为相对路径,则追加到当前拼接结果后(用
/
连接); - 自动过滤空片段(避免生成
a//b
这类无效路径)。
1.2.函数参数解析
参数名 | 类型 | 作用 |
---|---|---|
joined_path | 输出参数 | 存储最终拼接结果的变量名(需通过 PARENT_SCOPE 传递到函数外部)。 |
first_path_segment | 输入参数 | 第一个路径片段(拼接的起始基础,不能为空,否则后续相对路径会出错)。 |
ARGN | 可变输入参数 | 后续的路径片段(可多个,如 b 、c/d 、/e 等)。 |
1.3.核心逻辑分步拆解
CMake基础:宏(macro)和函数(function)
函数内部执行流程可分为 4 步,以下结合代码逐行解释:
1.初始化拼接结果
set(temp_path "${first_path_segment}")
- 用第一个路径片段
first_path_segment
初始化临时变量temp_path
(后续拼接基于此变量)。 - 例:若
first_path_segment = "src"
, 则temp_path = "src"
。
2.遍历后续路径片段(ARGN
)
foreach(current_segment IN LISTS ARGN)
ARGN
是 CMake 内置变量,代表函数定义中未显式声明的所有后续参数(如调用join_paths(result "a" "b" "c")
时,ARGN = ["b", "c"]
)。- 循环处理每个后续片段
current_segment
。
3.过滤空片段 + 处理绝对 / 相对路径
if(NOT ("${current_segment}" STREQUAL "")) # 1. 过滤空片段if(IS_ABSOLUTE "${current_segment}") # 2. 若为绝对路径:覆盖之前结果set(temp_path "${current_segment}")else() # 3. 若为相对路径:追加到当前结果set(temp_path "${temp_path}/${current_segment}")endif()
endif()
- 过滤空片段:若片段为空(如调用时传了
""
),直接跳过,避免生成a//b
这类含多余分隔符的路径。 - 绝对路径覆盖:若当前片段是绝对路径(如
/usr/local
或C:/Program Files
),则直接将temp_path
重置为该绝对路径(因为绝对路径不需要依赖之前的相对路径)。 - 相对路径追加:若为相对路径,用
/
连接到当前temp_path
后(CMake 会自动将/
转换为当前平台的路径分隔符,如 Windows 下的\
)。
4.输出拼接结果到父作用域
set(${joined_path} "${temp_path}" PARENT_SCOPE)
PARENT_SCOPE
是关键:CMake 函数内部的变量默认是局部变量,添加此关键字可将temp_path
的值赋值给外部传入的joined_path
变量(让函数外部能使用拼接结果)。
1.4.常用示例
通过不同调用场景,理解函数的实际效果(跨平台兼容,以下示例结果在 Windows 下会自动将 /
转为 \
):
示例 1:基本相对路径拼接
# 调用:拼接 "src"、"ui"、"dialogs"
join_paths(RESULT "src" "ui" "dialogs")
message(STATUS "拼接结果:${RESULT}") # 输出:src/ui/dialogs(Windows 下为 src\ui\dialogs)
示例 2:包含绝对路径(覆盖逻辑)
# 调用:第一个片段是相对路径,第三个片段是绝对路径
join_paths(RESULT "src" "temp" "/usr/local/include")
message(STATUS "拼接结果:${RESULT}") # 输出:/usr/local/include(绝对路径覆盖之前的 "src/temp")
示例 3:包含空片段(自动过滤)
# 调用:第二个片段是空字符串,会被跳过
join_paths(RESULT "build" "" "Debug" "bin")
message(STATUS "拼接结果:${RESULT}") # 输出:build/Debug/bin(空片段未影响)
示例 4:Windows 绝对路径拼接
# 调用:第一个片段是 Windows 绝对路径,后续追加相对路径
join_paths(RESULT "C:/Qt" "5.15.2" "msvc2019_64" "bin")
message(STATUS "拼接结果:${RESULT}") # 输出:C:/Qt/5.15.2/msvc2019_64/bin(Windows 下为 C:\Qt\5.15.2\msvc2019_64\bin)
1.5.注意事项
1.第一个片段不能为空:若 first_path_segment
是空字符串,后续相对路径会从根目录开始(如 join_paths(RESULT "" "a" "b")
会生成 /a/b
,Windows 下为 \a\b
),可能不符合预期,建议第一个片段至少为非空路径(如 .
代表当前目录)。
2.跨平台兼容性:函数中用 /
连接路径,但 CMake 会自动根据当前平台转换为对应的分隔符(Windows 用 \
,Unix 用 /
),无需手动处理。
3.输出参数需传变量名:
调用时第一个参数必须是变量名(如 RESULT
),不能直接传值(如 join_paths("src/ui" "src" "ui")
是错误的,会导致结果无法输出)。
4.CMake 版本兼容:
函数依赖 IS_ABSOLUTE
(CMake 3.0+ 支持)和 foreach(IN LISTS)
(CMake 3.0+ 支持),适用于绝大多数现代 CMake 环境(3.0+)。
2.cmake_path
2.1.简介
在 CMake 3.20+ 中,cmake_path
是一个专门用于路径处理的现代化命令,核心作用是统一解决路径拼接、解析、转换、判断等场景,替代了早期 CMake 中需要自定义函数(如 join_paths
)或依赖 file()
命令的繁琐操作。它原生支持跨平台路径格式(自动处理 Windows 的 \
和 Unix 的 /
),且语法清晰、功能覆盖全面。
相比早期路径处理方式(如自定义 join_paths
、file(TO_CMAKE_PATH)
),cmake_path
的优势的在于:
- 功能模块化:将路径操作拆分为
JOIN
(拼接)、GET_FILENAME_COMPONENT
(解析)、ABSOLUTE_PATH
(绝对化)等子命令,逻辑清晰; - 跨平台兼容:自动适配不同系统的路径分隔符,无需手动替换
\
或/
; - 内置容错:自动处理路径中的
.
(当前目录)、..
(上级目录),避免无效路径; - 支持变量直接操作:可直接读取 / 修改路径变量,无需中间临时变量。
2.2.常用子命令与实战示例
cmake_path
的语法格式为:
cmake_path(<子命令> [参数1] [参数2] ... OUTPUT <输出变量>)
(部分子命令无需 OUTPUT
,直接操作变量)
以下是项目中最常用的 6 类子命令:
1.路径拼接:JOIN
将多个路径片段拼接为完整路径,自动处理分隔符和绝对路径覆盖(类似自定义 join_paths
,但更智能)。
语法:cmake_path(JOIN <片段1> <片段2> ... [OUTPUT <结果变量>])
示例:
# 1. 基本拼接:相对路径片段
cmake_path(JOIN "src" "ui" "dialogs" OUTPUT UI_DIR)
message(STATUS "UI 目录:${UI_DIR}") # 输出:src/ui/dialogs(Windows 下为 src\ui\dialogs)# 2. 包含绝对路径:后续绝对路径会覆盖之前的拼接结果
cmake_path(JOIN "build" "temp" "/usr/local/include" OUTPUT INC_DIR)
message(STATUS "头文件目录:${INC_DIR}") # 输出:/usr/local/include(绝对路径覆盖)# 3. 包含 . 和 ..:自动简化路径
cmake_path(JOIN "src" ".." "bin" OUTPUT BIN_DIR)
message(STATUS "二进制目录:${BIN_DIR}") # 输出:bin(src/.. 简化为当前目录)
2.路径解析:GET_FILENAME_COMPONENT
从完整路径中提取指定部分(如文件名、目录名、扩展名),替代早期的 file(GET_FILENAME_COMPONENT)
。
语法:cmake_path(GET_FILENAME_COMPONENT <路径变量> <提取类型> OUTPUT <结果变量>)
常见 提取类型:
DIRECTORY
:提取路径中的目录部分;NAME
:提取文件名(含扩展名);NAME_WE
:提取文件名(不含扩展名);EXTENSION
:提取文件扩展名;STEM
:同NAME_WE
,提取文件名(不含扩展名)。
示例:
# 定义一个完整路径
set(FILE_PATH "/home/user/project/src/main.cpp")# 1. 提取目录部分
cmake_path(GET_FILENAME_COMPONENT FILE_DIR "${FILE_PATH}" DIRECTORY OUTPUT DIR_RESULT)
message(STATUS "文件目录:${DIR_RESULT}") # 输出:/home/user/project/src# 2. 提取完整文件名(含扩展名)
cmake_path(GET_FILENAME_COMPONENT FILE_NAME "${FILE_PATH}" NAME OUTPUT NAME_RESULT)
message(STATUS "完整文件名:${NAME_RESULT}") # 输出:main.cpp# 3. 提取文件名(不含扩展名)
cmake_path(GET_FILENAME_COMPONENT FILE_NAME_WE "${FILE_PATH}" NAME_WE OUTPUT NAME_WE_RESULT)
message(STATUS "文件名(无扩展名):${NAME_WE_RESULT}") # 输出:main# 4. 提取扩展名
cmake_path(GET_FILENAME_COMPONENT FILE_EXT "${FILE_PATH}" EXTENSION OUTPUT EXT_RESULT)
message(STATUS "文件扩展名:${EXT_RESULT}") # 输出:cpp
3.路径绝对化:ABSOLUTE_PATH
将相对路径转换为绝对路径,基于指定的 “基准目录”(默认基准目录为当前构建目录 CMAKE_CURRENT_BINARY_DIR
)。
语法:cmake_path(ABSOLUTE_PATH <路径变量> [BASE_DIRECTORY <基准目录>] OUTPUT <结果变量>)
示例:
# 相对路径
set(RELATIVE_PATH "src/ui")# 1. 基于当前构建目录(默认)转换为绝对路径
cmake_path(ABSOLUTE_PATH "${RELATIVE_PATH}" OUTPUT ABS_PATH1)
message(STATUS "绝对路径1:${ABS_PATH1}") # 输出:/home/user/project/build/src/ui(Unix)# 2. 基于源码目录(CMAKE_CURRENT_SOURCE_DIR)转换为绝对路径
cmake_path(ABSOLUTE_PATH "${RELATIVE_PATH}" BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT ABS_PATH2)
message(STATUS "绝对路径2:${ABS_PATH2}") # 输出:/home/user/project/src/ui(Unix)
4.路径规范化:NORMAL_PATH
简化路径中的 .
(当前目录)、..
(上级目录)和重复的分隔符,生成规范的路径格式。
语法:cmake_path(NORMAL_PATH <路径变量> [OUTPUT <结果变量>])
示例:
# 含冗余部分的路径
set(MESSY_PATH "/home/user/../project/src/./ui//dialogs")# 规范化路径
cmake_path(NORMAL_PATH "${MESSY_PATH}" OUTPUT NORMALIZED_PATH)
message(STATUS "规范化路径:${NORMALIZED_PATH}") # 输出:/home/project/src/ui/dialogs
5.路径判断:EXISTS
/IS_ABSOLUTE
/IS_DIRECTORY
/IS_FILE
判断路径是否存在、是否为绝对路径、是否为目录 / 文件,结果存储为布尔变量(TRUE
/FALSE
)。
语法:cmake_path(<判断类型> <路径变量> OUTPUT <结果变量>)
常见 判断类型:
EXISTS
:路径是否存在(文件或目录);IS_ABSOLUTE
:路径是否为绝对路径;IS_DIRECTORY
:路径是否为目录(且存在);IS_FILE
:路径是否为文件(且存在)。
示例:
set(TEST_PATH "/home/user/project/src/main.cpp")
set(REL_PATH "src/ui")# 1. 判断是否为绝对路径
cmake_path(IS_ABSOLUTE "${TEST_PATH}" OUTPUT IS_ABS)
message(STATUS "${TEST_PATH} 是否为绝对路径:${IS_ABS}") # 输出:TRUE# 2. 判断路径是否存在
cmake_path(EXISTS "${TEST_PATH}" OUTPUT PATH_EXISTS)
message(STATUS "${TEST_PATH} 是否存在:${PATH_EXISTS}") # 输出:TRUE(若文件存在)# 3. 判断是否为目录
cmake_path(IS_DIRECTORY "${REL_PATH}" OUTPUT IS_DIR)
message(STATUS "${REL_PATH} 是否为目录:${IS_DIR}") # 输出:TRUE(若目录存在)
6.路径追加:APPEND
在已有路径变量后追加片段(类似 JOIN
,但直接修改原变量,无需输出新变量)。
语法:cmake_path(APPEND <路径变量> <片段1> <片段2> ...)
示例:
# 初始路径
set(BIN_DIR "build/bin")# 追加片段(Debug 目录)
cmake_path(APPEND BIN_DIR "Debug")
message(STATUS "追加后路径:${BIN_DIR}") # 输出:build/bin/Debug(Windows 下为 build\bin\Debug)# 继续追加片段(exe 目录)
cmake_path(APPEND BIN_DIR "exe")
message(STATUS "再次追加后路径:${BIN_DIR}") # 输出:build/bin/Debug/exe
2.3.与旧方法的对比(优势)
以 “路径拼接” 为例,对比 cmake_path(JOIN)
与早期自定义 join_paths
函数:
特性 | cmake_path(JOIN) (CMake 3.20+) | 自定义 join_paths 函数 |
---|---|---|
跨平台分隔符 | 自动适配(\ // ) | 需依赖 CMake 自动转换,或手动处理 |
路径简化(.``.. ) | 自动简化(如 src/../bin → bin ) | 需额外添加简化逻辑,否则生成无效路径 |
语法简洁性 | 一行命令,直接输出变量 | 需定义函数,调用时需传递输出变量名 |
错误处理 | 内置容错(如空片段自动忽略) | 需手动添加空片段过滤逻辑 |
2.4.注意事项
1.CMake 版本要求:cmake_path
是 CMake 3.20 及以上版本引入的,若项目需兼容旧版本(如 3.19 及以下),需改用 file()
命令或自定义函数(如 join_paths
);
2.路径变量引用:所有路径参数需用 ${}
引用变量(如 ${FILE_PATH}
),直接传字符串(如 "src/ui"
)也支持;
3.输出变量覆盖:OUTPUT
指定的变量若已存在,会被覆盖,需注意变量名冲突;
4.Windows 特殊路径:支持 Windows 下的盘符路径(如 C:/Program Files
),自动保留盘符信息。
2.5.常见使用场景总结
场景需求 | 推荐子命令 | 示例代码片段 |
---|---|---|
拼接源码 / 构建目录路径 | JOIN | cmake_path(JOIN "${CMAKE_SOURCE_DIR}" "src" OUTPUT SRC_DIR) |
提取库文件的目录 | GET_FILENAME_COMPONENT | cmake_path(GET_FILENAME_COMPONENT "${LIB_FILE}" DIRECTORY OUTPUT LIB_DIR) |
检查配置文件是否存在 | EXISTS | cmake_path(EXISTS "${CONFIG_FILE}" OUTPUT CONFIG_EXISTS) |
将相对路径转为绝对路径 | ABSOLUTE_PATH | cmake_path(ABSOLUTE_PATH "${REL_PATH}" BASE_DIRECTORY "${CMAKE_SOURCE_DIR}" OUTPUT ABS_PATH) |
简化用户输入的冗余路径 | NORMAL_PATH | cmake_path(NORMAL_PATH "${USER_PATH}" OUTPUT NORMAL_PATH) |
cmake_path
是 CMake 为路径处理设计的官方标准化工具,覆盖了路径拼接、解析、转换、判断的全场景,解决了早期自定义函数的繁琐和兼容性问题。在 CMake 3.20+ 的项目中,推荐优先使用 cmake_path
替代旧的路径处理方式,提升代码的可读性和跨平台稳定性。
相关链接
- CMake 官网 https://cmake.org/
- CMake 官方文档:https://cmake.org/cmake/help/latest/guide/tutorial/index.html
- CMake 源码:https://github.com/Kitware/CMake
- CMake 源码:https://gitlab.kitware.com/cmake/
- 中文版基础介绍: https://www.hahack.com/codes/cmake/
- wiki: https://gitlab.kitware.com/cmake/community/-/wikis/Home
- Modern CMake 简体中文版: https://modern-cmake-cn.github.io/Modern-CMake-zh_CN/