CMake管理第三方依赖
背景
最近在一个C++项目中尝试Bazel
编译,编译依赖方式确实写着比较舒服和直观,但最后链接出来的二进制文件在执行时报Segment error
,但用CMake
编译出来的二进制文件就可以成功执行,Bazel编译的问题无从下手。另外,Bazel无法从系统目录查找头文件,这就不能忍了,有人建议从cc_toolchain_config.bzl
查找问题,但toolchain实在是有点麻烦,就暂时放弃Bazel,继续使用CMake了。Bazel里提供的git_repositry
等从外部源自动下载编译依赖的方式很好用,所以就思考在CMake里是不是也有类似的东西呢。之前使用CMake时,第三方依赖都是手动先在本地安装好,后来查找到了CMake里提供了类似Bazel的命令,那就是ExternalProject
,不过这个命令只管下载编译等操作,但git_repositry
更好使一些,它可以根据依赖自动判断是不是下载,而ExternalProject
就没这么丝滑了,所以本文记录下在CMake怎样基于ExternalProject
打造git_repositry
那种丝滑的体验。
FindPackageHandleStandardArgs
在具体方案之前,先看一下CMake里提供的这个函数,在后面的实现中它可是非常重要哦。
This module provides a function intended to be used in Find Modules implementing find_package(<PackageName>) calls. It handles the REQUIRED, QUIET and version-related arguments of find_package. It also sets the <PackageName>_FOUND variable. The package is considered found if all variables listed contain valid results, e.g. valid filepaths.
find_package(<PackageName>)
,它判断一个包是否找到就是利用了FindPackageHandleStandardArgs
。这个函数会判断几个关键变量是否有正确的值,如果都经过了验证,就设置<PackageName>_FOUND
,既找到了包,否则就没找到。它有两个函数定义,一个简单的是这样滴:
find_package_handle_standard_args(<PackageName>
(DEFAULT_MSG|<custom-failure-message>)
<required-var>...
)FIND_PACKAGE_HANDLE_STANDARD_ARGS(Gflags DEFAULT_MSG GFLAGS_INCLUDE_DIR GFLAGS_LIBRARY)
GFLAGS_INCLUDE_DIR
和GFLAGS_LIBRARY
是否有正确值来断定GFlags
是否找到,这里的GFLAGS_INCLUDE_DIR
和GFLAGS_LIBRARY
当然是我们自己设定喽,比如我们这么设定它们,如果搜到了头文件和库文件,就设置正确路径,否则就没值嘛。注意,这里我们最好不要用GFLAGS_LIBIARIES
这种变量,这种我们是用来作为链接依赖库的变量的呀,同理,头文件那个变量也一样哦。
find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h /usr/local/include)
find_library(GFLAGS_LIBRARY gflags HINTS /usr/local/lib)
具体方案
我们比较常用的一种第三方库依赖的方式是这样嘛,先在本地系统目录搜下,如果找到就用,如果找不到就自动去下载编译。所以这里我们还是以GFlags
为例,分两步:
*
从系统目录查找GFlags,如果找到则设置GFLAGS_INCLUDE_DIR
和GFLAGS_LIBRARY
*
如果上一步没找到,那么从某个地方下载Gflags,本地编译安装后,再设置GFLAGS_INCLUDE_DIR
和GFLAGS_LIBRARY
我们把这两步定义成两个宏DO_FIND_GFLAGS_SYSTEM
和DO_FIND_GFLAGS_DOWNLOAD
,然后条件判断调用就行啦,像这样:
if(NOT GFLAGS_FOUND)
DO_FIND_GFLAGS_SYSTEM()
endif()
if(NOT GFLAGS_FOUND)
DO_FIND_GFLAGS_DOWNLOAD()
endif()
DO_FIND_GFLAGS_SYSTEM
比较简单哦,我们直接给出代码:
macro(DO_FIND_GFLAGS_SYSTEM)
find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h
PATHS /usr/local/include /usr/include
)
message("GFLAGS_INCLUDE_DIR: " ${GFLAGS_INCLUDE_DIR})
find_library(GFLAGS_LIBRARY
NAMES gflags
PATHS /usr/local/lib /usr/local/lib64 /usr/lib /usr/lib64
)
message("GFLAGS_LIBRARY: " ${GFLAGS_LIBRARY})
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Gflags DEFAULT_MSG
GFLAGS_INCLUDE_DIR GFLAGS_LIBRARY
)
set(GFLAGS_LIBRARIES ${GFLAGS_LIBRARY})
set(GFLAGS_INCLUDE_DIRS ${GFLAGS_INCLUDE_DIR})
mark_as_advanced(GFLAGS_LIBRARIES GFLAGS_INCLUDE_DIRS)
endmacro()GFLAGS_INCLUDE_DIR
和GFLAGS_LIBRARY
,然后调用FIND_PACKAGE_HANDLE_STANDARD_ARGS
来通过我们设置的两个变量来断定GFlags到底找没找到,如果找到它会自动设置GFLAGS_FOUND
为True
,那第二个宏,就是先下载再编译的那个,就不用执行喽。最后,我们设定GFLAGS_INCLUDE_DIRS
和GFLAGS_LIBRARIES
供我们的主程序作为头文件路径和库依赖路径使用呗。
第二个宏,我们终于用到开头提到的ExternalProject
这个牛逼玩意了^_^
The ExternalProject_Add() function creates a custom target to drive download, update/patch, configure, build, install and test steps of an external project.
macro(DO_FIND_GFLAGS_DOWNLOAD)
include(ExternalProject)
ExternalProject_Add(
Gflags
URL https://github.com/gflags/gflags/archive/v2.2.1.zip
URL_HASH SHA256=4e44b69e709c826734dbbbd5208f61888a2faf63f239d73d8ba0011b2dccc97a
UPDATE_COMMAND ""
CONFIGURE_COMMAND cmake -DCMAKE_INSTALL_PREFIX=${GFLAGS_ROOT_DIR} -DBUILD_SHARED_LIBS=ON -DBUILD_STATIC_LIBS=ON -DGFLAGS_NAMESPACE=google .
BUILD_COMMAND make
BUILD_IN_SOURCE true
INSTALL_COMMAND make install
INSTALL_DIR ${GFLAGS_ROOT_DIR}
)
ExternalProject_Get_Property(Gflags INSTALL_DIR)
set(GFLAGS_INCLUDE_DIR ${INSTALL_DIR}/include)
message("GFLAGS_INCLUDE_DIR: " ${GFLAGS_INCLUDE_DIR})
set(GFLAGS_LIBRARY ${INSTALL_DIR}/lib/${LIBRARY_PREFIX}gflags${LIBRARY_SUFFIX})
message("GFLAGS_LIBRARY: " ${GFLAGS_LIBRARY})
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Gflags DEFAULT_MSG
GFLAGS_INCLUDE_DIR GFLAGS_LIBRARY
)
set(GFLAGS_LIBRARIES ${GFLAGS_LIBRARY})
set(GFLAGS_INCLUDE_DIRS ${GFLAGS_INCLUDE_DIR})
mark_as_advanced(GFLAGS_LIBRARIES GFLAGS_INCLUDE_DIRS)
endmacro()ExternalProject_Add
从gayhub
上下载gflags源码,然后通过一系列编译安装操作,本地某临时目录就安装好GFlags了。里面几个参数CONFIGURE_COMMAND
、BUILD_COMMAND
、INSTALL_COMMAND
都是项目常用的流程,还是比较舒服滴。安装好后,我们通过ExternalProject_Get_Property
获取到真实安装路径,就可以像上面第一步那样设置GFLAGS_INCLUDE_DIR
和GFLAGS_LIBRARY
啦,后面都一样一样的。
回顾
是不是很简单?那咱总结下里面的关键点吧。 1.
先使用find_path
、find_library
查找系统头文件和库,找到了设置GFLAGS_INCLUDE_DIR
和GFLAGS_LIBRARY
变量
2.
本地没找到,就用ExternalProject_Add
从某地下载安装,然后设置GFLAGS_INCLUDE_DIR
和GFLAGS_LIBRARY
变量
3.
使用FIND_PACKAGE_HANDLE_STANDARD_ARGS
来验证上面两个变量,验证没问题则GFLAGS_FOUND
为True
那主程序那边怎么用呢?上面那些东西都保存为third_party/FindGflags.cmake
文件,在我们的CMakeLists.txt
里把它导入就行啦。
# 导入FindGflags.cmake
list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/third_party)
# 查找gflags
find_package(Gflags)
# 主程序头文件搜索路径
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${GFLAGS_INCLUDE_DIRS}
)
# 主目标
add_executable(hello hello.cc)
target_link_libraries(
hello
${GFLAGS_LIBRARIES}
)
最后,几个常用的第三方库都用这种方式实现了下,主要是我自己用的哦。