[cmake]cmake命令之file

file是文件操作命令,用于文件或路径的操作,结果也会在文件系统上进行存储。因此,与cmake_path命令只是语义概念上对路径的处理不同,file会与文件系统进行实际的交互。

一、命令格式

file命令包含文件系统路径转换传输归档七个子命令,格式如下:

子命令:读
  file(READ <filename> <out-var> [...])
  file(STRINGS <filename> <out-var> [...])
  file(<HASH> <filename> <out-var>)
  file(TIMESTAMP <filename> <out-var> [...])
  file(GET_RUNTIME_DEPENDENCIES [...])

子命令:写
  file({WRITE | APPEND} <filename> <content>...)
  file({TOUCH | TOUCH_NOCREATE} [<file>...])
  file(GENERATE OUTPUT <output-file> [...])
  file(CONFIGURE OUTPUT <output-file> CONTENT <content> [...])

子命令:文件系统
  file({GLOB | GLOB_RECURSE} <out-var> [...] [<globbing-expr>...])
  file(MAKE_DIRECTORY [<dir>...])
  file({REMOVE | REMOVE_RECURSE } [<files>...])
  file(RENAME <oldname> <newname> [...])
  file(COPY_FILE <oldname> <newname> [...])
  file({COPY | INSTALL} <file>... DESTINATION <dir> [...])
  file(SIZE <filename> <out-var>)
  file(READ_SYMLINK <linkname> <out-var>)
  file(CREATE_LINK <original> <linkname> [...])
  file(CHMOD <files>... <directories>... PERMISSIONS <permissions>... [...])
  file(CHMOD_RECURSE <files>... <directories>... PERMISSIONS <permissions>... [...])

子命令:路径转换
  file(REAL_PATH <path> <out-var> [BASE_DIRECTORY <dir>] [EXPAND_TILDE])
  file(RELATIVE_PATH <out-var> <directory> <file>)
  file({TO_CMAKE_PATH | TO_NATIVE_PATH} <path> <out-var>)

子命令:传输
  file(DOWNLOAD <url> [<file>] [...])
  file(UPLOAD <file> <url> [...])

子命令:锁
  file(LOCK <path> [...])

子命令:归档
  file(ARCHIVE_CREATE OUTPUT <archive> PATHS <paths>... [...])
  file(ARCHIVE_EXTRACT INPUT <archive> [...])

二、命令使用

在开始举例说明前,命令操作的文件/目录树说明如下:

├── CMakeLists.txt
├── copy                # 该目录用于演示COPY子命令
│   └── myfile_read     # COPY子命令拷贝的文件结果
├── myfile_download     # 用于演示DOWNLOAD下载子命令
├── myfile_read         # 用于演示READ子命令
├── myfile_write        # 用于演示WRITE子命令
└── result.tar          # 用于演示归档ARCHIVE_CREATE子命令

读文件内容

file(READ myfile_read out_var)
message("Content of myfile_read is: ${out_var}")

运行结果:
Content of myfile_read is: This is a file for test

写文件:如果文件不存在,会创建文件。

set(content "Write hello to file.")
file(WRITE myfile_write ${content}) # 文件不存在会创建,直接覆盖文件写
file(READ myfile_write out_var)
message("Content of myfile_write: ${out_var}")
file(APPEND myfile_write "\nAnother line.") # 文件不存在会创建,在文件末尾追加写
file(READ myfile_write out_var)
message("Content of myfile_write: ${out_var}")

运行结果:
Content of myfile_write: Write hello to file.
Content of myfile_write: Write hello to file.
Another line.

文件系统操作

file(COPY myfile_read DESTINATION ./copy) # 将文件拷贝到./copy目录下

运行cmake .后,myfile_read会被复制一份到./copy目录下。

路径转换

file(REAL_PATH "./copy" out_var BASE_DIRECTORY "/bin") # ./copy目录基于base目录/bin的路径
message("./copy relative base /bin result: ${out_var}")

运行结果:
./copy relative base /bin result: /bin/copy

文件传输操作

file(DOWNLOAD "https://sh.rustup.rs" myfile_download) # 下载文件

运行cmake .命令后,会将文件下载到本地,并命名为myfile_download

归档

file(ARCHIVE_CREATE OUTPUT "result.tar" PATHS myfile_read myfile_write myfile_download FORMAT gnutar) # 将myfile_read myfile_write myfile_download打包成tar文件格式

运行cmake .命令后,会生成一个result.tar文件,使用tar tvf result.tar命令查看文件内容:

-rw-r--r-- 0 XXX YYY 24 5 10 07:42 myfile_read
-rw-r--r-- 0 XXX YYY 34 5 10 08:23 myfile_write
-rw-r--r-- 0 XXX YYY 21102 5 10 08:23 myfile_download

三、子命令详解
3.1 子命令:读
  • file(READ <filename> <variable> [OFFSET <offset>] [LIMIT <max-in>] [HEX])

说明:从文件<filename>中读取内容,并将结果存入到<variable>中。可以指定从文件偏移<offset>处开始读,最多读取字节数(bytes)通过<max-in>参数指定。HEX选项会将读取结果转换成十六进制表示(在读取二进制数据的时候非常有用),且十六进制的输出将按照小写输出(也就是或说输出a b c d e f)。

# CMakeLists.txt

# 从myfile_read的第5个字节偏移处开始读,读取4个字节
file(READ myfile_read content OFFSET 8 LIMIT 6)
message("read from myfile_read at offset 8 and read 6 bytes: ${content}")

# 读取结果转换成十六进制表示,十六进制表示中的ABCDEF将按照小写输出
file(READ myfile_read content_hex OFFSET 8 LIMIT 6 HEX)
message("read from myfile_read at offset 8 and read 6 bytes (HEX output): ${content_hex}")

myfile_read文件的原始内容为:THIS IS A FILE FOR READ TEST.

