8. 文件操作:file()命令的读写、下载、上传、压缩、解压、路径操作
说到 CMake 里的文件操作,我刚开始接触时也觉得——不就是读写文件嘛,有啥好讲的?
但后来在项目中吃过亏,才发现 file() 命令远比想象中强大。它不仅能读写文件,还能下载、上传、压缩、解压,甚至做路径操作。说白了,它就是 CMake 自带的瑞士军刀。
核心要点:file() 命令是 CMake 中处理文件的统一入口,掌握它,能省去大量外部脚本调用。
8.1 读写文件:最基础的操作
先看读写。我个人习惯用 file(READ) 和 file(WRITE) 来处理配置文件或生成代码。
# 读取文件内容到变量
file(READ "config.json" CONFIG_CONTENT)
# 写入字符串到文件(覆盖)
file(WRITE "output.txt" "Hello, CMake!\n")
# 追加内容
file(APPEND "output.txt" "这是追加的内容\n")
嗯,这里要注意:WRITE 会直接覆盖原文件,如果你只是想追加,用 APPEND。我曾经因为没注意这个,把项目里的配置文件覆盖了,结果排查了半天……
8.2 下载与上传:网络操作也包了
你想想看,以前我们要下载文件,是不是得写 Python 脚本或者用 curl?CMake 直接内置了 file(DOWNLOAD) 和 file(UPLOAD)。
# 下载文件
file(DOWNLOAD
"https://example.com/sdk.zip"
"${CMAKE_SOURCE_DIR}/third_party/sdk.zip"
STATUS DOWNLOAD_STATUS
SHOW_PROGRESS
)
# 上传文件
file(UPLOAD
"build/report.log"
"https://upload.example.com/logs"
STATUS UPLOAD_STATUS
)
小技巧:加上 SHOW_PROGRESS 参数,可以在终端看到下载进度条,对大型文件特别有用。
我在做 CI/CD 流水线时,经常用 file(DOWNLOAD) 来拉取预编译的第三方库。省去了手动下载的麻烦,也避免了版本不一致的问题。
8.3 压缩与解压:告别外部工具
以前处理压缩包,我得在 CMake 里调用 tar 或 unzip,跨平台时还得判断系统类型。现在 file(ARCHIVE) 直接搞定。
# 解压文件
file(ARCHIVE_EXTRACT
INPUT "third_party/sdk.zip"
DESTINATION "third_party/sdk/"
)
# 创建压缩包
file(ARCHIVE_CREATE
OUTPUT "dist/release.tar.gz"
PATHS "bin/" "lib/" "config/"
FORMAT gztar
)
| 格式 | 说明 | 适用场景 |
|---|---|---|
| zip | 标准 ZIP 格式 | 跨平台分发 |
| gztar | tar.gz 格式 | Linux 环境 |
| 7zip | 7z 格式 | 高压缩比需求 |
注意:CMake 的压缩功能依赖系统工具。在 Windows 上,可能需要安装 7-Zip 或确保 tar 可用。我曾在 Windows CI 上踩过这个坑——解压失败,最后发现是环境变量没配好。
8.4 路径操作:别再用字符串拼接了
路径操作是我用得最多的功能之一。说白了,就是帮我们处理文件路径的拼接、拆分、规范化。
# 获取文件名
file(GET_FILENAME_COMPONENT(FILE_NAME "src/main.cpp" NAME)
# FILE_NAME = "main.cpp"
# 获取目录路径
file(GET_FILENAME_COMPONENT(DIR_PATH "src/main.cpp" DIRECTORY)
# DIR_PATH = "src"
# 获取绝对路径
file(REAL_PATH "config.json" ABS_CONFIG_PATH
BASE_DIRECTORY "${CMAKE_SOURCE_DIR}"
)
# 路径拼接
file(JOIN_PATH "${CMAKE_SOURCE_DIR}" "build" "output" OUTPUT_DIR)
为什么我强调别用字符串拼接?因为不同系统的路径分隔符不一样——Windows 用反斜杠,Linux 用正斜杠。用 file() 的路径操作,CMake 会自动处理这些差异。
8.5 文件遍历与查找
有时候我们需要批量处理文件,比如收集所有源文件、查找配置文件。
# 递归查找所有 .cpp 文件
file(GLOB_RECURSE SOURCES
"src/*.cpp"
"src/*.h"
)
# 查找特定文件
file(FIND_PATH OPENCV_INCLUDE_DIR
"opencv2/opencv.hpp"
PATHS /usr/include /usr/local/include
)
# 获取目录下所有文件(非递归)
file(GLOB HEADERS "include/*.h")
避坑指南:我曾经用 file(GLOB) 来收集源文件,结果新加了文件后忘记重新运行 CMake,导致编译漏了文件。后来我改用显式列出源文件,或者用 file(GLOB) 后加上 CONFIGURE_DEPENDS 参数,让 CMake 自动检测文件变化。
8.6 知识体系总览
下面这张图总结了 file() 命令的核心功能模块,方便你快速回顾:
8.7 实战:一个完整的文件处理流程
最后,我分享一个实际项目中的例子。这个流程包含了下载、解压、路径处理、文件查找的完整链路:
# 1. 下载第三方库
file(DOWNLOAD
"https://github.com/user/lib/archive/v1.0.zip"
"${CMAKE_BINARY_DIR}/downloads/lib.zip"
STATUS DL_STATUS
)
# 2. 解压到指定目录
file(ARCHIVE_EXTRACT
INPUT "${CMAKE_BINARY_DIR}/downloads/lib.zip"
DESTINATION "${CMAKE_BINARY_DIR}/third_party/"
)
# 3. 获取解压后的目录名(自动处理版本号)
file(GLOB LIB_DIRS "${CMAKE_BINARY_DIR}/third_party/lib-*")
list(GET LIB_DIRS 0 LIB_DIR)
# 4. 查找头文件
file(FIND_PATH LIB_INCLUDE_DIR
"lib/core.h"
PATHS "${LIB_DIR}"
)
# 5. 收集源文件
file(GLOB_RECURSE LIB_SOURCES
"${LIB_DIR}/src/*.cpp"
)
message(STATUS "第三方库路径: ${LIB_DIR}")
message(STATUS "头文件路径: ${LIB_INCLUDE_DIR}")
这个流程我用了很多次,从下载到集成,全部在 CMake 里完成,不需要写任何 shell 脚本。你想想看,这省了多少跨平台适配的功夫?
个人建议:刚开始用 file() 命令时,可以先从 READ/WRITE 入手,慢慢扩展到下载和压缩。别一口气想学完所有功能——我在项目中也是边用边查,用多了自然就记住了。