Parallel201-1.现代CMake进阶指南

Parallel201 笔记+课后作业,课程传送门:

命令行小技巧

古代 CMake 指的是 CMake2.x 版本,现代 CMake 已经到了 3.x 版本,许多地方得到了简化

PS:本文提到的构建一般指编译

CMake 命令行构建流程(-B)

CMake 2.x

1
2
3
4
5
6
mkdir -p build
cd build
cmake .. #依据上层目录的CMakeLists.txt在build目录下生成makefile、.slu等
make -j4 #make根据makefile四核心并行构建可执行文件
sudo make install #make根据makefile将生成的文件放到指定的位置
#make clean 可以清除make生成的构建文件

CMake 3.x

1
2
3
cmake -B build           #在build目录下生成makefile、.slu等
cmake --build build -j4 #make -C build -j4, build为makefile所在目录, windows上会调用别的构建系统
cmake --build build --target install

指定配置变量(-D、-G)

cmake -B build 为配置阶段, 生成 makefile、.slu, 在这个阶段可以指定一些生成规则

  • -D:设置缓存变量, 下一次 cmake -B build 时, 之前的 -D 添加仍然会被保留

格式: -D VAR=VAL

1
2
3
4
5
#使用Release模式构建
cmake -B build -DCMAKE_BUILD_TYPE=Release

#指定安装路径/opt/openvdb-8.0(会安装到 /opt/openvdb-8.0/lib/libopenvdb.so)
cmake -B build -DCMAKE_INSTALL_PREFIX=/opt/openvdb-8.0
  • -G:指定使用的构建系统
1
2
#使用Ninja来进行构建
cmake -GNinja -B build

Ninja 是现代化的构建系统, 跨平台, 可多核心构建, 速度优于 make;但是 CUDA toolkit 在 windows 上只允许用 MSBuild 进行构建

添加源文件

目录结构:

1
2
3
--------------------
- main.cpp
- CMakeLists.txt

添加一个可执行文件 main 为构建目标, main 由 main.cpp 构建而成

1
add_executable(main main.cpp)

或者先添加一个可执行文件, 然后添加源文件

1
2
add_executable(main main.cpp)
target_sources(main PUBLIC main.cpp)

多个源文件

目录结构:

1
2
3
4
5
--------------------
- main.cpp
- other.cpp
- other.h
- CMakeLists.txt

一个个手动添加

1
add_executable(main main.cpp other.cpp)

通过变量的方式

1
2
3
add_executable(main)
set(sources main.cpp other.cpp)
target_sources(main PUBLIC ${sources})

main.cpp 中有#include "other.h"所以上面不写 other.h 也是能正常构建的

但是写上更好, 写上之后 VS 的”Header Files”一栏就会出现 other.h 了

Tips:

  • 变量相当于文本替换, 支持嵌套, 且字符串中的${}也会发生替换

GLOB 实现批量添加源文件

使用 GLOB 自动查找当前目录下指定扩展名的文件

GLOB 选项将会为所有匹配查询表达式的文件生成一个文件 list,并将该 list 存储进变量 variable 里

1
2
3
add_executable(main)
file(GLOB sources *.cpp *.h)
target_sources(main PUBLIC ${sources})

上面的 sources 变量只会在第一次 cmake -B build 时被赋值, 之后如果不更改 CMakeLists.txt, 它就一直不变, 即使创建了新的.cpp 文件

为了让它每次 cmake -B build 时都会更新, 加上 CONFIGURE_DEPENDS

1
2
3
add_executable(main)
file(GLOB sources CONFIGURE_DEPENDS *.cpp *.h)
target_sources(main PUBLIC ${sources})

另外 GLOB 只会搜索当前目录下的.cpp、.h, 如果源文件在子目录就不会被找到

需要写子目录的查询表达式

