Parallel201-1.现代 CMake 进阶指南

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

命令行小技巧

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

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

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

CMake 2.x

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

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

#使用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:指定使用的构建系统
#使用Ninja来进行构建
cmake -GNinja -B build

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

添加源文件

目录结构:

--------------------
  - main.cpp
  - CMakeLists.txt

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

add_executable(main main.cpp)

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

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

多个源文件

目录结构:

--------------------
  - main.cpp
  - other.cpp
  - other.h
  - CMakeLists.txt

一个个手动添加

add_executable(main main.cpp other.cpp)

通过变量的方式

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 里

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

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

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

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

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

#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 标志,返回的结果将会是相对于给定路径的相对路径。

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

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

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

add_executable(main)
aux_source_directory(. sources)
aux_source_directory(子目录 sources)
target_sources(main PUBLIC ${sources})

项目配置变量

CMAKE_BUILD_TYPE, 项目构建类型

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:

    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 字段

指定项目所用的语言

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 字段

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)这种方式

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 版本

#最低需要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

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. 标准库在<algorithm>头中定义了两个模板函数 std::min() 和 std::max() 但在 windows 上无法使用,因为 windows 的<windows.h>中定义了两个传统的宏 min/max, 所以用-DNOMINMAX 来去除定义
  4. 如果可以的话使用 ccache 来加速编译

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

链接库文件

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

Games101-Lecture 03 Transformation
Parallel101-1.学 C++从 CMake 学起