首页 >> 大全

现代 CMake 模块化项目管理指南

2023-11-17 大全 24 作者:考证青年

文章目录 二、用 寻找系统中第三方库/依赖项配置 其他

一、基于CMake,对文件/目录组织规范 1.推荐输出是可执行程序的目录组织方式

目录组织格式:

.txt 中写:

源码文件中写:

#include <项目名/模块名.h>
项目名::函数名();

头文件(项目名//项目名/模块名.h)中写:

#pragma once
namespace 项目名 {
void 函数名();
}

实现文件(项目名/src/模块名.cpp)中写:

#include <项目名/模块名.h>
namespace 项目名 {
void 函数名() { 函数实现 }
}

2.划分子项目

eg:

大型的项目,往往会划分为几个子项目。

即使你只有一个子项目,也建议你先创建一个子目录,方便以后追加新的子项目。

上图的案例中,我们在根目录下,创建了两个子项目 和 ,他们分别在各自的目录下有自己的 .txt。

3.根项目的 .txt 配置

cmake_minimum_required(VERSION 3.18)if (NOT CMAKE_BUILD_TYPE)set(CMAKE_BUILD_TYPE Release)
endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake;${CMAKE_MODULE_PATH}")project(CppCMakeDemo LANGUAGES CXX)include(MyUsefulFuncs)add_subdirectory(pybmain)
add_subdirectory(biology)

4.子项目的 .txt 配置

file(GLOB_RECURSE srcs CONFIGURE_DEPENDS src/*.cpp include/*.h)
add_library(biology STATIC ${srcs})
target_include_directories(biology PUBLIC include)

5.子项目的头文件

6.子项目的源文件

补充:GLOB 和 的区别

file (GLOB myvar CONFIGURE_DEPENDS src/*.cpp)
file (GLOB_RECURSE myvar CONFIGURE_DEPENDS src/*.cpp)

7.头文件和源文件的一一对应关系

8.只有头文件,没有源文件的情况

9.每新增一个功能模块,需要创建两个文件

10.一个模块依赖其他模块,则应导入他的头文件

如果新模块(Carer)中用到了其他模块()的类或函数,则需要在新模块(Carer)的头文件和源文件中都导入其他模块()的头文件。

11.依赖其他模块但不解引用,则可以只前向声明不导入头文件

如果模块 Carer 的头文件 Carer.h 虽然引用了其他模块中的 类,但是他里面并没有解引用 (定义一个变量,或者访问它里面的成员),只有源文件 Carer.cpp 解引用了 。

那么这个头文件是不需要导入 .h 的,只需要一个前置声明 ,只有实际调用了 成员函数的源文件需要导入 .h。

11.以项目名为名字空间(),避免符号冲突

在声明和定义外面都套一层名字空间,例如此处我的子项目名是 ,那我就 ::。避免暴露全局的 。

12.依赖另一个子项目,则需要链接他

13.CMake 的 功能

和 C/C++ 的 # 一样,CMake 也有一个 命令。

你写 (XXX),则他会在 这个列表中的所有路径下查找 XXX.cmake 这个文件。

补充:macro 和 的区别

macro 相当于直接把代码粘贴过去,直接访问调用者的作用域。这里写的相对路径 和 src,是基于调用者所在路径。

则是会创建一个闭包,优先访问定义者的作用域。这里写的相对路径 和 src,则是基于定义者所在路径。

相当于直接把代码粘贴过去,直接访问调用者的作用域。这里创建的变量和外面共享,直接 set(key val) 则调用者也有 ${key} 这个变量了。

中则是基于定义者所在路径,优先访问定义者的作用域。这里需要 set(key val ) 才能修改到外面的变量。

二、用 寻找系统中第三方库/依赖项配置 1. 命令

常用参数列表一览:

find_package(<PackageName> [version] [EXACT] [QUIET] [CONFIG] [MODULE][REQUIRED] [[COMPONENTS] [components...]][OPTIONAL_COMPONENTS components...]

命令用法举例

find_package(OpenCV)
查找名为 OpenCV 的包,找不到不报错,事后可以通过 ${OpenCV_FOUND} 查询是否找到。find_package(OpenCV QUIET)
查找名为 OpenCV 的包,找不到不报错,也不打印任何信息。find_package(OpenCV REQUIRED)    # 最常见用法
查找名为 OpenCV 的包,找不到就报错(并终止 cmake 进程,不再继续往下执行)。find_package(OpenCV REQUIRED COMPONENTS core videoio)
查找名为 OpenCV 的包,找不到就报错,且必须具有 OpenCV::core 和 OpenCV::videoio 这两个组件,如果没有这两个组件也会报错。find_package(OpenCV REQUIRED OPTIONAL_COMPONENTS core videoio)
查找名为 OpenCV 的包,找不到就报错,可具有 OpenCV::core 和 OpenCV::videoio 这两个组件,没有这两组件不会报错,通过 ${OpenCV_core_FOUND} 查询是否找到 core 组件。

2. 说是找“包”,到底是在找什么?

这个包配置文件(例如.cmake),这个配置文件里包含了包的具体信息,包括动态库文件的位置,头文件的目录,链接时需要开启的编译选项等等。

3.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 目录下)

/usr/lib/cmake/OpenCV/OpenCVConfig.cmake
/usr/lib/cmake/opencv4/OpenCVConfig.cmake
同样都是可以被 find_package(OpenCV REQUIRED) 搜索到的。

/usr/lib/cmake/Qt5/Qt5Config.cmake
/usr/lib/x86_64-linux-gnu/cmake/Qt5/Qt5Config.cmake
/usr/share/cmake/Qt5/Qt5Config.cmake
/usr/lib/Qt5/Qt5Config.cmake
/usr/lib/x86_64-linux-gnu/Qt5/Qt5Config.cmake
/usr/share/Qt5/Qt5Config.cmake
/usr/Qt5/lib/cmake/Qt5/Qt5Config.cmake
/usr/Qt5/lib/x86_64-linux-gnu/cmake/Qt5/Qt5Config.cmake
/usr/Qt5/share/cmake/Qt5/Qt5Config.cmake
/usr/Qt5/lib/Qt5/Qt5Config.cmake
/usr/Qt5/lib/x86_64-linux-gnu/Qt5/Qt5Config.cmake
/usr/Qt5/share/Qt5/Qt5Config.cmake

4.安装在非标准路径的库

以 Qt5 为例,如果你安装在下列标准路径, 能够自动找到。

Linux:/usr/lib/cmake/Qt5/Qt5Config.cmake。

但是假如我的库不是装在这些标准路径,而是我自定义的路径,怎么办?

例如我把 Qt5 安装到了 /opt/Qt5.12.1。

(1) 单次有效。在 configure 阶段,可以从命令行设置:
cmake -B build -DQt5_DIR=”/opt/Qt5.12.1/lib/cmake/Qt5”(2) 全局启用。修改你的 ~/.bashrc 文件添加环境变量:
export Qt5_DIR=”/opt/Qt5.12.1/lib/cmake/Qt5”,然后重启终端。这样以后你每次构建任何项目,find_package 都能自动找到这个路径的 Qt5 包了。(3) 单项目有效。直接在你自己项目的 CMakeLists.txt 最开头写一行:
set(Qt5_DIR ”/opt/Qt5.12.1/lib/cmake/Qt5”)    # 一定要加在最前面!cmake -B build -DQt5_DIR=D:/Qt5   # 只需要第一次指定好,
cmake -B build                                  # 以后第二次运行可以省略!
rm -rf build                                         # 只有清理了 build 以后,
cmake -B build -DQt5_DIR=D:/Qt5   # 才需要重新指定。

补充:类似 Qt 这种亲 Unix 软件,在 Linux 下的目录组织格式

Linux 用户从源码安装 Qt 这种库时,会有一个 -- 选项,指定安装的根路径。

/usr/include/qt/QtCore/qstring.h(实际的头文件,对应 Qt5::Core)
/usr/lib/libQt5Core.so(实际的动态库文件,对应 Qt5::Core)
/usr/lib/libQt5Core.a(实际的静态库文件,对应 Qt5::Core)/usr/lib/cmake/Qt5/Qt5Config.cmake(包配置文件,用于 find_package)

/usr/local/lib/cmake/Qt5/Qt5Config.cmake

/opt/myqtroot/lib/cmake/Qt5/Qt5Config.cmake这种非常规安装,就需要设置变量 -DQt5_DIR=/opt/myqtroot/lib/cmake/Qt5 

补充:亲 Unix 软件从源码安装的通用套路

构建系统:

./configure --prefix=/usr --with-some-options    # 生成 Makefile(这个 configure 脚本由 Autoconf 生成)
make -j 8                # 8 核心编译,生成 libtest.so
sudo make install   # 安装,拷贝到 /usr/lib/libtest.so

CMake 构建系统:

cmake -B build -DCMAKE_INSTALL_PREFIX=/usr -DWITH_SOME_OPTIONS=ON  # 生成 Makefile
cmake --build build --parallel 8                  # 8 核心编译,生成 libtest.so
sudo cmake --build build --target install    # 安装,拷贝到 /usr/lib/libtest.so注:如果 -DCMAKE_INSTALL_PREFIX=/usr/local 则会拷贝到 /usr/local/lib/libtest.so

5.如果第三方库发懒,没有提供 文件怎么办?

绝大多数常用 C++ 库都提供了 CMake 支持(即使他们本身不一定是用 CMake 构建的)

/usr/lib/cmake/Boost-1.80.0/BoostConfig.cmake
/usr/lib/cmake/opencv4/OpenCVConfig.cmake
/usr/lib/cmake/Qt5/Qt5Config.cmake这些 Config 文件都是由第三方库负责安装到 /usr/lib/cmake。

也有少数不听话的库,官方不提供 CMake 支持,即安装时不自带 文件。

/usr/share/cmake/Modules/FindCUDAToolkit.cmake
/usr/share/cmake/Modules/FindPython.cmake

那么如果有个不太热门的第三方库没提供包配置文件,CMake 也没提供包搜索文件,我们该如何找到他?

虽然 文件通常风格比较统一,都是 XXX::xxx 这种格式。但是不同的 Find 文件,特别是这种网上志士仁人自己编写的文件,风格可能千差万别(没办法,毕竟不是官方的支持嘛),很多都还是古代 CMake 的用法,例如 ${}。

set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake;${CMAKE_MODULE_PATH}")
这样你之后的 find_package(XXX) 就会用你下载的这个 FindXXX.cmake 去找包了。

补充:自己编写一个.cmake

CMAKE 编写 .cmake

cmake函数: nt

6.现代CMake的 VS 古代CMake的

.cmake(现代)

现代 CMake 模块化项目管理指南_现代 CMake 模块化项目管理指南_

.cmake(古代)

参考:

Cmake之深入理解()的用法

不管是 Find 类还是 类,一定要打开相应的 cmake 文件看看注释,才能确定他是古代风格还是现代风格。

古代

find_package(XXX)
if (NOT XXX_FOUND)message(FATAL_ERROR “XXX not found”)
endif()
target_include_directories(yourapp ${XXX_INCLUDE_DIRS})
target_link_libraries(yourapp ${XXX_LIBRARIES})

现代

find_package(XXX REQUIRED COMPONENTS xxx)
target_link_libraries(yourapp XXX::xxx)

find_package(CURL REQUIRED)结果:
wangji@DESKTOP-9TAUMH9:~/code/my_course/gittee_mycourese/xiaopenlaoshi/course/16/00$ cmake -B build
-- Found CURL: /usr/lib/x86_64-linux-gnu/libcurl.so (found version "7.81.0")  

7. 的两种模式

模式

只会寻找 FindTBB.cmake,搜索路径:
${CMAKE_MODULE_PATH}(默认为 /usr/share/cmake/Modules)

模式

只会寻找 TBBConfig.cmake,搜索路径:
${CMAKE_PREFIX_PATH}/lib/cmake/TBB(默认为 /usr/lib/cmake/TBB)
${TBB_DIR}$ENV{TBB_DIR}

(TBB )

不指定则两者都会尝试,先尝试 FindTBB.cmake,再尝试 TBBConfig.cmake。

8.关于 vcpkg 的坑

比如 ,他不提供 文件,需要我们自己手写(或抄别人开源项目里的)个 Find 文件,用起来很不方便。

9.语义版本号( )系统

软件行业记录版本迭代普遍采用的是一套所谓的语义版本号系统,英文简称 。

通常他的格式是三个用点分隔开来的十进制数字:<major>.<minor>.<patch>

<major>.<minor>.<patch>.<tweak>。
并且如果你写 0.6.8 他会自动帮你把多余的 tweak 默认为 0, 
也就是说 0.6.8 == 0.6.8.0
1.2 == 1.2.0 == 1.2.0.0。

project(CppCMakeDemo LANGUAGES CXX VERSION 1.2.3)
message("www: ${CppCMakeDemo_VERSION}, ${CMAKE_PROJECT_VERSION}")比较版本号时,可以用 if (${XXX_VERSION} VERSION_LESS 3.1.0) 判断大小
或者if (CppCMakeDemo_VERSION VERSION_LESS 3.1.0) 

10. 命令指定版本

如果没写全,则没写的部分默认为 0。例如下列三者等价:

find_package(OpenCV 2 REQUIRED)
find_package(OpenCV 2.0 REQUIRED)
find_package(OpenCV 2.0.0 REQUIRED)

11.使用非标准路径库总结

安装 TBB:

cd tbb
./configure --prefix=/opt/tbbinstalldir
make -j 8
sudo make install

在你的项目里使用 TBB:

cd yourapp
cmake -B build -DTBB_DIR=/opt/tbbinstalldir/lib/cmake/TBB
cmake --build build --parallel 8

.txt 这样写:

project(yourapp)
add_executable(yourapp yourmain.cpp)
find_package(TBB CONFIG REQUIRED COMPONENTS tbb)
target_link_libraries(yourapp PUBLIC TBB::tbb)

14.常见问题

使用古代CMake

target_link_libraries(yourapp ${XXX_LIBRARIES})
target_include_directories(yourapp ${XXX_INCLUDE_DIRS})

问题:

打印检查一下这两个变量是不是空的:message(!!!!!!${XXX_INCLUDE_DIRS})

现代CMake和

标准方法:

find_package(spdlog REQUIRED)
target_link_libraries(yourapp PUBLIC spdlog::spdlog)

方法:

add_subdirectory(spdlog)  # 需要下载好他们的源码放到你的根目录下
target_link_libraries(yourapp PUBLIC spdlog)

其他 1.变量,函数,类与

由于函数和类的定义和声明可以用{}区分,所以C语言开发者偷懒,就省略了,其实加上更好。

//声明
extern in a;
int f();
struct C{};//extern "C"是绑定在一起的关键字
extern "C" int cf();//定义
int a;
int f() {
return 0;
};
struct C{};
extern "C" int cf()
{return 0;}

2.# 和# "/Carer.h"区别

# 符合Cmake规范,不会搜索当前目录,只会搜索里面指定的目录:( );

# "/Carer.h"努夫和Cmake规范,会搜索当前目录,( );也会起作用;

参考:

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了