CMake Tutorial (3.30-rc3版) 练习和点评

news/2024/10/3 19:12:38

CMake Tutorial (3.30-rc3版) 练习和点评

CMake 官方文档提供了 CMake Tutorial, 目前最新版是 CMake-3.30-rc3, 有12个Step供用户练习。 CMake Tutorial 是从 CMake 3.16 版本开始能从官方网页找到, 并且每一版都有改进 Tutorial 内容。

作为有实际C/C++项目中大幅使用 CMake 构建经验的程序员, 我认为这套教程不适合入门, 有一定 CMake 经验后可以尝试练习, 但目前的教程内容主次不当, 在一些细节上过于精雕细琢, 而基本的软件包发布流程迟迟没有走通, 让人难以捉摸。

Anyway,总的来说有一些收获, 也有很多不足, 简单记录一下。

目录
  • CMake Tutorial (3.30-rc3版) 练习和点评
    • 介绍
    • Step 1: 基础起点
    • Step 2: 创建库
    • Step 3: 添加库的使用需求
    • Step 4: 生成器表达式
    • Step 5: 安装
      • 5.1 基本安装
      • 5.2 安装到带有版本信息的目录
      • 5.3 安装 export 相关文件并适配
      • 5.4 导入安装的包
    • 其他/补充/吐槽

介绍

这份教程使用的源代码是在 cmake-3.30.0-rc1-tutorial-source.zip 中, 每个步骤对应一个子目录, 提供了起点代码。 这份教程里的例子是逐步递进的, 也就是说每个 step 的初始代码, 都是前一个 step 的完整解决方案。

Step 1: 基础起点

本节共有12个TODO, 按顺序逐一实现即可, 包含了:

  • 创建项目
  • 指定项目版本号
  • 指定 C++ 标准
  • 创建可执行目标
  • 指定版本号
  • 通过 configure 文件使用版本号
  • C++ 代码中使用版本号

文档里也提到了一个细节,尽可能遵循:

cmake 命令, 应当使用小写而不是大写, 更不是混合大小写

执行构建, 运行:

cd Step1
cmake -S . -B build
cmake --build build
.\build\Debug\Tutorial.exe 42

个人认为版本号、 configure 文件, 初学者可以跳过, 实际项目中遇到使用的并没有很多。

Step 2: 创建库

本节共有14个TODO, 坦白说如果让初学者一次完全学会和写出, 还是有难度。

  • 使用了 add_library() 创建库
  • 使用了 add_subdirectory() 引入子目录
  • 使用了 target_include_directories() 设置包含目录
  • 使用了 target_link_libraries() 设置链接库
  • 使用了 option() 定义选项变量
  • 使用了 target_compile_defitions() 定义 target 专属(私有)宏
  • 在 C/C++ 代码中, 使用宏区分不同代码

个人认为小白用户第一次使用 CMake 时, 最多掌握本节50%左右的内容。

Step 3: 添加库的使用需求

仅仅设定头文件包含目录、 链接库, 比较粗放, 更好的控制, 则是设定如下内容:

  • target_compile_definitions()
  • target_compile_options()
  • target_include_directories()
  • target_link_directories()
  • target_link_options()
  • target_precompile_headers()
  • target_sources()
    上述常见函数, 除了设定具体的“需求”, 还设定了“传递属性”(transitive property),也就是 PUBLIC/PRIVATE/INTERFACE。 实际项目中, 很多初级算法工程师缺乏“传递属性”的概念, 依赖关系永远是平铺式的, 而不是树状的。 平铺式依赖关系的问题在于, 没有做抽象层级划分, 也就是抽象泄漏, 或者说, 一锅粥乱炖瞎几把搞。

这一节, 学到的第一个技巧是, 创建的 INTERFACE 库可以没有任何源文件, 然后能为它设定属性:

add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)

由于没有任何源文件, 生成的 .sln 中并不会存在 tutorial_compiler_flags 的 project:

而在使用 tutorial_compiler_flags 这一链接库时, TODO 5~7 描述的有问题:

Link A to B

按我理解是把 A 链接到 B 上, 而官方给的答案则是把 B 链接到 A 上。

# 正确的描述: TODO 5: Link tutorial_compiler_flags to Tutorial
target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_compiler_flags)# 正确的描述: TODO 6: Link tutorial_compiler_flags to SqrtLibrary 
target_link_libraries(SqrtLibrary INTERFACE tutorial_compiler_flags)# 正确的描述: TODO 7: Link tutorial_compiler_flags to MathFunctions
target_link_libraries(MathFunctions INTERFACE tutorial_compiler_flags)

在链接 tutorial_compiler_flags 时, 教程中的 TODO 题目是让我们分别链接到三个 target 上的:

  • SqrtLibrary
  • MathFunctions
  • Tutorial
    显然, 每次链接时的传递属性都是 PRIVATE, 否则只需在 SqrtLibrary 上链接即可。