运行结果:
read from myfile_read at offset 8 and read 6 bytes: A FILE
read from myfile_read at offset 8 and read 6 bytes (HEX output): 412046494c45

  • file(STRINGS <filename> <variable> [<options>...])

说明
  按照ASCII编码从文件中读取并存储在<variable>中。换行符会被忽略,如果是二进制数据,也会被忽略。默认情况下是以换行符对读取的内容进行分割,每一行读取为输出结果序列的一个子项,不同子项以分号分割(也可以使用长度进行分割等,见下详解)。该命令的选项详解如下:

  • LENGTH_MAXIMUM <max-len>:每次读取不超过给定长度的字符串,如果某一行的字符超过指定长度,会以指定长度对该行进行分割读取。
  • LENGTH_MINIMUM <min-len>:每次读取不少于给定长度的字符串,如果某一行的字符串长度低于指定长度,则不会读取到结果中。
  • LIMIT_COUNT <max-num>:限制提取的字符串子项个数,例如默认情况下是以换行分割读取,假如<max-num>5,如果输入文件有6行,那么只会读取前5行。
  • LIMIT_INPUT <max-in>:限制从输入文件的读取的字节数。
  • LIMIT_OUTPUT <max-out>:限制存入到<variable>中的总字节数,当解析得到最后一个字符串子项长度累加后超过指定长度时候,最后一个字符串子项会丢弃。
  • NEWLINE_CONSUME:将换行符作为字符串内容的一部分,而不是在换行时停止读取。
  • NO_HEX_CONVERSIONIntel十六进制和MotorolaS-record文件会自动转换成二进制,除非指定了本选项。
  • REGEX <regex>:读取匹配指定正则表达式的字符串。
  • ENCODING <encoding-type>3.1版本中引入。按照指定的编码读取字符串,当前支持的编码包括:UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE。此外,如果未指定本选项并且文件本身有字节顺序标记Byte Order Mark,那么会按照文件的字节顺序标记进行解析。

# CMakeLists.txt
file(STRINGS myfile_read content_s) # 默认会以换行将文件的内容分割成多个子项,按照分号序列的方式存储
message("read myfile_read as STRINGS: ${content_s}")

# 限制每次提取的字符串长度不超过10
file(STRINGS myfile_read content_s LENGTH_MAXIMUM 10)
message("read myfile_read as STRINGS - LMax: ${content_s}")

# 限制每次提取的字符串长度至少为10,小于10会舍弃
file(STRINGS myfile_read content_s LENGTH_MINIMUM 10)
message("read myfile_read as STRINGS - LMin: ${content_s}")

# 限制提取的字符串子项数量最多为2个
file(STRINGS myfile_read content_s LIMIT_COUNT 2)
message("read myfile_read as STRINGS - LCount: ${content_s}")

# 限制只提取文件的前35个字符
file(STRINGS myfile_read content_s LIMIT_INPUT 35)
message("read myfile_read as STRINGS - LInput: ${content_s}")

# 限制输出的总长度为45
file(STRINGS myfile_read content_s LIMIT_OUTPUT 45)
message("read myfile_read as STRINGS - LOutput: ${content_s}")

# 不以换行进行分割,输出结果包含换行
file(STRINGS myfile_read content_s NEWLINE_CONSUME)
message("read myfile_read as STRINGS - Contain CR: ${content_s}")

myfile_read文件的原始内容有三行:
THIS IS A FILE FOR READ TEST.
new line1
new line2

运行结果为:
read myfile_read as STRINGS: THIS IS A FILE FOR READ TEST.;new line1;new line2
read myfile_read as STRINGS - LMax: THIS IS A ;FILE FOR R;EAD TEST.;new line1;new line2
read myfile_read as STRINGS - LMin: THIS IS A FILE FOR READ TEST.
read myfile_read as STRINGS - LCount: THIS IS A FILE FOR READ TEST.;new line1
read myfile_read as STRINGS - LInput: THIS IS A FILE FOR READ TEST.;new l
read myfile_read as STRINGS - LOutput: THIS IS A FILE FOR READ TEST.;new line1
read myfile_read as STRINGS - Contain CR: THIS IS A FILE FOR READ TEST.
new line1
new line2

  • file(<HASH> <filename> <variable>)

说明:计算文件的hash结果,支持的hash算法可以参考string(<HASH>)

# CMakeLists.txt
file(MD5 myfile_read content_hash)
message("Hash of myfile_read is: ${content_hash}")

运行结果为:
Hash of myfile_read is: 6a4ff0158d402b69360ee52ba217faf3

  • file(TIMESTAMP <filename> <variable> [<format>] [UTC])

说明:将文件的修改时间转换成字符串表示,并存储在结果<variable>中。如果无法获得时间戳,字符串将置为""<format>UTC选项的详细使用方法见string(TIMESTAP)。未使用UTC选项时,返回的时本地时间,默认的格式为年-月-日T时:分:秒;当指定UTC选项时,默认格式为年-月-日T时:分:秒Z

# CMakeLists.txt
file(TIMESTAMP myfile_read time_string)
message("myfile_read modify time is(local time): ${time_string}")

file(TIMESTAMP myfile_read time_string UTC)
message("myfile_read modify time is(UTC): ${time_string}")

file(TIMESTAMP myfile_read time_string "%B.%d %Y-%H:%M:%S-%A")
message("myfile_read modify time is(format: 月.日 年-时:分:秒-周): ${time_string}")

