文章目录
- 一、cmake依赖管理介绍
- 二、源码依管理
- 1. `FetchContent`
- 与find_package进行集成
- 2. CPM
- 3. git submodule
- 附加: address_sanitizer 和 undefined sanitizer
一、cmake依赖管理介绍
CMake 是跨平台的构建系统,支持 C/C++、Objective-C、Fortran 等多种语言。CMake 提供了丰富的依赖管理功能,可以帮助开发人员轻松地管理第三方库的依赖关系。
CMake 的依赖管理功能主要包括以下几点:
- 支持多种依赖获取方式:CMake 支持 Git Clone、下载源码压缩包等多种方式获取第三方库。
- 支持重复依赖处理:CMake 可以自动处理依赖树中存在的重复依赖。
- 支持第三方库的 Find 脚本:CMake 官方为许多常用的第三方库提供了 Find 脚本,可以帮助开发人员快速地获取和配置这些库。
CMake 的依赖管理功能可以分为两种类型:
- 源码依赖:这种依赖是指第三方库的源代码。CMake 可以使用 Git Clone 或下载源码压缩包的方式获取第三方库的源代码。
- 二进制依赖:这种依赖是指第三方库的二进制文件。CMake 可以使用第三方库的包管理器(例如 Conda 或 Homebrew)来获取第三方库的二进制文件。
今天我们就主要介绍: 源码管理
二、源码依管理
- cmake自带的命令:
FetchContent
- cmake三方社区支持的命令:
CPM
- git的
submodule
该如何选择何种方式作为依赖管理:
如果你的依赖不是cmake
项目,那就推荐使用git submodule
进行管理
如果你的依赖项目是cmake
项目,那FetchContent
和CPM
都能满足需要,但是CPM
更为优雅,所以更加推荐
1. FetchContent
使用FetchContent
是非常简单的,只需要三步:
- 引入头文件
- 定义依赖需要下载代码的地址和一些其它属性
- enable依赖
比如:
# 从GitHub上下载代码
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG 703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
)
# 从网址下载代码
FetchContent_Declare(
myCompanyIcons
URL https://intranet.mycompany.com/assets/iconset_1.12.tar.gz
URL_HASH MD5=5588a7b18261c20068beabfb4f530b87
)
# 确保依赖项在这里已经完成下载和构建
FetchContent_MakeAvailable(googletest myCompanyIcons)
FetchContent_Declare命令会生成以下变量:
<lowercaseName>_POPULATED
: 设置为TRUE
<lowercaseName>_SOURCE_DIR
: 设置依赖下载的位置<lowercaseName>_BINARY_DIR
: 设置依赖构建的位置
在设置使用关键字定义依赖源的一些信息时候,有一些可供选择的设置:
# 从GitHub上下载代码
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG 703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
# 先通过find_package命令,通过name Gtest来找库,找不到就调用Fetch下载依赖
# FIND_PACKAGE_ARGS NAMES GTest
# find_package不能从本地找库,要用Fetch下载下来的库
# OVERRIDE_FIND_PACKAGE
# 该依赖的cmakelist配置文件是在该项目cmake文件夹内。
# SOURCE_SUBDIR cmake
# 设置下载的依赖存放路径
# SOURCE_DIR firmware
# 是否启用浅克隆,(只会克隆当前分支或标签的提交,以及这些提交所引用的所有父提交)
# GIT_SHALLOW
)
# 确保依赖项在这里已经完成下载和构建
FetchContent_MakeAvailable(googletest myCompanyIcons)
有些情况下,想要对依赖的管理控制的颗粒度更加精细,我们可以:
# 检查是否依赖已经下载
FetchContent_GetProperties(depname)
# 如果还没有下载
if(NOT depname_POPULATED)
# 执行下载
FetchContent_Populate(depname )
endif()
使用该方法进行下载安装依赖,该方法依赖FetchContent_Declare()
提前声明依赖。
但是下载依赖只会下载一次,如果二次调用则会因为错误而停止,
所以项目在调用FetchContent_Populate
之前,应该先调用FetchContent_GetProperties()
方法,获得当前的状态
使用保存的内容详细信息时,对 FetchContent_MakeAvailable()
或 FetchContent_Populate()
的调用会在全局属性中记录可以随时查询的信息。该信息可以包括与内容相关联的源目录和二进制目录,以及内容填充是否已在当前配置运行期间被处理。
与find_package进行集成
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG 703bd9caab50b139428cea1aaff9974ebee5742e # release-1.10.0
FIND_PACKAGE_ARGS NAMES GTest
)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG 605a34765aa5d5ecbf476b4598a862ada971b0cc # v3.0.1
FIND_PACKAGE_ARGS
)
# This will try calling find_package() first for both dependencies
FetchContent_MakeAvailable(googletest Catch2)
对于 Catch2
,不需要为 find_package()
提供额外的参数,因此在 FIND_PACKAGE_ARGS
关键字之后不需要提供额外的参数。对于 googletest
,其包通常称为 GTest
,因此添加参数以支持通过该名称找到它。
OVERRIDE_FIND_PACKAGE
: find_package应该只使用FetchContent_Declare里的信息
FIND_PACKAGE_ARGS
: 如果find_package成功在本地找到二进制文件,则不需要FetchContent_Declare里的信息。
SOURCE_SUBDIR cmake
: 指定依赖的项目cmakelist
文件的地址, 该实例配置在项目跟路径下的cmake文件夹。
SOURCE_DIR firmware
:指定下载依赖后存放的路径,该设置配置在项目跟路径下的firmware
文件夹
2. CPM
非官方的cmake依赖管理工具: CPM GitHub
使用方法: 直接到release页面,将cmake脚本文件下载,导入到自己的代码中。
include(CPM)
# 使用GitHub用户nlohmann的仓库的json仓的3.11.2版本代码
cpmaddpackage("gh:nlohmann/json#v3.11.2")
gh:
: githubnlohmann
: 用户名json
: 仓库名#v3.11.2
: tag版本
3. git submodule
cmake demo
如果依赖是cmake项目,不太推荐使用该方法,将该方法封装为一个函数。可以参考:
# 函数add_git_submodule, 接受一个参数dir
function(add_git_submodule dir)
# 依赖Git
find_package(Git REQUIRED)
# 如果指定的文件夹不存在,则调用git submodule的方法拉取最新代码
if (NOT EXISTS ${CMAKE_SOURCE_DIR}/${dir}/CMakeLists.txt)
execute_process(COMMAND ${GIT_EXECUTABLE}
submodule update --init --recursive -- ${CMAKE_SOURCE_DIR}/${dir}
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
endif()
# 拉取下来后将项目进行编译
if (EXISTS ${CMAKE_SOURCE_DIR}/${dir}/CMakeLists.txt)
message("Adding: ${dir}/CMakeLists.txt")
add_subdirectory(${CMAKE_SOURCE_DIR}/${dir})
else()
message("Could not add: ${dir}/CMakeLists.txt")
endif()
endfunction(add_git_submodule)
- 使用方法:
# 设置cmake模块的路径
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/")
# 引入cmake的模块(xx.txt, xx就是模块名字)
include(AddGitSubmodule)
add_git_submodule(external/json)
附加: address_sanitizer 和 undefined sanitizer
option(ENABLE_SANITIZE_ADDR "Enable address sanitizer" ON)
option(ENABLE_SANITIZE_UNDEF "Enable undefined sanitizer" ON)
function(add_sanitizer_flags)
if (NOT ENABLE_SANITIZE_ADDR AND NOT ENABLE_SANITIZE_UNDEF)
message(STATUS "Sanitizers deactivated.")
return()
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
add_compile_options("-fno-omit-frame-pointer")
add_link_options("-fno-omit-frame-pointer")
if(ENABLE_SANITIZE_ADDR)
add_compile_options("-fsanitize=address")
add_link_options("-fsanitize=address")
endif()
if(ENABLE_SANITIZE_UNDEF)
add_compile_options("-fsanitize=undefined")
add_link_options("-fsanitize=undefined")
endif()
elseif(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
if(ENABLE_SANITIZE_ADDR)
add_compile_options("/fsanitize=address")
endif()
if(ENABLE_SANITIZE_UNDEF)
message(STATUS "Undefined sanitizer not impl. for MSVC!")
endif()
else()
message(STATUS "Sanitizer not supported in this environment!")
endif()
endfunction(add_sanitizer_flags)
-
参考:
-
CPP的项目脚手架
-
FetchContent官网文档
-
UdemyCmake(https://github.com/franneck94/UdemyCmake/tree/master)
-
UdemyCmkae vedeo