此时,如果是使用 VS2022, 由于默认是C++14标准, 会影响我们观察到各个 target 上经由依赖关系传递过来的 C++ 标准, 可以尝试改为 C++20 后再观察。

这里还有另外一个坑: 如果改为由 SqrtLibrary 执行 PUBLIC 方式链接 tutorial_compiler_flags,在 build 阶段是很丝滑了, 但在 INSTALL 阶段, 需要让每个 target 对应生成的 xxx-config.cmake 中, 都标记出 C++11 标准吗? 应该也不需要, 也是通过传递方式即可。 但要传递, 就需要好用的包管理器。。。

Step 4: 生成器表达式

这一节是 generator expression 的讲解和练习, 需要先查看 https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html 的内容。

本节内容比较难, 初学者可能没法直接掌握, 包括 generator expression 的形式, 以及 BUILD_INTERFACE 的理解。

困难的原因是,没找到官方专门对于 configure/generate/build/install/export 阶段的讲解。

Step 5: 安装

这一节包含了两个 Exercise, 个人认为不相关, 应该拆分存放:

  • Exercise1:安装 MathFunctions 库, 以及 Tutorial 可执行程序
  • Exercise2: 执行单元测试

5.1 基本安装

教程里缺乏设定 CMAKE_INSTALL_PREFIX, 它默认值是 C:/Program Files (x86)/Tutorial。 我们将它做修改:

set(CMAKE_INSTALL_PREFIX "C:/pkgs/tutorial/1.0)

如果觉得上述设定过于武断, 想要先判断是否定义了 CMAKE_INSTALL_PREFIX 后再设定, 那么需要在 project() 之前判断:

if(DEFINED CMAKE_INSTALL_PREFIX)message(STATUS "[debug] CMAKE_INSTALL_PREFIX is defined: ${CMAKE_INSTALL_PREFIX}")
else()message(STATUS "[debug] CMAKE_INSTALL_PREFIX is not defined")
endif()
project(Tutorial VERSION 1.0)

因为 project() 命令会在没有设定 CMAKE_INSTALL_PREFIX 的前提下填入默认值:

  • c:/Program Files/${PROJECT_NAME} on Windows.
  • /usr/local on UNIX platforms.

然而实际运行, 发现 Windows 下默认的 CMAKE_INSTALL_PREFIX 是 c:/Program Files (x86)/Tutorial 而不是 c:/Program Files/Tutorial, 多了一个 (x86).

实际上最佳写法是这样的:先判断当前 CMAKE_INSTALL_PREFIX 是不是和默认值一样, 如果是,那就改掉:

project(Tutorial VERSION 1.0)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)set(CMAKE_INSTALL_PREFIX "C:/pkg/Tutorial" CACHE PATH "..." FORCE)
endif()

还有一个问题: 安装的库, 没有体现出版本号来:

  • 每个 target 的安装路径都是 ${CMAKE_INSTALL_PREFIX}/Tutorial。 如果足够 modern cmake, install 阶段也应该能分别指定。
  • 使用者怎样区分不同版本的 mathfuncion 库?

使用者目前能在运行或编译阶段区分, 但是, 更好的做法应当能让使用者, 在 cmake configure 阶段, 在 CMakeLists.txt 里, 就能区分不同版本的 mathfuncions 库的版本。

进一步的问题: Tutorial 的版本号是1.0,但是 MathFunctions 库和 SqrtLibrary 库没有版本号。

5.2 安装到带有版本信息的目录

本来是是 Testing, 但是我觉得主题混乱, 改成安装到带有版本信息的目录了。

5.3 安装 export 相关文件并适配

本小结内容源自:

Step 11: Adding Export Configuration

export, 是说专门安装 xxx.cmake 文件, 本节我们安装的是:

  • MathFunctionsTargets.cmake
  • MathFunctionsTargets-debug.cmake
  • MathFunctionsConfig.cmake

其他内容见原版的 Step11.

由于我们选择安装到 带有版本信息的目录, 因此每次出现的 DESTINATION 参数,都要注意手动加一个子目录前缀。

5.4 导入安装的包

这一节是原版教程没有的。 原版教程的问题在于, 详略不当。 既然 find_package() 是 CMake 的一大特色, 那为什么在费了九牛二虎之力完成 “安装” 后, 从来都没使用过安装的包? 再简单也要展示下啊。

其他/补充/吐槽

Installing, 细节还得打磨:

  • 官方默认的 CMAKE_INSTALL_PREFIX 文档写错了, 而且 C 盘的那个目录, 用户是没有权限写入的好吗?
  • 在 project() 命令前和命令后, CMAKE_INSTALL_PREFIX 取值是不一样的,能说说吗?
  • 如果 CMAKE_INSTALL_PREFIX 取得了默认值, 就为用户自定义值, 这个能否在文档里说的更直白写,而不是丢一个链接, 点进去看不懂