运行结果为:
***myfile_read modify time is(local time): 2022-05-11T08:14:34
myfile_read modify time is(UTC): 2022-05-11T00:14:34Z
myfile_read modify time is(format: 月.日 年-时:分:秒-周): May.11 2022-08:14:34-Wednesday*******

  • file(GET_RUNTIME_DEPENDENCIES[RESOLVED_DEPENDENCIES_VAR <deps_var>] [UNRESOLVED_DEPENDENCIES_VAR <unresolved_deps_var>] [CONFLICTING_DEPENDENCIES_PREFIX <conflicting_deps_prefix>] [EXECUTABLES [<executable_files>...]] [LIBRARIES [<library_files>...]] [MODULES [<module_files>...]] [DIRECTORIES [<directories>...]] [BUNDLE_EXECUTABLE <bundle_executable_file>] [PRE_INCLUDE_REGEXES [<regexes>...]] [PRE_EXCLUDE_REGEXES [<regexes>...]] [POST_INCLUDE_REGEXES [<regexes>...]] [POST_EXCLUDE_REGEXES [<regexes>...]] [POST_INCLUDE_FILES [<files>...]] [POST_EXCLUDE_FILES [<files>...]])

说明:这个命令比较复杂,且根install命令有关,后续等install命令完成后返回来补充。

# CMakeLists.txt

运行结果为:


3.2 子命令:写
  • file(WRITE|APPEND <filename> <content>...)

说明:将内容写入到指定的文件,如果文件不存在则创建文件;如果文件已经存在,WRITE选项会覆盖原文件,而APPEND则将内容添加在原文件的末尾。此外,文件filename路径中不存在的目录也会被创建出来。

# CMakeLists.txt
file(WRITE myfile_wirte "write one line\n")
file(READ myfile_wirte content)
message("file(user wirte): ${content}")

file(WRITE myfile_wirte "write another line\n")
file(READ myfile_wirte content)
message("file(user wirte): ${content}")

file(APPEND myfile_wirte "append one line\n")
file(READ myfile_wirte content)
message("file(user append): ${content}")

运行结果为:
file(user wirte): write one line

file(user wirte): write another line

file(user append): write another line
append one line

  • file(TOUCH|TOUCH_NOCREATE [<files>...])

说明3.12版本引入。

  • TOUCH会创建一个内容为空的新文件,如果文件已经存在,那么该命令不会对文件的内容有任何影响,只会将文件的访问或者修改时间更新为该命令调用时间。
  • TOUCH_NOCREATE选项不会创建新文件,如果文件不存在则会忽略,如果文件存在,那么则更新文件的访问或者修改时间为该命令调用的时间。

该命令可以指定多个文件。

# CMakeLists.txt
file(TIMESTAMP myfile_read modify_time)
message("myfile_read modify time is: ${modify_time}")
file(TOUCH_NOCREATE myfile_read) # 如果myfile_read不存在,那么不会创建文件,如果myfile_read存在,则更新其访问/修改时间
file(TIMESTAMP myfile_read modify_time)
message("after touch(but not create) myfile_read modify time is: ${modify_time}")
file(TOUCH mynewfile) # 创建新文件
file(TIMESTAMP mynewfile modify_time)
message("create mynewfile time is: ${modify_time}")

运行结果为:
myfile_read modify time is: 2022-05-11T08:14:34
after touch(but not create) myfile_read modify time is: 2022-05-12T22:18:51
create mynewfile time is: 2022-05-12T22:18:51

  • file(GENERATE OUTPUT output-file <INPUT input-file|CONTENT content> [CONDITION expression] [TARGET target] [NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS | FILE_PERMISSIONS <permissions>...] [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])

说明:该命令跟生成表达式有关,待完成生成表达式后补充。

3.3 子命令:文件系统
  • file(GLOB <variable> [LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS] [<globbing-expressions>...])

说明
  生成能匹配表达式<globbing-expressions>的一组文件,并将结果存入到<variable>中,结果按照字典序进行排列(3.6版本及之后)。Globbing expressions与正则表达式类似,但是比正则表达式要简单很多。
  在WindowsmacOS系统中,Globbing expressions是大小写不敏感的(在匹匹配为之前会将文件名和Globbing expressions转换为小写);其他系统Globbing expressions是大小写敏感的。
  具体的选项解析如下:

  • LIST_DIRECTORIES:默认情况下,结果会将目录也列出来,如果将LIST_DIRECTORIES设置为false,那么目录会被忽略,只会列出文件存入结果中。
  • RELATIVE:指定该选项,将返回匹配文件相对于RELATIVE指定的路径。RELATIVE必须指定一个绝对路径。
  • CONFIGURE_DEPENDS:指定该选项,CMake会在构建时重新运行该命令。

# CMakeLists.txt
file(GLOB result "matches/*") # 列出目录matches下的所有文件,带全路径
message("all files in matches: ${result}")
file(GLOB result "matches/*.txt") # 匹配matches下后缀为txt的文件,带全路径
message("match *.txt result: ${result}")
file(GLOB result RELATIVE /XXX/YYY/ZZZ/matches "myfile*")
message("list file: ${result}") # 查找当前目录./下的myfile开头的文件,返回相对于./matches路径的结果

运行结果:
*all files in ./matches: /XXX/YYY/ZZZ/matches/log1.txt;/XXX/YYY/ZZZ/matches/log2.txt;/XXX/YYY/ZZZ/matches/log3.txt;/XXX/YYY/ZZZ/matches/log4.txt;/XXX/YYY/ZZZ/matches/log5.txt;/XXX/YYY/ZZZ/matches/test1.dat;//XXX/YYY/ZZZ/matches/test2.dat;/XXX/YYY/ZZZ/matches/test3.dat;/XXX/YYY/ZZZ/matches/test4.dat
match .txt result: /XXX/YYY/ZZZ/matches/log1.txt;/XXX/YYY/ZZZ/matches/log2.txt;/XXX/YYY/ZZZ/matches/log3.txt;/XXX/YYY/ZZZ/matches/log4.txt;/XXX/YYY/ZZZ/matches/log5.txt
list file: ../myfile_download;../myfile_read;../myfile_wirte;../myfile_write

  • file(GLOB_RECURSE <variable> [FOLLOW_SYMLINKS] [LIST_DIRECTORIES true|false] [RELATIVE <path>] [CONFIGURE_DEPENDS] [<globbing-expressions>...])

