前言
后台C语言或者C++代码会集成一些三方库,一些跨平台软件要求兼容linux各种国产系统、mac、windows等,用到的这些三方库也需要支持各个平台。如果在项目代码编译时集成的是库源码,只需要管理一份库的源码即可。
如果在项目代码中使用的是三方库二进制文件,例如windows(动态库dll、静态库lib)、国产linux系统(各个架构系统下的.a和.so),mac下的(.a、.dylib),往往需要事先手动下载并放入thirdparty/ 目录。一般项目管理要求部署一个服务器,把使用的三方库源码和二进制文件放到服务器一份,不放到git源码里,方便大家统一维护使用。
对于小公司来说,几个人开发,只要做好版本管理大家沟通好就可以了。如果遇到那种不写文档,不说清楚的同事,这样强制规范一些是很合理的,减少了不必要的沟通成本。也给了领导安全感,不至于人跑路了,东西找不到了,重新来做。
一、实现思路
如下提供一种在项目源码中自动下载引用三方库的思路:
1、检查本地是否已有目标依赖
例如在项目源码中存在一个thirdparty三方库文件夹,要检查的依赖是spdlog(日志三方库源码)。首先需要检查thirdparty下是否存在spdlog文件夹,如果存在,则认为已经下载源码,无需重复下载,否则根据cmaklist中提供的链接从服务器下载源码压缩包并解压到thirdparty/spdlog目录中。
2、如果没有三方库 ,自动从指定 URL 下载
3、解压 到指定目录
.tar.gz
4、解压后删除压缩包
5、支持旧版 CMake(无 也能正常工作)
file(ARCHIVE_EXTRACT)
二、代码实现
# ===========================================================
# 下载并解压一个依赖包
# 用法:
# download_and_extract(<LIB_NAME> <LIB_URL> <DEST_DIR>)
# 示例:
# download_and_extract(spdlog "http://example.com/spdlog-1.x.tar.gz" "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty")
# ===========================================================
function(download_and_extract LIB_NAME LIB_URL DEST_DIR)
message(STATUS "Preparing dependency: ${LIB_NAME}")
set(LIB_PATH "${DEST_DIR}/${LIB_NAME}")
get_filename_component(LIB_TAR_NAME "${LIB_URL}" NAME)
set(LIB_TAR_PATH "${LIB_PATH}/${LIB_TAR_NAME}")
if (NOT EXISTS "${LIB_PATH}")
message(STATUS "Downloading ${LIB_NAME} from ${LIB_URL} ...")
file(MAKE_DIRECTORY "${DEST_DIR}")
file(MAKE_DIRECTORY "${LIB_PATH}")
file(DOWNLOAD
"${LIB_URL}"
"${LIB_TAR_PATH}"
SHOW_PROGRESS
)
if (COMMAND file)
file(ARCHIVE_EXTRACT
INPUT "${LIB_TAR_PATH}"
DESTINATION "${LIB_PATH}"
)
else()
execute_process(
COMMAND ${CMAKE_COMMAND} -E tar xzf "${LIB_TAR_NAME}"
WORKING_DIRECTORY "${LIB_PATH}"
)
endif()
file(REMOVE "${LIB_TAR_PATH}")
message(STATUS "Removed archive: ${LIB_TAR_PATH}")
else()
message(STATUS "Dependency ${LIB_NAME} already exists, skipping download.")
endif()
endfunction()
调用示例:
set(LIB_BASE_URL "http://xxx.xxx.com/xxxx-deps")
set(LIB_DIR "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty")
# 下载并解压 spdlog
download_and_extract(
spdlog
"${LIB_BASE_URL}/spdlog/spdlog-1.x.tar.gz"
"${LIB_DIR}"
)
# 然后直接引入
add_subdirectory("${LIB_DIR}/spdlog")
首次运行时会看到类似输出:
-- Preparing dependency: spdlog
-- Downloading spdlog from http://xxx.xxx.com/xxxx-deps/spdlog/spdlog-1.x.tar.gz ...
-- Removed archive: .../thirdparty/spdlog/spdlog-1.x.tar.gz
三、分平台下载三方库编译结果和头文件
在三方库是源码编译时,不需要区分平台。在使用三方库编译后的可执行文件和头文件引用时,需要区分平台。
如果第三方依赖在服务器上按平台分类(例如 win/x86_64/iconv.tar.gz),可以添加如下逻辑自动选择下载路径,自动判断平台与架构实现如下:
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" SYS_CPU)
if (SYS_CPU MATCHES "x86_64|amd64")
set(ARCH "x86_64")
elseif (SYS_CPU MATCHES "i386|i686|x86")
set(ARCH "x86")
elseif (SYS_CPU MATCHES "arm64|aarch64")
set(ARCH "arm64")
else()
set(ARCH "unknown")
endif()
if (WIN32)
set(PLATFORM "win")
elseif (APPLE)
set(PLATFORM "mac")
elseif (UNIX)
set(PLATFORM "linux")
endif()
set(LIB_URL "${LIB_BASE_URL}/${LIB_NAME}/${PLATFORM}/${ARCH}/${LIB_NAME}.tar.gz")
引用如下:
download_and_extract(
libetpan
"${LIB_BASE_URL}/iconv/${LIB_PLATFORM}/${LIB_ARCH}/iconv.tar.gz"
"${LIB_DIR}"
)
if (WIN32)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/iconv/include)
target_link_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/iconv/lib)
elseif(UNIX)
#其它
endif()
四、windows版本将dll复制到项目编译结果目录
有一些库的dll要放到编译结果目录,在运行时才能找到库正常运行,cmakelist脚本示例如下:
# 获取 xxx/lib 目录下的所有 .dll 文件
file(GLOB LIBETPAN_DLLS "${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/xxx/lib/*.dll")
# 遍历并复制每个 DLL 到输出目录
foreach(DLL ${LIBETPAN_DLLS})
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${DLL}
$<TARGET_FILE_DIR:${PROJECT_NAME}>
)
endforeach()


