现代 CMake 模块化项目管理指南
现代 CMake 模块化项目管理指南
参考小彭老师的视频教程整理笔记,学习同时方便快速查阅,视频链接如下
【公开课】现代 CMake 模块化项目管理指南【C/C++】
对应课程 PPT 和源码见
https://github.com/parallel101/course
文件/目录组织规范
完整案例参考源码仓库
https://github.com/parallel101/course/tree/master/16/00
推荐的目录组织方式
.
├── biology
│ ├── CMakeLists.txt
│ ├── include
│ │ └── biology
│ │ └── Animal.h
│ └── src
│ └── Animal.cpp
├── CMakeLists.txt
└── pybmain
├── CMakeLists.txt
├── include
│ └── pybmain
│ └── myutils.h
└── src
└── main.cpp
第一点,划分了 biology 和 pybmain 两个子项目,每个子项目在各自目录下各有自己的 CMakeLists.txt 文件。
第二点,所有子项目中都被划分为了 include 和 src 两个子目录,分别用来放头文件和源码文件,而其中 include 目录又套了一层项目名,这样可以避免头文件名称冲突。子项目的 CMakeLists.txt 文件中需要使用
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
源码文件中这样写,
#include <biology/Animal.h>
#include <pybmain/myutils.h>
第三点,推荐每个模块都有自己的命名空间,头文件中需要
#pragma once
namespace biology {
class Animal {
//...
};
}
源码文件中需要
#include <biology/Animal.h>
namespace biology {
//...
};
根项目的 CMakeLists.txt 配置
cmake_minimum_required(VERSION 3.15)
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(CMake_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
project(CppCMakeDemo LANGUAGES CXX)
add_subdirectory(biology)
add_subdirectory(pybmain)
最后两行是关键,用来添加子项目,会调用子项目的 CMakeLists.txt 文件。
子项目的 CMakeLists.txt 配置
库文件的配置
# biology/CMakeLists.txt
file(GLOB_RECURSE srcs CONFIGURE_DEPENDS src/*.cpp include/*.h)
add_library(biology STATIC ${srcs})
target_include_directories(biology PUBLIC include)
首先使用 GLOB_RECURSE 命令获取所有源码文件,然后使用 add_library 命令添加静态库,最后使用 target_include_directories 命令添加头文件搜索路径。
PUBLIC修饰符表示这个头文件搜索路径会被暴露给其他依赖这个库的项目,链接了 biology 库的 pybmain 项目也可以共享这个路径。- 注意到我们将 .h 文件也一并添加到了
add_library命令中,这样可以确保 .h 文件也会被添加到 IDE 中,方便查看。 GLOB_RECURSE相比GLOB允许匹配嵌套的目录。CONFIGURE_DEPENDS选项表示如果源码文件发生变化,cmake --build build会检测目录是否更新,有新文件则自动重新运行cmake -B build重新配置项目。
可执行文件的配置
# pybmain/CMakeLists.txt
file(GLOB_RECURSE srcs CONFIGURE_DEPENDS src/*.cpp include/*.h)
add_executable(pybmain ${srcs})
target_include_directories(pybmain PUBLIC include)
target_link_libraries(pybmain PUBLIC biology)
基本和库文件的配置一致,只是使用 add_executable 命令添加可执行文件,使用 target_link_libraries 命令链接库文件。
CMake 的 include 功能
和 C/C++ 的 #include 一样,CMake 也有一个 include 命令,CMake 会在 CMAKE_MODULE_PATH 中搜索相应的 XXX.cmake 文件。
# ./CMakeLists.txt
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
project(CppCMakeDemo LANGUAGES CXX)
include(MyUsefulFuncs)
# cmake/MyUsefulFuncs.cmake
macro (my_add_target name type)
# 用法:my_add_target(pybmain EXECUTABLE)
file(GLOB_RECURSE srcs CONFIGURE_DEPENDS src/*.cpp src/*.h)
if ("${type}" MATCHES "EXECUTABLE")
add_executable(${name} ${srcs})
else()
add_library(${name} ${type} ${srcs})
endif()
target_include_directories(${name} PUBLIC include)
endmacro()
set(SOME_USEFUL_GLOBAL_VAR ON)
set(ANOTHER_USEFUL_GLOBAL_VAR OFF)
macro 和 function 的区别
- macro 相当于直接把代码粘贴过去,直接访问调用者的作用域。这里写的相对路径 include 和 src,是基于调用者所在路径。
- function 则是会创建一个闭包,优先访问定义者的作用域。这里写的相对路径 include 和 src,则是基于定义者所在路径。
include 和 add_subdirectory 的区别
- include 相当于直接把代码粘贴过去,直接访问调用者的作用域。这里创建的变量和外面共享,直接 set(key val) 则调用者也有 ${key} 这个变量了。
- function 中则是基于定义者所在路径,优先访问定义者的作用域。这里需要 set(key val PARENT_SCOPE) 才能修改到外面的变量。
第三方库/依赖项配置
主要讲解 find_package 命令,其官方文档为
https://cmake.org/cmake/help/latest/command/find_package.html
用法举例
# 查找名为 OpenCV 的包,找不到不报错,事后可以通过 ${OpenCV_FOUND} 查询是否找到。
find_package(OpenCV)
# 查找名为 OpenCV 的包,找不到不报错,也不打印任何信息。
find_package(OpenCV QUIET)
# 查找名为 OpenCV 的包,找不到就报错(并终止 cmake 进程,不再继续往下执行)。
find_package(OpenCV REQUIRED) # 最常见用法
# 查找名为 OpenCV 的包,找不到就报错,且必须具有 OpenCV::core 和 OpenCV::videoio 这两个组件,如果没有这两个组件也会报错。
find_package(OpenCV REQUIRED COMPONENTS core videoio)
# 查找名为 OpenCV 的包,找不到就报错,可具有 OpenCV::core 和 OpenCV::videoio 这两个组件,没有这两组件不会报错,通过 ${OpenCV_core_FOUND} 查询是否找到 core 组件。
find_package(OpenCV REQUIRED OPTIONAL_COMPONENTS core videoio)
find_package 原理
实际上 find_package(OpenCV) 是在找一个叫做 OpenCVConfig.cmake 的文件,这个文件是 OpenCV 项目提供的,用来告诉 cmake OpenCV 的安装路径和组件信息。
这个包配置文件由第三方库作者提供,在安装这个包时一并安装到系统中的,一般的约定是将其安装到 /usr/lib/cmake/XXX/ 目录下,其中 XXX 为包名。
Windows 系统下的搜索路径
<prefix>/
<prefix>/cmake/
<prefix>/<name>*/
<prefix>/<name>*/cmake/
<prefix>/<name>*/(lib/<arch>|lib*|share)/cmake/<name>*/
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/cmake/
其中 <prefix> 是变量 ${CMAKE_PREFIX_PATH},Windows 平台默认为 C:/Program Files,<name> 是在 find_package(<name> REQUIRED) 命令中指定的包名,<arch> 是系统的架构名。
Unix 系统下的搜索路径
<prefix>/(lib/<arch>|lib*|share)/cmake/<name>*/
<prefix>/(lib/<arch>|lib*|share)/<name>*/
<prefix>/(lib/<arch>|lib*|share)/<name>*/cmake/
<prefix>/<name>*/(lib/<arch>|lib*|share)/cmake/<name>*/
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/cmake/
其中 <prefix> 是变量 ${CMAKE_PREFIX_PATH},Unix 平台默认为 /usr,<name> 是你在 find_package(<name> REQUIRED) 命令中指定的包名,<arch> 是系统的架构,例如 x86_64-linux-gnu 或 i386-linux-gnu(用于伺候 Ubuntu 喜欢把库文件套娃在 /usr/lib/x86_64-linux-gnu 目录下)。
另外 <name> 可以有额外后缀,例如 find_package(Qt5) 可以在 Qt5.12.1/cmake 或者 Qt5xxx/cmake 目录下找到 Qt5Config.cmake 文件。
非标准路径安装的库
想让 CMake 找到非标准路径安装的库,本质上是定义好 NAME_DIR 变量,告诉 CMake 库文件的路径,然后再调用 find_package 命令。
例如可以直接在 CMakeLists.txt 中定义该变量,
# pybmain/CMakeLists.txt
set(OpenCV_DIR "/home/pyb/opencv/build")
find_package(OpenCV REQUIRED)
这种方式对该项目有效,但是不便于开源分发,因其路径直接写死在 CMakeLists.txt 中,其他人要用的时候就会找不到该路径下的 OpenCVConfig.cmake 文件。
也可以设置环境变量
export OpenCV_DIR="/home/pyb/opencv/build"
这种方式全局有效,但是不便于多版本共存,因为环境变量是全局的,如果要切换到其他版本的 OpenCV,就需要重新设置环境变量。
最好的方式是在调用 cmake 命令时定义该变量,例如
cmake -B build -DOpenCV_DIR="/home/pyb/opencv/build"
虽然每次需要在命令行中输入,但是这种方式既不会污染全局环境,也不会污染项目的 CMakeLists.txt,而且可以方便的切换版本。并且 CMake 本身有缓存功能,只要没有删除 build 目录下的 CMakeCache.txt 文件,下次再运行 cmake -B build 时不输入该变量 CMake 也会自动读取缓存中的值。
未提供 Config 文件的第三方库
有一些库非常热门,但是并未提供 Config 文件,例如 Python,CUDA,Jemalloc 等,这时候就需要我们自己写 FindXXX.cmake 文件来查找该库,幸运的是 CMake 已经为我们提供了这些库的 FindXXX.cmake 文件,可以在 CMake 安装目录下的 share/cmake/Modules/ 目录下找到。
另外有一些库没有那么热门,CMake 也没有为我们提供 FindXXX.cmake 文件,这时候需要我们自己编写相应的 Find 文件,但是往往网上已经有人写过了,只需要搜索一下就可以找到,下面的链接是 Jemalloc 的 Find 文件
https://github.com/AcademySoftwareFoundation/openvdb/blob/master/cmake/FindJemalloc.cmake
这些文件有些使用的是古代 CMake 风格,有些是现代 CMake 风格,命名也不尽统一,但是一般都会有相应的说明文档,可以参考着使用。
指定 find_package 模式
find_package 命令有两种模式,一种是 MODULE 模式,一种是 CONFIG 模式。
MODULE模式,只会寻找FindTBB.cmake文件,而不会寻找TBBConfig.cmake文件,这种模式下,find_package命令会在CMAKE_MODULE_PATH(默认为/usr/share/cmake/Modules)中搜索FindTBB.cmake文件find_package(TBB MODULE REQUIRED)
CONFIG模式,只会寻找TBBConfig.cmake文件,而不会寻找FindTBB.cmake文件,这种模式下,find_package命令会在${CMAKE_PREFIX_PATH}/lib/cmake/TBB(默认为/usr/lib/cmake/TBB)、${TBB_DIR}和$ENV{TBB_DIR}中搜索TBBConfig.cmake文件find_package(TBB CONFIG REQUIRED)
不指定则两者都会尝试,默认先查找 Find 文件,如果找不到再查找 Config 文件。
直接作为子模块引用
有些库并不是通过 find_package 命令来引用的,而是直接将其作为子模块引入项目中,例如
add_subdirectory(spdlog)
target_link_libraries(myapp PUBLIC spdlog)
现代 CMake 模块化项目管理指南的更多相关文章
- cmake简明使用指南
cmake简明使用指南 Last update 2018/8/8 先执行cmake生成makefile,然后看看里面的内容,(至少在ubuntu16.04上的cmake3.5.1上),有如下内容提供: ...
- cmake编译opencv指南
目录 cmake编译opencv指南 用包管理器安装 查看opencv相关的包 执行安装 编译安装 为什么编译安装 下载源码 编译脚本 执行编译脚本.解决3rdparty下载 编译后的设定-环境变量 ...
- ubuntu日常使用指南
目录 换源 开发相关的基本包 vimrc python, pip zsh, oh-my-zsh, josh 配置android相关环境 查看库文件(libxxx.a/libxxx.so,动态静态库均可 ...
- 项目管理目标:添加人员并向其分配任务 - Project
已剪辑自: https://support.office.com/zh-cn/article/%E9%A1%B9%E7%9B%AE%E7%AE%A1%E7%90%86%E7%9B%AE%E6%A0%8 ...
- Linux下的几个好用的命令与参数
将所有文件的编码,转换为UTF-8 find . ! -type d -exec enca -L zh_CN -x UTF-8 {} \; 将指定目录下所有文件权限设定为644 find . ! -t ...
- 源码编译安装 MySQL 5.5.x 实践
1.安装cmakeMySQL从5.5版本开始,通过./configure进行编译配置方式已经被取消,取而代之的是cmake工具.因此,我们首先要在系统中源码编译安装cmake工具. # wget ht ...
- 源码编译安装 MySQL 5.5.x 实践(转)
1.安装cmakeMySQL从5.5版本开始,通过./configure进行编译配置方式已经被取消,取而代之的是cmake工具.因此,我们首先要在系统中源码编译安装cmake工具. # wget ht ...
- OpenGL编程逐步深入(一)创建一个窗口
原文地址:http://ogldev.atspace.co.uk/ 原文中使用gnu make进行项目管理,本系列文章使用visual studio2012.在翻译过程中并非直译,加入了一些笔者个人观 ...
- 以QT为例谈环境搭建
以QT为例谈环境搭建 作者:哲思 时间:2022.1.5 邮箱:1464445232@qq.com GitHub:zhe-si (哲思) (github.com) 前言 自从实习结束,好久没写博客了. ...
- 项目管理之道--纪我的新书《PMP项目管理认证学习指南(第4版)》出版并预祝大卖!
新年伊始,我最新的项目管理书籍——<PMP项目管理认证学习指南(第4版)>也出版了,真是新年新气象啊!翻译英文书籍是一件任重道远的工作,除了要具备扎实的基本功,熟悉相关的领域外,还需要细致 ...
随机推荐
- 基于百度AI实现文字和图像敏感内容审核
前言 百度AI是指百度公司的人工智能技术全称.它采用深度学习技术,包括自然语言处理.语音识别.计算机视觉.知识图谱等,可应用于各个领域如互联网.医疗.金融.教育.汽车.物流等.百度AI的发展将帮助人类 ...
- Linux下jdk配置
1.首先执行以下命令查看可安装的jdk版本: yum -y list java* 执行成功后可看到如下界面: 2.选择自己需要的jdk版本进行安装,比如这里安装1.8,执行以下命令: yum in ...
- Arduino 板的说明
Arduino 板的说明 在本章中,我们将了解 Arduino 板上的不同组件.将学习 Arduino UNO 板,因为它是 Arduino 板系列中最受欢迎的.此外,它是开始使用电子和编码的最佳板. ...
- CF1364B
题目简化和分析: 这题没啥好说的,找其绝对值最大,也就是找到每一个山峰山谷. 这样不仅满足选择的个数最少,并且值最大. 正确性证明: 若 \(a\le b\le c\) \(|a-b|+|b-c|=( ...
- python加解密小工具
1.地址 https://github.com/Doneone/RSA_GUI 2.用法 python3 rsa_gui.py 创建密钥对 3.思考 为什么要写rsa小工具呐,因为单纯想实现一个简单的 ...
- 飞码LowCode前端技术系列(二):如何便捷配置出页面 | 京东云技术团队
一.配置解法 飞码LowCode前端技术(一)中飞码提出了至少需要满足2个大能力点以及对应16个细化点.在业务复杂的场景下数据具有流转性质,事件的触发会改变数据.同时也会触发其他事件等情况.飞码使用数 ...
- angular:响应式表单(Reactive Forms)和模板驱动表单(Template-Driven Forms)分别进行验证
2022-01-18 响应式表单 响应式表单是围绕Observable的流构建的. 使用响应式表单时,FormControl类是最基本的构造类. 在使用响应式表单前,需要先导入 ReactiveFor ...
- spring---面向切面(AOP @Pointcut 表达式篇)
AOP(面向切面编程),可以说是OOP(面向对象编程)的补充和完善.OOP引入封装.继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合. 当我们需要为分散的对象引入公共行为的时候,O ...
- 字符串匹配算法:KMP
Knuth–Morris–Pratt(KMP)是由三位数学家克努斯.莫里斯.普拉特同时发现,所有人们用三个人的名字来称呼这种算法,KMP是一种改进的字符串匹配算法,它的核心是利用匹配失败后的信息,尽量 ...
- 国产瀚高数据库简单实践 及 authentication method 13 not supported 错误解决方法
近几年IT界软硬件"国产化"搞得很密集,给很多公司带来了商机.但是有些公司拿国外的代码改改换个皮肤,就是"自主知识产权"的国产软件,光明正大卖钱,这个有点... ...