说明
  与GLOB区别是,该子命令会递归的遍历匹配目录的所有子目录,然后寻找匹配的文件。

  • file(MAKE_DIRECTORY [<directories>...])

说明:创建指定目录,如果指定的目录的路径中有不存在的目录,也会一并创建。

file(MAKE_DIRECTORY ./dir1/dir2/dir3/mynewdir) # dir1、dir2、dir3、mynewdir目录都会被创建

  • file(REMOVE|REMOVE_RECURSE [<files>...])

说明:移除指定的文件,REMOVE_RECURSE模式将会移除给定的文件和目录(即使目录是非空的)。如果给定的文件不存在,也不会有错误的信息提示,如果文件是以相对路径提供,那么会以当前目录作为参考。
3.15版本之前,如果是空的文件路径输入,会将空路径当成相对路径,以当前目录作为参考,删除当前目录的内容;而3.15及之后的版本,空的文件输入会被忽略,并给出一个提示信息。

# CMakeLists.txt
file(REMOVE "./dir1/dir2/dir3/mynewdir/testfile1") # 删除指定文件testfile1
file(REMOVE_RECURSE "./dir1/dir2/dir3/mynewdir/testfile2") # 删除指定文件testfile2
file(REMOVE_RECURSE "./dir1/dir2/dir3/mynewdir") # 删除指定目录mynewdir,会递归删除mynewdir目录下所有的文件和子目录,也会删除mynewdir目录本身
file(REMOVE "") # 3.15及之后的版本会忽略该语句并给出提示,假设该语句位于CMakeLists.txt文件的第23行,提示信息见下

运行结果:
CMake Warning (dev) at CMakeLists.txt:23 (file):
Ignoring empty file name in REMOVE.
This warning is for project developers. Use -Wno-dev to suppress it.

  • file(RENAME <oldname> <newname> [RESULT <result>] [NO_REPLACE])

说明:将指定的文件或者目录从旧路径<oldname>移动到新路径<newname>下,如果存在相同的名称文件或者目录会进行自动替换。

  • RESULT <result>3.21版本引入。如果命令执行成功,将<result>变量置为0,否则<result>变量存储错误信息(CMake不会产生错误)。如果未指定该选项,当执行不成功,CMake会产生一个错误。
  • NO_REPLACE3.21版本引入。默认情况下新路径下已经存在文件或目录,会自动进行替换,指定该选项则不会对相同的文件或目录进行替换,而是会产生一个错误(如果指定了RESULT选项,NO_REPLACE会存储到RESULT指定的变量中。)

# CMakeLists.txt
file(RENAME ./rename_test/old/oldfile6 ./rename_test/new/) # 会提示错误:No such file or directory
file(RENAME ./rename_test/old/oldfile6 ./rename_test/new/ RESULT info) # 不会提示错误,错误信息No such file or directory会存储到info中
message("operation result: ${info}")
file(RENAME ./rename_test/old/oldfile1 ./rename_test/new/newfile1 RESULT info) # 执行成功,info结果为0
message("operation result: ${info}")
file(RENAME ./rename_test/old/olddir1/oldfile4 ./rename_test/new/olddir1/oldfile4 NO_REPLACE RESULT info) # 有NO_REPLACE选项,因此无法进行替换操作,info中存储的是NO_REPLACE
message("operation result: ${info}")
file(RENAME ./rename_test/old/olddir1/oldfile4 ./rename_test/new/olddir1/oldfile4 RESULT info) # 执行成功,info结果为0
message("operation result: ${info}")

上述命令操作的文件树结构为:

├── CMakeLists.txt
├── rename_test
│   ├── new
│   │   └── olddir1
│   │       └── oldfile4
│   └── old
│   │   └── olddir1
│   │       └── oldfile4
│   |   ├── olddir1
│   |   ├── oldfile2
│   |   └── oldfile3

  • file(COPY_FILE <oldname> <newname> [RESULT <result>] [ONLY_IF_DIFFERENT])

说明3.21版本引入。将文件从<oldname>复制到<newname>,不支持目录的复制。如果<oldname>是一个符号链接,那么会读取<oldname>实际链接的内容,并将实际指向的内容复制到<newname>中。该命令支持的几个选项解析如下:

  • RESULT <result>:如果执行成功,<result>变量内容为0,执行出错该变量内容为具体的错误信息。如果未指定该选项且命令执行出错,那么CMake会抛出错误并停止构建。
  • ONLY_IF_DIFFERENT:如果目标文件<newname>已经存在,并且<newname>文件的内容与<oldname>内容一样,那么不做替换操作,这样就可以避免更新<newname>的时间戳。

# CMakeLists.txt
file(COPY_FILE mydir mydir_copy) # 不支持目录,如果不指定RESULT,CMake构建会提示错误:file COPY_FILE cannot copy a directory
file(COPY_FILE mydir mydir_copy RESULT result)
message("Copy dir result: ${result}") # 复制不成功,CMake构建不会提示错误,错误信息会存到result中

file(COPY_FILE myfile myfile_copy RESULT result) # myfile内容为"This is a file for testing."
file(TIMESTAMP myfile_copy ts)
message("Copy file result: ${result}, timestamp: ${ts}") # 复制成功,结果为0

execute_process(COMMAND sleep 5)
file(COPY_FILE myfile myfile_copy RESULT result) # 复制相同的内容,未指定ONLY_IF_DIFFERENT选项,仍会执行赋值操作,注意时间戳打印不一致
file(TIMESTAMP myfile_copy ts)
message("Copy file for same content result: ${result}, timestamp: ${ts}")

execute_process(COMMAND sleep 5)
file(COPY_FILE myfile myfile_copy RESULT result ONLY_IF_DIFFERENT) # 复制相同的内容,指定ONLY_IF_DIFFERENT选项,不会执行复制动作,因此时间戳没有变化
file(TIMESTAMP myfile_copy ts)
message("Copy file for same content result: ${result}, timestamp: ${ts}")

