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 里调用 tarunzip,跨平台时还得判断系统类型。现在 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() 命令的核心功能模块,方便你快速回顾:

file() 命令 读写文件 READ / WRITE / APPEND 网络操作 DOWNLOAD / UPLOAD 压缩解压 ARCHIVE_EXTRACT/CREATE 路径操作 GET_FILENAME_COMPONENT REAL_PATH / JOIN_PATH 文件查找 GLOB / GLOB_RECURSE FIND_PATH 核心原则:用 file() 替代外部脚本,提升跨平台兼容性 注意:GLOB 需配合 CONFIGURE_DEPENDS 使用

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 入手,慢慢扩展到下载和压缩。别一口气想学完所有功能——我在项目中也是边用边查,用多了自然就记住了。


公众号:蓝海资料掘金营,微信deep3321