chapter03 - first cmake project

cmake_minimal_required

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

Note

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

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

project

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

默认的语言是 C 和 CXX 两个(不包含 CUDA)。对于 C++ 项目设置语言为 CXX 的好处是 cmake 不再会为所有语言检查编译器的存在情况,配置时间会短一些(对于 hello-world 项目,在 6 core Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz + wsl2 中 CXX 的配置时间是 200 ms,C 和 CXX 的配置时间是 345 ms)。

也可以用 enable_language 添加语言,这不会设置任何元变量。

最小项目

cmake_minimum_required(VERSION 3.20.0)
# 默认语言是 C 和 CXX
project(hello
  VERSION 1.0
  LANGUAGES CXX)
add_executable(hello hello.cpp)

添加子目录作为项目的一部分

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

EXCLUDE_FROM_ALL 可以使得子目录的目标不作为默认目标加入本列表(比如 examples 和 tests)。这样就能尽可能少地编译。如果要用这个,一般情况下说明这个子目录也应该有自己的 project 命令,可以被独立地编译。

推荐的工程结构

  • doc 目录是构建文档的代码,而不是文档本身。
  • extern 是第三方。
  • cmake 放宏和函数、find_modules、脚本。
  • src/libn 应该也可以是纯头文件的库吧?
  • test “contains code for automated tests”
  • 这里没有单独的 include,是把 include 放到源码各个 target 自己的目录中去了。
  • 有人建议把 src 分成 src 和 lib,用来区别可执行文件和库,但对 cmake 来说他们都是 target,没有区别。

书上给的例子中:

  • 每个目标的 target_link_libraries 都是用的 PRIVATE,而 target_include_directories 都是用的 PUBLIC
  • app1 除了链接其内部的 lib3 之外,还链接了 lib1 和 lib2。尽管 lib1 和 lib2 在外层。
  • lib3 还可以提供对外的头文件。(怎么用?直接 target_include_directory 会将 private 也 include 进来,除非只 include lib3 这一个文件夹,但是这样又会没有层级。)

总体结构:

书上用的是 add_subdirectory(src bin),但是 src 里面还有 lib,这样不妥吧。

如果想要把空目录加入 git,可以在目录下创建一个 .keep 空文件。

和环境有关的变量

系统信息

CMAKE_SYSTEM_{NAME,VERSION}

交叉编译

CMAKE_HOST_* 和主机相关。其他的则是和 target 机器相关。

默认是在和主机相同的条件下编译,要进行交叉编译需要设置 CMAKE_SYSTEM_{NAME,VERSION}

缩写变量

更方便地查询系统信息,每个都是可以直接用在 if 中的条件。

ANDROID,APPLE,CYGWIN,UNIX,IOS,WIN32,WINCE,WINDOWS_PHONE
CMAKE_HOST_{APPLE,SOLARIS,UNIX,WIN32}

UNIX 对于 Linux、macOS、Cygwin 都是真。

WIN32/CMAKE_HOST_WIN32 对于 32/64 位的 Windows 和 MSYS 都是真。

检索其他系统配置

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

cmake_host_system_information 可以查更多的配置。由于检查配置是花时间的,所以不显式指定就不会去检查这些。

比如:

  • FQDN(fully qualified domain name)在 wsl2 竟花费 4 秒去检查,但结果和 HOSTNAME 相同。
  • 在 wsl2 上检出的虚拟内存是 2G,物理内存是 <8G(可能有一些用于缓存和其他部分的内存);在 Windows 上两者分别约等于 32G 和约等于 16G。

获取指针长度

CMAKE_SIZEOF_VOID_P

大小端

CMAKE_<LANG>_BYTE_ORDER

在我的电脑上值为 LITTLE_ENDIAN

设置语言标准

小三行。

set(CMAKE_CXX_STANDARD 20)          # 偏好 C++20
set(CMAKE_CXX_STANDARD_REQUIRED ON) # 让偏好成为强制
set(CMAKE_CXX_EXTENSIONS OFF)       # 禁用扩展,现在不能用 gnu++20 只能用 c++20
  1. 尽管只有第一行时,标准只是偏好而不是强制,但是也必须是一个合法标准。比如 21 是无效值。
  2. 和直接添加 CMAKE_CXX_FLAGS 相比,这样的设置方式是跨平台的,换编译器也可用。

如果对单个目标想要更改语言标准:

set_property(TARGET Standard CXX_STANDARD 23)

(C++23 在 cmake 3.20 中被支持。)

链接时优化

首先要检查编译器是否支持,支持的时候才打开,否则可能出错。(和直接加选项相比,这样能跨平台。)

include(CheckIPOSupported)
check_ipo_supported(RESULT supported)
if(supported)
  set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)
endif()

编译标志

CMAKE_CXX_COMPILE_FEATURES 列表。

可以用 list(FIND) 方法去查找标志。

try_compile 和 try_run

可以在 configure 的阶段尝试编译和运行程序并获得结果。

禁用 in-source-builds

在 project 下面加上:

if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
  set(msg "
CMake is about to abort. To clean the workspace, run:
---
cmake -E rm -rf \"${CMAKE_CURRENT_LIST_DIR}/CMakeFiles\"
cmake -E rm -rf \"${CMAKE_CURRENT_LIST_DIR}/CMakeCache.txt\"
---
")
  message(${msg})
  message(FATAL_ERROR "In-source build is not allowed")
endif()

There is a difference between these variables. CMAKE_SOURCE_DIR does indeed refer to the folder where the top-level CMakeLists.txt is defined. However, PROJECT_SOURCE_DIR refers to the folder of the CMakeLists.txt containing the most recent project() command.

虽然网上提到有一些标志位可以打开,但截至 3.20.0 还没有正式的官方说明。

即便是有了 FATAL_ERROR,一些文件仍然会被写入到构建目录,这一点还解决不了。只能打印出来要删的文件让用户手动删除一下。

结果:

# 如果前面有配置过程,这里会多一个换行

CMake is about to abort. To clean the workspace, run:
---
cmake -E rm -rf "/data/modern-cmake/examples/chapter03/06-toolchain/CMakeFiles"
cmake -E rm -rf "/data/modern-cmake/examples/chapter03/06-toolchain/CMakeCache.txt"
---
CMake Error at CMakeLists.txt:12 (message):
  In-source build is not allowed


-- Configuring incomplete, errors occurred!