file(WRITE myfile_copy "Rewrite file content.")
execute_process(COMMAND sleep 5)
file(COPY_FILE myfile myfile_copy RESULT result ONLY_IF_DIFFERENT) # 由于目标文件内容和源文件内容不一致了,会执行复制操作
file(TIMESTAMP myfile_copy ts)
message("Copy file for different content result: ${result}, timestamp: ${ts}")

执行结果为:
CMake Error at CMakeLists.txt:4 (file): file COPY_FILE cannot copy a directory

/XXX/YYY/ZZZ/mydir

Copy dir result: cannot copy a directory
Copy file result: 0, timestamp: 2022-05-15T08:07:51
Copy file for same content result: 0, timestamp: 2022-05-15T08:07:56
Copy file for same content(use ONLY_IF_DIFFEREBT)result: 0, timestamp: 2022-05-15T08:07:56
Copy file for different content result: 0, timestamp: 2022-05-15T08:08:06
-- Configuring incomplete, errors occurred!

  • file(<COPY|INSTALL> <files>... DESTINATION <dir> [NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS] [FILE_PERMISSIONS <permissions>...] [DIRECTORY_PERMISSIONS <permissions>...] [FOLLOW_SYMLINK_CHAIN] [FILES_MATCHING] [[PATTERN <pattern> | REGEX <regex>] [EXCLUDE] [PERMISSIONS <permissions>...]] [...])

说明:如果只是简单的文件复制,可以使用前面的file(COPY_FILE)命令。相对于file(COPY_FILE),本命令是一个更复杂的命令,它可以复制文件、目录、符号链接等。输入文件的目录是相对于当前源文件路径,目的文件路径是相对于当前构建路径。

  • COPY子命令会保存输入文件的时间戳,如果目的文件存在,那么会将目的文件的时间戳修改成和输入文件一致。默认情况下(USE_SOURCE_PERMISSIONS选项)也会将输入文件的权限传递给目的文件,除非通过NO_SOURCE_PERMISSIONS选项显示禁止。
  • INSTALL子命令与COPY子命令稍微不一样的地方是:INSTALL子命令会打印状态信息,并且默认情况下不会将输入文件的权限传递给目的文件(也就是默认为NO_SOURCE_PERMISSIONS选项)。install()命令就是使用file(INSTALL)命令来产生安装脚本文件的。在3.22版本以后,还可以给CMAKE_INSTALL_MODE环境变量赋值来改变file(INSTALL)的默认复制行为。
  • FOLLOW_SYMLINK_CHAIN:如果指定了该选项,会递归的解析符号链接,直到找到最终的文件,并且每一个符号链接都会在指定的目的目录创建出来,并且都指向同个目录下创建的最终文件。这个选项在类Unix系统下特别有用,在这些系统中,库文件都是以版本号命名的符号链接形式安装,由不具体的版本指向具体的版本。
  • 对于权限,FILES_MACHINGPATTERNREGEXEXCLUDE请参考install(DIRECTORY)

目录结构为:
copy_test
├── dest
└── source
    ├── dir_for_copy
    │   └── file1
    └── libs
        ├── mylib.so
        ├── mylib.so.1 -> mylib.so
        ├── mylib.so.1.2 -> mylib.so.1
        └── mylib.so.1.2.3 -> mylib.so.1.2

# CMakeLists.txt
file(COPY copy_test/source/dir_for_copy/file1 DESTINATION copy_test/dest/) # 假定file1的权限是660,使用COPY,默认拷贝后的文件权限也是660,查看 执行结果1
file(INSTALL copy_test/source/dir_for_copy/file1 DESTINATION copy_test/dest/) # 假定file1的权限是660,使用INSTALL,默认拷贝后的文件权限是644(系统默认文件创建权限),查看 执行结果2

file(COPY copy_test/source/dir_for_copy DESTINATION copy_test/dest/) # 拷贝dir_for_copy目录

file(COPY copy_test/source/libs/mylib.so DESTINATION copy_test/dest/libs FOLLOW_SYMLINK_CHAIN) # 递归拷贝mylib.so的链接并创建,查看 执行结果3
file(COPY copy_test/source/libs/mylib.so DESTINATION copy_test/dest/libs_nosymlink) # 只会拷贝单个mylib.so文件,查看 执行结果4

  • 执行结果1:
    复制前的file1权限:
    -rw-rw---- copy_test/source/dir_for_copy/file1
    复制后的file1权限:
    -rw-rw---- copy_test/dest/file1
  • 执行结果2:
    复制前的file1权限:
    -rw-rw---- copy_test/source/dir_for_copy/file1
    复制后的file1权限:
    -rw-r--r-- copy_test/dest/file1
    并且使用INSTALL会打印处中间的执行状态信息:
    -- Up-to-date: /XXX/YYY/ZZZ/copy_test/dest//file1
  • 执行结果3:
    FOLLOW_SYMLINK_CHAIN选项,复制后的目录结构,会将整个链接递归创建出来:
    copy_test
    ├── dest
    │ ├── libs
    │ │ ├── mylib.so -> mylib.so.1
    │ │ ├── mylib.so.1 -> mylib.so.1.2
    │ │ ├── mylib.so.1.2 -> mylib.so.1.2.3
    │ │ └── mylib.so.1.2.3
    └── source
    ├── dir_for_copy
    │ └── file1
    └── libs
    ├── mylib.so -> mylib.so.1
    ├── mylib.so.1 -> mylib.so.1.2
    ├── mylib.so.1.2 -> mylib.so.1.2.3
    └── mylib.so.1.2.3
  • 执行结果4:
    没有FOLLOW_SYMLINK_CHAIN选项,只会将链接文件当成普通文件复制:
    copy_test
    ├── dest
    │ └── libs_nosymlink
    │ └── mylib.so -> mylib.so.1
    └── source
    ├── dir_for_copy
    │ └── file1
    └── libs
    ├── mylib.so -> mylib.so.1
    ├── mylib.so.1 -> mylib.so.1.2
    ├── mylib.so.1.2 -> mylib.so.1.2.3
    └── mylib.so.1.2.3
  • file(SIZE <filename> <variable>)

