今天补下之前在 ExternalProject 实践 留下的坑:如何下载第三方依赖。
理清需求
由于大家的需求很可能是不一致的,这里选一个比较通用的需求:下载第三方依赖压缩包,于是我们就需要下载压缩包文件到本地,验证文件签名,然后解压到指定目录。
CMake 提供的命令
我们要用到主要有以下两个命令:
- file
- DOWNLOAD:下载文件
- INSTALL:安装文件到目录
- READ:读取文件内容
- REMOVE:删除文件
- REMOVE_RECURSE:递归删除文件
- MAKE_DIRECTORY:创建目录
- cmake_parse_arguments:解析传入的函数参数
- execute_process:运行外部命令(这里需要注意的是,外部命令很有可能运行在不同的操作系统,因此,尽量使用 CMake 提供的命令,这样可以在各个平台都能运行。)
实现
接下来我们一步步把功能实现。
下载功能
1 2 3 4
| function(download_file url filename) message(STATUS "Download to ${filename} ...") file(DOWNLOAD ${url} ${filename}) endfunction()
|
这便是实现了一个最简单的下载函数,我们直接传入链接地址以及文件名即可下载 download_file('http://example.com/1.zip', '2.zip')
。
接下来,我们开始添油加醋,慢慢实现自己的需求。
文件签名验证
1 2 3 4
| function(download_file_with_hash url filename hash_type hash) message(STATUS "Download to ${filename} ...") file(DOWNLOAD ${url} ${filename} EXPECTED_HASH ${hash_type}=${hash}) endfunction()
|
于是,调用方式变为 download_file_with_hash('http://example.com/1.zip', '2.zip', 'SHA1', 'xxxxxxxxxxxxxxx')
。
解压文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function(extract_file filename extract_dir) message(STATUS "Extract to ${extract_dir} ...")
set(temp_dir ${CMAKE_BINARY_DIR}/tmp_for_extract.dir) if(EXISTS ${temp_dir}) file(REMOVE_RECURSE ${temp_dir}) endif() file(MAKE_DIRECTORY ${temp_dir}) execute_process(COMMAND ${CMAKE_COMMAND} -E tar -xf ${filename} WORKING_DIRECTORY ${temp_dir})
file(GLOB contents "${temp_dir}/*") list(LENGTH contents n) if(NOT n EQUAL 1 OR NOT IS_DIRECTORY "${contents}") set(contents "${temp_dir}") endif()
get_filename_component(contents ${contents} ABSOLUTE) file(INSTALL "${contents}/" DESTINATION ${extract_dir}) file(REMOVE_RECURSE ${temp_dir}) endfunction()
|
下载后解压
1 2
| download_file('http://example.com/1.zip', '2.zip', 'SHA1', 'xxxxxxxxxxxxxxx') extract_file('2.zip', '/path/to/install')
|
是不是很简单?现在我们加入更多的功能。
文件缓存
很多时候,如果下载的文件存在,我们只需要验证它的签名即可,即我们不用重复下载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| if(EXISTS ${filename}) file(${hash_type} ${filename} _ACTUAL_CHKSUM) if(NOT (${hash} STREQUAL ${_ACTUAL_CHKSUM})) message(STATUS "Expect ${DAE_HASH_TYPE}=${_EXPECT_HASH}") message(STATUS "Actual ${DAE_HASH_TYPE}=${_ACTUAL_CHKSUM}") message(WARNING "File hash mismatch, remove & retry ...") file(REMOVE ${filename}) download_file_with_hash(${url} ${filename} ${hash_type} ${hash}) else() message(STATUS "Using exists local file ${filename}") endif() else() download_file_with_hash(${url} ${filename} ${hash_type} ${hash}) endif()
|
参数解析
最后,我们将这个过程封装成一个单独的函数,并且加上参数解析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| function(download_and_extract) set(options REMOVE_EXTRACT_DIR_IF_EXISTS) set(oneValueArgs DESTINATION RENAME) set(multiValueArgs) set(oneValueArgs URL FILENAME HASH_TYPE HASH EXTRACT_DIR) cmake_parse_arguments(DAE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if(NOT DEFINED DAE_URL) message(FATAL_ERROR "Missing URL") endif() if(NOT DEFINED DAE_FILENAME) message(FATAL_ERROR "Missing FILENAME") endif() if(NOT DEFINED DAE_HASH_TYPE) message(FATAL_ERROR "Missing HASH_TYPE") endif() if(NOT DEFINED DAE_HASH) message(FATAL_ERROR "Missing HASH") endif() if(NOT DEFINED DAE_EXTRACT_DIR) message(FATAL_ERROR "Missing EXTRACT_DIR") endif()
if(EXISTS ${DAE_EXTRACT_DIR}) if(DAE_REMOVE_EXTRACT_DIR_IF_EXISTS) message(STATUS "${DAE_EXTRACT_DIR} already exists, removing...") file(REMOVE_RECURSE ${DAE_EXTRACT_DIR}) else() message( STATUS "${DAE_EXTRACT_DIR} already exists, skip download & extract") return() endif() endif()
if(EXISTS ${DAE_FILENAME}) file(${DAE_HASH_TYPE} ${DAE_FILENAME} _ACTUAL_CHKSUM)
if(NOT (${_EXPECT_HASH} STREQUAL ${_ACTUAL_CHKSUM})) message(STATUS "Expect ${DAE_HASH_TYPE}=${_EXPECT_HASH}") message(STATUS "Actual ${DAE_HASH_TYPE}=${_ACTUAL_CHKSUM}") message(WARNING "File hash mismatch, remove & retry ...") file(REMOVE ${DAE_FILENAME}) download_file_with_hash(${DAE_URL} ${DAE_FILENAME} ${DAE_HASH_TYPE} ${_EXPECT_HASH}) else() message(STATUS "Using exists local file ${DAE_FILENAME}") endif() else() download_file_with_hash(${DAE_URL} ${DAE_FILENAME} ${DAE_HASH_TYPE} ${_EXPECT_HASH}) endif() extract_file(${DAE_FILENAME} ${DAE_EXTRACT_DIR}) endfunction()
|
于是,一个完整的文件下载解压函数就完成了,我们可以在项目中,这样使用自己实现的函数:
1 2 3 4 5 6
| download_and_extract( URL https://example.com/1.tar.gz FILENAME /tmp/1.tar.gz HASH_TYPE SHA1 HASH xxxxxxxx EXTRACT_DIR /tmp/example_dir)
|
首发于 Github issues: https://github.com/xizhibei/blog/issues/142 ,欢迎 Star 以及 Watch
本文采用 署名-非商业性使用-相同方式共享(BY-NC-SA)进行许可
作者:习之北 (@xizhibei)
原链接:https://blog.xizhibei.me/zh-cn/2020/06/30/cmake-9-implement-download-extract-file/