0%

cmake_minimal_required

cmake_minimal_required(VERSION xxx) 是项目都应该有的说明。因为它指定了最小的版本,而且还会隐式去调用 cmake_policy,这样即便 cmake 版本更新了,policy 没变,旧项目仍然可以正常构建。

Note

cmake policy 是每次 cmake 有不向后兼容的改动时增加的标志。

VERSION 可以是一个范围 a...b,如果只有一个值则是最小版本要求。

project

project 命令用来设置项目名、版本、还有语言等。

创建 target

三种方式:

add_executable     # 默认在 ALL 中
add_library        # 默认在 ALL 中
add_custom_target  # 默认不在 ALL 中
# add_custom_command 是创建文件的方式,但是不是创建 target

add_library: https://cmake.org/cmake/help/latest/command/add_library.html#object-libraries

  • Normal library: 如果不指定 STATIC/SHARED/MODULEadd_library 会根据 BUILD_SHARED_LIBS 变量去决定是否使用 SHARED 还是 STATIC
  • Object library: 生成目标文件。
  • Interface library:不生成文件。用来打包传播属性。
  • Imported library:IMPORTED 属性和 STATIC 等是可以叠加的。IMPORTED 库是已经被编译好的库,不需要另外编译,但是需要用 set_target_property 来设置其位置。
  • Alias library

add_custom_target 可以有多条命令,而且不一定有产物。add_custom_target 创建的 target 是默认不 ALL 的!和 add_custom_command 的对比看后文。

我认为的依赖有两种:

  1. 文件依赖(sources)既有同级依赖(比如 add_custom_commandDEPENDS 参数),又有 targets 到 sources 的依赖。
  2. target 依赖是一种 targets 之间的同级依赖关系。

Target 依赖

创建 target 可以用 add_executable/add_library/add_custom_target。前面两个都默认包含在 all 中,而最后一个默认不包含在 all 中。此外,add_custom_target 如果给的是命令,而不是依赖文件,则创建的 targets 永远处于“需要被构建”的状态;如果给的是文件,则就只是文件依赖。

可以用 add_dependencies 来创建 targets 之间的依赖。

文件依赖

如果 target 或者 command(由 add_custom_command 创建)依赖一个源文件,这个文件找不到、但是又被标记了 GENERATED 属性,则会想办法去生成这个文件。

Initial configuration

target_sources              # 追加源码
target_include_directories
target_compile_definitions
target_compile_options      # 不能跨平台
target_compile_features     # 对编译器要求某种特性
target_precompile_headers
  • target_sources 中添加了头文件是会被过滤掉的,会形成依赖,但是不会真正被编译。
  • target_compile_features 的特性非常多。一般也就只用 cxx_std_{98,11,14,17,20,23} 这种语言标准,而不是具体的标准。

Preprocessor configuration

target_compile_definitions 能够自动去除多加的 -D,不过最好还是不要加,因为这个命令本来就是用于跨平台的,不加风格更统一。

通过 execute_process 和 宏定义,可以将当前的最新 commit 传入源码,这样源码就能自己计算最新版本,而不需要每次都手动更改!然后提交的代码通过 CD 就能够自动构建了。特别是在用户只有产品时,能够获得具体的 commit sha1 能够很方便定位出问题的版本。

程序分段

  • .text section: Machine code, with all the instructions to be executed by the processor

  • .data section: All values of the initialized global and static objects (variables)

  • .bss section: All values of the uninitialized global and static objects (variables), which will be initialized to zero on program start

  • .rodata section: All values of the constants (read-only data)

  • .strtab section: 常量字符串的表

  • .shstrtab section: 每个 section 的名称字符串的表

    可执行文件有一些差异,其中之一就是 program header,它描述了创建进程镜像的信息。

库的分类

CMake 支持的有 STATIC, SHARED, MODULE

MODULE

不应该将 MODULE 链接到其他文件。成为 MODULE 的库需要在运行时被显式链接。

Best option: find_package

https://cmake.org/cmake/help/latest/command/find_package.html

有三种工作模式。

Module mode

Find<PackageName>.cmake

主要是包外提供,比如 CMake、操作系统、或者写当前工程的人提供的试探性地去搜索库的方式。先找 CMAKE_MODULE_PATH,然后就是 cmake 的安装目录。

我们实验室项目不少路径都是硬编码的,感觉写 Find Module 会更合适。

CTest

基本用法

要使用 CTest 和相关的东西需要调用 enable_testing

ctest 是要在 build tree 执行的。

ctest 也能直接编译和运行(两步合为一步),但是要手动提供测试命令(有点奇怪)。

Dry run:

说明

这一章节是作者将一些分析工具以函数或内置支持的形式自动化地加入到了 CMake 构建工程的过程中了。

格式化

clang-format

静态检查

  1. clang-tidy
  2. cpplint(按照 Google Coding Style 检查)
  3. cppcheck
  4. include-what-you-use
  5. link what you use(CMake 内置)

静态检查受到 CMake 的直接支持:

All we need to do is set an appropriate target property to a semicolon-separated list containing the path to the checker’s executable, followed by any command-line options that should be forwarded to the checker.

  • <LANG>_CLANG_TIDY
  • <LANG>_CPPCHECK
  • <LANG>_CPPLINT
  • <LANG>_INCLUDE_WHAT_YOU_USE
  • LINK_WHAT_YOU_USE

Adding Doxygen to your project

安装必要软件包:

apt-get install doxygen graphviz

然后就能在 CMake 中引入 Doxygen 包并使用 doxygen_add_docs

Since CMake 3.9, we can use the doxygen_add_docs() function from FindDoxygen find-module, which sets the documentation target up.

注意 FindDoxygen module 推荐使用方式是 find_package,虽然可以 include 但是不推荐。

doxygen 用的是 Javadoc 的格式。

Exporting without installation

使用 export 命令可以创建导出文件,然后其他工程只要 include 这个导出文件就能使用当前这个包中的 target,而不需要先将这个包用 install 安装在系统里。

export(TARGETS [target1 [target2 [...]]]
      [NAMESPACE <namespace>] [APPEND] FILE <path>
      [EXPORT_LINK_INTERFACE_LIBRARIES]

NAMESPACE is recommended as a hint, stating that the target has been imported from other projects.

APPEND tells CMake that it shouldn’t erase the contents of the file before writing. 也就是说本身会按照 FILE 来覆写文件,但是 APPEND 使得写入方式变成追加。

EXPORT_LINK_INTERFACE_LIBRARIES will export target link dependencies (including imported and config-specific variants).

例子:

cmake_minimum_required(VERSION 3.20.0)
project(ExportCalcCXX)
add_subdirectory(src bin)
set(EXPORT_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake")
export(TARGETS calc
  FILE "${EXPORT_DIR}/CalcTargets.cmake"
  NAMESPACE Calc::
)
...