说明3.14版本引入,计算文件的大小,要求指向文件的路径是合法的路径,且文件本身是可读的。

# CMakeLists.txt
set(filename "myfile_read")
file(SIZE ${filename} sz)
message("file ${filename} size is : ${sz} bytes")

执行结果为:
file myfile_read size is : 50 bytes

  • file(READ_SYMLINK <linkname> <variable>)

说明3.14版本引入。读取链接文件并将该链接文件指向的文件路径保存到变量<variable>中,如果链接文件不存在,CMake会抛出一个错误。需要注意的是,该命令返回的时候链接文件原始指向的文件路径,并且是相对路径。

# CMakeLists.txt
set(linkname "/XXX/YYY/ZZZ/copy_test/source/libs/mylib.so")
file(READ_SYMLINK ${linkname} result) # 复制的是mylib.so执行的mylib.so.1,且不会递归解析
message("Get link result: ${result}")

# 可以通过如下方法来获取绝对路径
if (NOT IS_ABSOLUTE "${result}")
    get_filename_component(dir "${linkname}" DIRECTORY)
    set(result "${dir}/${result}")
endif()
message("Get link result after add absolute path: ${result}")

执行结果为:
Get link result: mylib.so.1
Get link result after add absolute path: /XXX/YYY/ZZZ/copy_test/source/libs/mylib.so.1

  • file(CREATE_LINK <original> <linkname> [RESULT <result>] [COPY_ON_ERROR] [SYMBOLIC])

说明3.14版本引入,创建一个指向<original>的链接文件<linkname>,如果<linkname>已经存在,则会被覆盖。默认创建的是硬链接(硬链接要求原始文件存在且不是目录)。

  • RESULT <result>:如果执行成功,<result>变量内容为0,执行出错该变量内容为具体的错误信息。如果未指定该选项且命令执行出错,那么CMake会抛出错误并停止构建。
  • SYMBOLIC:创建软连接。
  • COPY_ON_ERROR:如果创建链接文件失败,那么复制文件。主要用于<original><linkname>在不同的驱动器或者挂在点的场景,此时创建硬链接会失败,用此选项可以复制文件。

# CMakeLists.txt
set(original "myfile_read")
file(CREATE_LINK ${original} myfile_read_hardlink)
file(CREATE_LINK ${original} myfile_read_softlink SYMBOLIC)

执行结果为,硬链接用ls -l查询,数字是2
-rw-r--r-- 2 myfile_read
-rw-r--r-- 2 shengyi staff 50 5 12 22:18 myfile_read_hardlink
lrwxr-xr-x 1 shengyi staff 11 5 17 08:52 myfile_read_softlink -> myfile_read

  • file(CHMOD <files>... <directories>... [PERMISSIONS <permissions>...] [FILE_PERMISSIONS <permissions>...] [DIRECTORY_PERMISSIONS <permissions>...])

说明3.19版本引入,修改指定文件或目录的权限,合法的权限设置选项有:OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, WORLD_READ, WORLD_WRITE, WORLD_EXECUTE, SETUID, SETGID。此外,PERMISSIONSFILE_PERMISSIONSDIRECTORY_PERMISSIONS只能单独使用或者按照如下方式组合使用:

  • PERMISSIONS:所有指定的文件或目录的权限都会被修改。
  • FILE_PERMISSIONS:只改变指定文件的权限。
  • DIRECTORY_PERMISSIONS:只改变指定目录的权限。
  • 同时指定PERMISSIONSFILE_PERMISSIONS:文件的权限会被FILE_PERMISSIONS指定的权限覆写。
  • 同时指定PERMISSIONSDIRECTORY_PERMISSIONS:目录的权限会被DIRECTORY_PERMISSIONS指定的权限覆写。
  • 同时指定FILE_PERMISSIONSDIRECTORY_PERMISSIONSFILE_PERMISSIONS指定的权限应用于文件,DIRECTORY_PERMISSIONS指定的权限应用于目录。

# CMakeLists.txt
# 备注:原始权限 file_test 644; dir_test 755
file(CHMOD file_test PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE WORLD_READ WORLD_WRITE) # 修改权限为666
#file(CHMOD dir_test PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_WRITE GROUP_EXECUTE WORLD_READ WORLD_WRITE WORLD_EXECUTE) # 修改权限为777

file(CHMOD file_test PERMISSIONS OWNER_READ GROUP_READ WORLD_READ FILE_PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE WORLD_READ) # FILE_PERMISSIONS指定权限起作用,修改为664
file(CHMOD dir_test PERMISSIONS OWNER_READ GROUP_READ WORLD_READ DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) # DIRECTORY_PERMISSIONS指定的权限起作用,修改为755

  • file(CHMOD_RECURSE <files>... <directories>... [PERMISSIONS <permissions>...] [FILE_PERMISSIONS <permissions>...] [DIRECTORY_PERMISSIONS <permissions>...])

说明3.19版本引入,与CHMOD的区别就是CHMOD_RECURSE会递归的修改指定目录下的子目录和文件权限,类似于Linux命令chmod -R

3.4 子命令:路径转换
  • file(REAL_PATH <path> <out-var> [BASE_DIRECTORY <dir>] [EXPAND_TILDE])

