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
- 尽管只有第一行时,标准只是偏好而不是强制,但是也必须是一个合法标准。比如 21 是无效值。
- 和直接添加
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 recentproject()
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!