1
2
3
add_executable(main)
file(GLOB sources CONFIGURE_DEPENDS *.cpp *.h 子目录/*.cpp 子目录/*.h)
target_sources(main PUBLIC ${sources})

或者使用 GLOB_RECURSE, 这样会找到子目录下的.cpp、.h

但也会找到 build 目录下 cmake 构建时用于测试的临时.cpp 文件, 所以推荐将源码全放在 src 文件夹下, 然后使用 src/.cpp、src/.h

1
2
3
4
5
6
7
#add_executable(main)
#file(GLOB_RECURSE sources CONFIGURE_DEPENDS *.cpp *.h)
#target_sources(main PUBLIC ${sources})

add_executable(main)
file(GLOB_RECURSE sources CONFIGURE_DEPENDS src/*.cpp src/*.h)
target_sources(main PUBLIC ${sources})

文件名查询表达式与正则表达式类似。如果为一个表达式指定了 RELATIVE 标志,返回的结果将会是相对于给定路径的相对路径。

文件名查询表达式的例子:

1
2
3
*.cxx      - 匹配所有后缀名为 cxx 的文件
*.vt? - 匹配所有后缀名为 vta,...,vtz 的文件
f[3-5].txt - 匹配 f3.txt, f4.txt, f5.txt 文件

aux_source_directory 自动收集需要的文件后缀名

1
2
3
4
add_executable(main)
aux_source_directory(. sources)
aux_source_directory(子目录 sources)
target_sources(main PUBLIC ${sources})

项目配置变量

CMAKE_BUILD_TYPE, 项目构建类型

1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.15)

project(hellocmake LANGUAGES CXX)

set(CMAKE_BUILD_TYPE Release)

add_executable(main main.cpp)

项目构建类型与对应的编译器参数(以 g++为例)

  • Debug: -O0 -g

  • Release: -O3 -DNDEBUG

  • MinSizeRel: -Os -DNDEBUG

  • RelWithDebInfo: -O2 -g -DNDEBUG

NDEBUG 是一个 c 语言的一个宏, 如果#define NDEBUG 将会移除 assert(), -DNDEBUG 就相当于定义这个宏

这个写法就跟 cmake -B build -DVAR=VAL 一样

Tips: 如何设定变量的默认值

  • 默认情况下, CMAKE_BUILD_TYPE 未指定的话会默认为 Debug, 若想默认为 Release:

    1
    2
    3
    if (NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
    endif()

project 初始化, 项目名

project 可以命名项目, 并把当前目录作为项目根目录, 并提供一系列可用的变量

  • PROJECT_NAME: 当前项目名

  • PROJECT_SOURCE_DIR: 当前项目源码目录, 表示最近一次调用 project 的 CMakeLists.txt 所在的源码目录

  • PROJECT_BINARY_DIR: 二进制文件的生成目录, 由cmake -B指定, PROJECT_BINARY_DIR 会变成指定的目录路径

  • PROJECT_IS_TOP_LEVEL: 可以判断当前项目是不是顶层的项目, BOOL 类型

  • CMAKE_PROJECT_NAME: 根项目名

  • CMAKE_SOURCE_DIR: 根项目源码目录

  • CMAKE_BINARY_DIR: 根项目二进制文件生成目录

  • CMAKE_CURRENT_SOURCE_DIR: 当前 CMakeLists.txt 所在源码目录

  • CMAKE_CURRENT_BINARY_DIR: 当前 CMakeLists.txt 构建的二进制文件生成目录

详情:

可以在子模块里使用 project 命令, 将当前目录作为一个独立的子项目, 这样一来 PROJECT_SOURCE_DIR 就会是子模块的源码目录而不是外层了。CMake 会认为这个子模块是个独立的项目, 会额外做一些初始化, 他的构建目录 PROJECT_BINARY_DIR 也会变成 build/<源码相对路径>, 如在 MSVC 上会看见 build/mylib/mylib.vcxproj 的生成。

project 初始化, LANGUAGES 字段

指定项目所用的语言

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.15)

project(hellocmake LANGUAGES CXX)

#或者, 这样可以把enable_language放到if里, 某些情况下才启用某些语言
#project(hellocmake LANGUAGES NONE)
#enable_language(CXX)

add_executable(main main.cpp)

支持的语言:

  • C:C 语言
  • CXX:C++语言
  • ASM:汇编语言
  • Fortran:古老的编程语言
  • CUDA:英伟达的 CUDA(3.8 版本新增)
  • OBJC:苹果的 Objective-C(3.16 版本新增)
  • OBJCXX:苹果的 Objective-C++(3.16 版本新增)
  • ISPC:一种因特尔的自动 SIMD 编程语言(3.18 版本新增)
  • 如果不指定 LANGUAGES,默认为 C 和 CXX。

project 初始化, VERSION 字段

1
2
3
4
5
cmake_minimum_required(VERSION 3.15)

project(hellocmake LANGUAGES CXX VERSION 1.2.3)

add_executable(main main.cpp)
  • project(项目名 VERSION x.y.z)可以把当前项目的版本号设定为 x.y.z
  • 之后可以通过 PROJECT_VERSION 来获取当前项目的版本号
  • PROJECT_VERSION_MAJOR 获取 x(主版本号)
  • PROJECT_VERSION_MINOR 获取 y(次版本号)
  • PROJECT_VERSION_PATCH 获取 z(补丁版本号)

project 初始化, 其他字段

定义PROJECT_DESCRIPTIONPROJECT_HOMEPAGE_URL变量

定义项目名_VERSION项目名_SOURCE_DIR项目名_BINARY_DIR变量

设置 C++标准

为了跨平台不要使用set(CMAKE_CXX_FLAGS -std=c++17)这种方式

1
2
3
4
5
6
7
8
9
10
cmake_minimum_required(VERSION 3.15)

#使用C++17
set(CMAKE_CXX_STANDARD 17)
#如果编译器不支持C++17就报错, 设置为OFF, 不支持17的话会按14来编译
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#为了跨平台关闭GCC扩展语法
set(CMAKE_CXX_EXTENSIONS OFF)

project(hellocmake)

cmake_minimum_required, 指定最低所需的 CMake 版本

1
2
3
4
5
6
7
#最低需要cmake版本为3.15
#cmake_minimum_required(VERSION 3.15)

#需要cmake版本为3.15到3.20
cmake_minimum_required(VERSION 3.15...3.22)

project(hellocmake)

使用cmake --version指令可以获取当前 cmake 版本号

一份标准 CMakeLists.txt

CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
cmake_minimum_required(version 3.15)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

project(zeno LANGUAGES C CXX)

if (PROJECT_BINARY_DIR STREQUAL PROJECT_SOURCE_DIR)
message(WARNING "The binary directory of CMake cannot be the same as source directory!")
endif()

if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()

# Set to True when the target system is Windows, including Win64.

if (WIN32)
# Add -D define flags to the compilation of source files.eg: gcc -DNOMINMAX
add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES)
endif()

# Set to true when the compiler is some version of Microsoft Visual C++ or another compiler simulating the Visual C++ cl command-line syntax.
if (NOT MSVC)
find_program(CCACHE_PROGRAM ccache)
if (CCACHE_PROGRAM)
message(STATUS "Found CCache: ${CCACHE_PROGRAM}")
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM})
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PROGRAM})
endif()
endif()
  1. 如果生成二进制文件的文件夹和存储源码的文件夹是同一个文件夹, 那么将会产生警告
  2. 默认使用 Release 模式构建
  3. 标准库在头中定义了两个模板函数 std::min() 和 std::max()
    但在 windows 上无法使用,因为 windows 的<windows.h>中定义了两个传统的宏 min/max, 所以用-DNOMINMAX 来去除定义
  4. 如果可以的话使用 ccache 来加速编译

CMake 常见变量——Project 和 CMake 相关信息

链接库文件

链接库可以简单理解为里面包含了一系列的函数, 你可以方便的在自己的项目里调用他们, 只要引入了对应的头文件(函数声明)

作者

Meow-2

发布于

2022-03-12

更新于

2022-11-28


评论