说明3.19版本中引入。计算指定路径的绝对路径。

  • BASE_DIRECTORY <dir>:如果指定了该选项,且给定待转换的路径<path>是一个相对路径,那么计算<path>相对于<dir>的路径。如果未指定该选项,那么计算<path>相对于CMAKE_CURRENT_SOURCE_DIR环境变量的路径。
  • EXPAND_TILDE3.21版本新增。如果<path>~或以~/开头,那么~会被替换为用户的home目录。home目录的值是从环境变量中得到,在Wndows下,首先会从USERPROFILE环境变量获取,如果未指定该环境变量,则会从HOME环境变量中获取;其他系统只会从HOME环境变量中获取。

# CMakeLists.txt
file(REAL_PATH dir_test result BASE_DIRECTORY "/bin")
message("Conversion result: ${result}")

message("Home path is : $ENV{HOME}")
file(REAL_PATH "~/dir_test" result BASE_DIRECTORY "/bin" EXPAND_TILDE) # 此时会忽略掉BASE_DIRECTORY指定的值
message("Conversion result: ${result}")

运行结果(macOS系统):
Conversion result: /bin/dir_test
Home path is : /Users/user1
Conversion result: /Users/user1/dir_test

  • file(RELATIVE_PATH <variable> <directory> <file>)

说明:计算<directory><file>的相对路径,实际上就是怎么从<directory>通过...等切换到<file>。需要注意的是,<directory><file>都必须是绝对路径。

# CMakeLists.txt
file(RELATIVE_PATH result /usr/local /etc/passwd)
message("Conversion result: ${result}")

运行结果(macOS系统):
Conversion result: ../../etc/passwd

  • file(TO_CMAKE_PATH|TO_NATIVE_PATH "<path>" <variable>)

说明TO_CMAKE_PATH将系统原生路径转换为CMake风格的路径。TO_NATIVE_PATH则反过来,将CMake风格的路径转换为系统原生路径。

  • CMake风格的路径:路径以/作为目录之间的分隔符,且多个目录以;分割的序列。
  • 原生系统路径:在Windows下,以\作为目录分隔符,多个路径以;分隔;而在其他系统,以/作为目录分隔符,多个路径以:分隔。例如在我的macOS下,PATH环境变量(对应系统原生路径)为:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

注意:子命令RELATIVE_PATHTO_CMAKE_PATHTO_NATIVE_PATH已经分别被cmake_path的子命令RELATIVE_PATHCONVERT ... TO_CMAKE_PATH_LISTCONVERT ... TO_NATIVE_PATH_LIST取代。因此直接参考cmake_path的相关命令。

3.5 子命令:传输
  • file(DOWNLOAD <url> [<file>] [<options>...]);file(UPLOAD <file> <url> [<options>...])

说明:从<url>指定的地址下载到本地<file>中。从3.19版本开始,如果没有指定本地文件<file>,那么不会保存文件,这样在检测待下载文件是否存在的时候比较有用。该命令还有许多可选项,解析如下:

  • INACTIVITY_TIMEOUT <seconds>:当超过指定时间没有活动则终止操作(上传/下载)。
  • LOG <variable>:将可读的日志存储到变量中。
  • SHOW_PROGRESS:打印进度信息。
  • STATUS <variable>:存储命令的操作(上传/下载)结果,由长度为2,以;分隔的序列组成,序列的第一个元素是一个以数字表示的结果返回值,第二元素是一个字符串表示错误的信息。如果没有出错,则只返回0
  • TIMEOUT <seconds>:超时后终止操作(上传/下载)。
  • USERPWD <username>:<password>3.7新增,为操作(上传/下载)指定用户名和密码。
  • HTTPHEADER <HTTP-header>3.7新增,为操作(上传/下载)指定http协议的头,该选项可以重复多次使用。
  • NETRC <level>3.11新增,指定操作(上传/下载)是否使用.netrc文件,如果未指定该选项,那么会从变量CMAKE_NETRC中获取,有如下几个级别:IGNORED表示忽略.netrc文件,也是选项的默认值;OPTIONAL表示.netrc文件可选,会优先从URL中获取信息,如果未从URL获取到信息则会从.netrc文件获取;REQUIRED表示使用.netrc文件,忽略URL中的信息。
  • NETRC_FILE <file>3.11新增,指定另一个.netrc文件,来替换home目录下的.netrc文件。如果未指定该选项,则会从CMAKE_NETRC_FILE变量中获取。
  • TLS_VERIFY <ON|OFF>:指定是否校验https URL服务器证书,默认是不验证。如果未指定该选项,则会从CMAKE_TLS_VERIFY变量中获取。3.18版本中增加了对file(UPLOAD)的支持。
  • TLS_CAINFO <file>:为https URL指定一个定制的证书鉴权文件。未指定该选项,则会从CMAKE_TLS_CAINFO变量中获取。3.18版本中增加了对file(UPLOAD)的支持。

如下选项只支持DOWNLOAD

  • EXPECTED_HASH ALGO=<value>:验证下载的内容的哈希值是否匹配期望的哈希值。ALGO指定file(HASH)支持的HASH算法,如果不匹配下载会出错。注意,如果未指定本地<file>文件,该命令会执行出错。
  • EXPECTED_MD5 <value>:该选项是为了兼容历史选项,实际上可以用上面的命令替代。它校验下载内容的MD5值和期望的是否匹配。如果不匹配下载会出错。注意,如果未指定本地<file>文件,该命令会执行出错。
3.6 子命令:锁
  • file(LOCK <path> [DIRECTORY] [RELEASE] [GUARD <FUNCTION|FILE|PROCESS>] [RESULT_VARIABLE <variable>] [TIMEOUT <seconds>])

说明3.2版本新增。锁定<path>指定的文件。

  • DIRECTORY:指定该选项会锁定目录,并会创建一个<path>/cmake.lock文件。
  • GUARD:指定锁定的范围,默认是PROCESS(进程),可选的还有FUNCTION(函数)和FILE(文件)。
  • RELEASE:显式的释放锁。指定该选项时,GUARDTIMEOUT会被忽略。
  • TIMEOUT:指定等待锁定的超时时间,如果值为0,表示只会尝试去锁定一次,其他非0值则是尝试锁定文件的超时时间。如果未指定该选项,则会一直尝试锁定,直到锁定成功或者出错。
  • RESULT_VARIABLE:存储命令执行的结果,如果成功,结果是0,否则存储具体的错误。如果未指定该选项,发生错误时候CMake会终止构建并抛出错误。