Packing, 感觉没必要单独搞一个 cpack 命令。 tar cvf 就够了。

Testing, 感觉讲了又和没讲差不多, ctest 命令还算有点用, 但在 CMakeLists.txt 里写单元测试显然不实用, 是个糟粕。 正经项目显然用 C++ 的单元测试框架如 gtest, catch2。

Export, 前面提到了, 没必要单独放到一个章节说, 它就应当作为 install 的一部分, 只不过是中级的、 高级的, 而不是初级的部分。

给debug库设置postfix,同时编译安装debug和release库, 这是 Windows 平台经常用到的东西, 但可惜, 目前的 CMake Tutorial 写的很烂:

  • 设置的 postfix 值不对劲(不能区分库本是 d 结尾的情况)
  • Debug 和 Release 库同时编译的 Step12, 竟然不支持 Multi-Config, 而 Visual Studio 最广为人使用的 MSBuild 构建方式就是 Multi-Config, 这直接劝退用户了
  • 建议增加 Single-Config 和 Multi-Config 的专门的 Exercise, 毕竟 CMAKE_BUILD_TYPES 和 CMAKE_CONFIGURATION_TYPES 不是同一个东西

Configure_file(), 这个东西虽然说不难吧, 但是放到第一节, 不合适。 先学会走路再学习跳绳, 而不是一上来就让用户学习“双编”的花式跳绳技巧,然后再让用户慢慢学走路。

Generator Expression, 和 Configure_file() 类似, 有过之而无不及。

Usage Requirements, 这个名字就很不清晰, 改名为 transitive property 传递属性会更加直白。 这个也是阳春白雪的东西, 先让人吃口饭别饿死, 恢复体力了再跳绳吧。

Select Static or Shared Library, 这个需求个人觉得是来添乱的, 就应该做成两个包, 一个放到 xxx-static.zip, 一个放到 xxx-shared.zip, 不应该在同一个 build-tree 里生成。

Interface target, 一上来就让用户把 C++ 标准等 flags 放到 Interface target 中, 而不是介绍最广泛使用的 header-only 方式, 重点搞反了, 一点都不实用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/44524.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

多线程-信号量

ManualResetEvent的用法初始化:创建一个ManualResetEvent实例,并设置其初始状态。通常,初始状态可以设置为false(表示事件尚未发生)或true(表示事件已经发生)。例如:ManualResetEvent mre = new ManualResetEvent(false);等待事件:在需要等待事件发生的线程中,调用Wa…

过滤器和拦截器的区别

一、拦截器和过滤器的区别 1、过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。 2、拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理…

typora快捷键配置

typora高亮快捷键配置 (typora的高亮没有默认快捷键, 需要自己添加) 1.激活高亮功能 偏好设置 → markdown → markdown扩展语法 → 勾选高亮 2.添加快捷键 (原教程可参见官网) 首先可将typra的语言显示换成English, 方便后续定义: 偏好设置 → 通用 → 语言 每个软件, 理论上每…

Elasticsearch 系列(七)- 在ASP.NET Core中使用高级客户端NEST来操作Elasticsearch

本章将和大家分享在ASP.NET Core中如何使用高级客户端NEST来操作我们的Elasticsearch。本章将和大家分享在ASP.NET Core中如何使用高级客户端NEST来操作我们的Elasticsearch。 NEST是一个高级别的Elasticsearch .NET客户端,它仍然非常接近原始Elasticsearch API的映射。所有的…

Perfetto分析进阶

一、Perfetto介绍 Perfetto是Android Q中引入的全新下一代平台级跟踪工具,为Android、Linux和Chrome平台提供了一种通用的性能检测和跟踪分析工具集。其核心是引入了一种全新的用户空间到用户空间的跟踪协议,该协议基于protobuf序列化机制将抓取的数据填充到共享内存缓冲区,…

Kali中MSF利用永恒之蓝(复现)

Kali中MSF利用永恒之蓝(复现) 1、进入MSF框架:2、搜索MS17_010漏洞:这里找到了四个模块:0、1、4是漏洞利用模块;2、3是辅助模块,主要探测主机是否存在MS17_010漏洞。 3、利用Auxiliary辅助探测模块对漏洞进行探测(查看下所需要的参数):4、设置要探测的远程目标(两种…

B - Ticket Counter

B - Ticket Counter https://atcoder.jp/contests/abc358/tasks/abc358_b思路 第i个完成的时刻,done[i] 跟第i-1完成时间done[i-1]有关系, 第i个的开始时刻t[i] 大于 done[i-1], done[i] = t[i]+a第i个的开始时刻t[i] 不大于 done[i-1], done[i] = done[i-1]+aCode https…