锁只是一个建议项,不能保证锁定后进程也会遵循。也不能对一个文件锁定2次。

为了验证,我们创建了两个CMakeLists.txt文件对同一个文件/目录进行锁定,目录树如下:
├── CMakeLists.txt
├── file_lock_test
│ ├── CMakeLists.txt
├── myfile_read
├── myfile_read_dir

# 顶层的CMakeLists.txt:在锁定文件myfile_read和文件夹myfile_read_dir后,睡眠50秒,目的是让file_lock_test下的CMakeLists.txt指定
file(LOCK myfile_read_dir DIRECTORY RESULT_VARIABLE result)
message("lock myfile_read_dir result: ${result}")
file(LOCK myfile_read RESULT_VARIABLE result)
message("lock myfile_read result: ${result}")
execute_process(COMMAND sleep 50)

# file_lock_test下的CMakeLists.txt
file(LOCK ../myfile_read_dir DIRECTORY RESULT_VARIABLE result TIMEOUT 0)
message("lock dir myfile_read_dir result: ${result}")
file(LOCK ../myfile_read RESULT_VARIABLE result TIMEOUT 10)
message("lock myfile_read result: ${result}")

运行结果:
首先执行顶层的CMakeLists.txt,出现如下打印说明锁定成功
lock myfile_read_dir result: 0
lock myfile_read result: 0

然后执行file_lock_test下的CMakeLists.txt,出现如下打印说明文件或者目录已经被锁定,命令超时退出了
lock dir myfile_read_dir result: Timeout reached
lock myfile_read result: Timeout reached

3.7 子命令:归档
  • file(ARCHIVE_CREATE OUTPUT <archive> PATHS <paths>... [FORMAT <format>] [COMPRESSION <compression> [COMPRESSION_LEVEL <compression-level>]] [MTIME <mtime>] [VERBOSE])

说明3.18版本新增。将PATHS指定的文件和目录进行归档,不支持通配符。

  • FORMAT:指定归档的格式,支持7zip, gnutar, pax, paxr, raw, zip,如果未指定该选项,默认归档格式是paxr
  • COMPRESSION:指定压缩方式,7zipzip归档格式已经使用压缩,其他格式默认不压缩,因此需要指定压缩方式,支持的压缩方式有None, BZip2, GZip, XZ, Zstd
  • COMPRESSION_LEVEL3.19版本新增。在指定压缩方式的前提下,可以指定压缩级别,范围是0-9,默认是0
  • VERBOSE:呈现归档命令的输出。
  • MTIME:如果要指定压缩文件的修改时间,指定该选项。

待压缩文件夹的结构为:
archive_test
├── dir1
│ └── file4
├── file1
├── file2
└── file

# CMakeLists.txt
file(ARCHIVE_CREATE OUTPUT result.tar.gz PATHS archive_test FORMAT gnutar COMPRESSION GZip VERBOSE MTIME "2022/5/18 23:59:59")

运行结果,并生成了一个result.tar.gz的压缩包:
archive_test
archive_test/file3
archive_test/file2
archive_test/file1
archive_test/dir1
archive_test/dir1/file4

  • file(ARCHIVE_EXTRACT INPUT <archive> [DESTINATION <dir>] [PATTERNS <patterns>...] [LIST_ONLY] [VERBOSE])

说明3.18版本新增。提取或者显示归档文件的内容。

  • DESTINATION:将归档文件提取到指定路径。
  • PATTERNS:通过该选项,可以从归档文件中提取或者显示指定的文件/目录,支持通配符。
  • LIST_ONLY:显示归档文件内容,不提取。
  • VERBOSE:呈现提取归档文件命令的输出。

以归档的result.tar.gz为例:

# CMakeLists.txt
message("====list========")
file(ARCHIVE_EXTRACT INPUT result.tar.gz VERBOSE LIST_ONLY) # 显示归档文件内容,不提取
message("====extract dir1/file4========")
file(ARCHIVE_EXTRACT INPUT result.tar.gz DESTINATION extract_dir1 PATTERNS "*dir1/*" VERBOSE) # 提取dir1目录下文件到extract_dir1目录
message("====extract all========")
file(ARCHIVE_EXTRACT INPUT result.tar.gz DESTINATION extract_dir2 VERBOSE) # 提取全部内容到extract_dir2目录

运行结果:
====list========
drwxr-xr-x 0 shengyi staff 0 18 May 23:59 archive_test/
-rw-r--r-- 0 shengyi staff 8 18 May 23:59 archive_test/file3
-rw-r--r-- 0 shengyi staff 8 18 May 23:59 archive_test/file2
-rw-r--r-- 0 shengyi staff 6 18 May 23:59 archive_test/file1
drwxr-xr-x 0 shengyi staff 0 18 May 23:59 archive_test/dir1/
-rw-r--r-- 0 shengyi staff 8 18 May 23:59 archive_test/dir1/file4
====extract dir1/file4========
x archive_test/dir1/
x archive_test/dir1/file4
====extract all========
x archive_test/
x archive_test/file3
x archive_test/file2
x archive_test/file1
x archive_test/dir1/
x archive_test/dir1/file4

extract_dir1目录内容:
extract_dir1
└── archive_test
└── dir1
└── file4

extract_dir2目录内容:
extract_dir2
└── archive_test
├── dir1
│ └── file4
├── file1
├── file2
└── file3

附录:参考目录
  1. https://cmake.org/cmake/help/latest/command/file.html
  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FL1768317420

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值