CMake

CMake

我们使用的cmake版本为3.x 以上。

首先我们熟悉基本的命令:

cmake -B build // 在源码目录用 -B 直接创建 build 目录并生成 build/Makefile

cmake --build build -j4 // 自动调用本地的构建系统在 build 里构建,即:make -C build -j4

sudo cmake --build build --target install // 调用本地的构建系统执行 install 这个目标,即安装

结论:从现在开始,如果在命令行操作 cmake,请使用更方便的 -B 和 --build 命令。

-D 选项:指定配置变量(又称缓存变量)

CMake 项目的构建分为两步:

  • 第一步是 cmake -B build,称为配置阶段(configure),这时只检测环境并生成构建规则, 会在 build 目录下生成本地构建系统能识别的项目文件(Makefile 或是 .sln)
  • 第二步是 cmake --build build,称为构建阶段(build),这时才实际调用编译器来编译代码

在配置阶段可以通过 -D 设置缓存变量。第二次配置时,之前的 -D 添加仍然会被保留。

  • cmake -B build -DCMAKE_INSTALL_PREFIX=/opt/openvdb-8.0设置安装路径为 /opt/openvdb-8.0(会安装到 /opt/openvdb-8.0/lib/libopenvdb.so.

设置构建模式为发布模式(开启全部优化)

  • cmake -B build -DCMAKE_BUILD_TYPE=Release

一般我们使用这个参数比较多。

  • cmake -B build 第二次配置时没有 -D 参数,但是之前的 -D 设置的变量都会被保留(此时缓存里仍有你之前定义的 CMAKE_BUILD_TYPE 和 CMAKE_INSTALL_PREFIX)

-G 选项:指定要用的生成器

众所周知,CMake 是一个跨平台的构建系统,可以从 CMakeLists.txt 生成不同类型的构建系统(比如 Linux 的 make,Windows 的 MSBuild),从而让构建规则可以只写一份,跨平台使用。过去的软件(例如 TBB)要跨平台,只好 Makefile 的构建规则写一份,MSBuild 也写一份。现在只需要写一次 CMakeLists.txt,他会视不同的操作系统,生成不同构建系统的规则文件。那个和操作系统绑定的构建系统(make、MSBuild)称为本地构建系统(native buildsystem)。负责从 CMakeLists.txt 生成本地构建系统构建规则文件的,称为生成器(generator)。

Linux 系统上的 CMake 默认用是 Unix Makefiles 生成器;Windows 系统默认是 Visual Studio 2019 生成器;MacOS 系统默认是 Xcode 生成器。可以用 -G 参数改用别的生成器,例如 cmake -GNinja 会生成 Ninja 这个构建系统的构建规则。Ninja 是一个高性能,跨平台的构建系统,Linux、Windows、MacOS 上都可以用。Ninja 可以从包管理器里安装,没有包管理器的 Windows 可以用 Python 的包管理器安装:pip install ninja事实上,MSBuild 是单核心的构建系统,Makefile 虽然多核心但因历史兼容原因效率一般。而 Ninja 则是专为性能优化的构建系统,他和 CMake 结合都是行业标准了。

cmake -GNinja -B build

性能上:Ninja > Makefile > MSBuild

Makefile 启动时会把每个文件都检测一遍,浪费很多时间。特别是有很多文件,但是实际需要构建的只有一小部分,从而是 I/O Bound 的时候,Ninja 的速度提升就很明显。

1. 最简单的CMake

CMakeLists.txt

cmake_minimum_required(VERSION 3.15)   //要求最小的camke版本
project(hellocmake LANGUAGES CXX)   // 设置项目的名称 使用c++
set(CMAKE_BUILD_TYPE Release)       // 设置我们的构建类型为release 
add_executable(main main.cpp)       // 生成可执行文件

set(CMAKE_BUILD_TYPE Release)

CMAKE_BUILD_TYPE 是 CMake 中一个特殊的变量,用于控制构建类型,他的值可以是

  • Debug 调试模式,完全不优化,生成调试信息,方便调试程序
  • Release 发布模式,优化程度最高,性能最佳,但是编译比 Debug 慢
  • MinSizeRel 最小体积发布,生成的文件比 Release 更小,不完全优化,减少二进制体积
  • RelWithDebInfo 带调试信息发布,生成的文件比 Release 更大,因为带有调试的符号信息, 默认情况下 CMAKE_BUILD_TYPE 为空字符串,这时相当于 Debug。

在Release模式下,追求的是程序的最佳性能表现,在此情况下,编译器会对程序做最大的代码优化以达到最快运行速度。另一方面,由于代码优化后不与源代码一致,此模式下一般会丢失大量的调试信息。

Debug: -O0 -g

Release: -O3 -DNDEBUG

MinSizeRel: -Os -DNDEBUG

RelWithDebInfo: -O2 -g -DNDEBUG

此外,注意定义了 NDEBUG 宏会使 assert 被去除掉。

如何让 CMAKE_BUILD_TYPE 在用户没有指定的时候为 Release,指定的时候保持用户指定的值不变呢。就是说 CMake 默认情况下 CMAKE_BUILD_TYPE 是一个空字符串。因此这里通过 if (NOT CMAKE_BUILD_TYPE) 判断是否为空,如果空则自动设为 Release 模式。大多数 CMakeLists.txt 的开头都会有这样三行,为的是让默认的构建类型为发布模式(高度优化)而不是默认的调试模式(不会优化)。

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

2. 添加message信息

CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(hellocmake)

message("PROJECT_NAME: ${PROJECT_NAME}")
message("PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
message("PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}")
message("CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
message("CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}")
add_executable(main main.cpp)

add_subdirectory(mylib)

main.cpp

#include <cstdio>

int main() {
    printf("Hello, world!\n");
}

mylib/CMakeLists.txt

message("mylib got PROJECT_NAME: ${PROJECT_NAME}")
message("mylib got PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
message("mylib got PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}")
message("mylib got CMAKE_CURRENT_SOURCE_DIR: ${CMAKE_CURRENT_SOURCE_DIR}")
message("mylib got CMAKE_CURRENT_BINARY_DIR: ${CMAKE_CURRENT_BINARY_DIR}")

此时我们的项目目录结构为:

├── CMakeLists.txt
├── main.cpp
└── mylib
    └── CMakeLists.txt

project:初始化项目信息,并把当前CMakeLists.txt 所在位置作为根目录

message: 显示打印信息,这里面有些参数:

  • PROJECT_NAME: 我们指定的project里面的name: hellocmake
  • CMAKE_PROJECT_NAME:根项目的项目名
  • PROJECT_SOURCE_DIR: 当前项目源码路径(存放main.cpp的地方)
  • PROJECT_BINARY_DIR: 当前项目输出路径(存放main.exe的地方)
  • CMAKE_SOURCE_DIR: 根项目源码路径(存放main.cpp的地方)
  • CMAKE_BINARY_DIR:根项目输出路径(存放main.exe的地方)
  • CMAKE_CURRENT_SOURCE_DIR: 表示当前源码目录的位置
  • CMAKE_CURRENT_BINARY_DIR :表示当前输出目录的位置
  • PROJECT_IS_TOP_LEVEL:BOOL类型,表示当前项目是否是(最顶层的)根项目

message可以打印出状态,错误警告等一系列的信息:

  • message(STATUS “…”) 表示信息类型是状态信息,有 前缀
  • message(WARNING “…”) 表示是警告信息
  • message(AUTHOR_WARNING “…”) 表示是仅仅给项目作者看的警告信息, 可以通过 cmake -B build -Wno-dev 关闭
  • message(FATAL_ERROR “…”) 表示是错误信息,会终止 CMake 的运行
  • message(SEND_ERROR “…”) 表示是错误信息,但之后的语句仍继续执行

PROJECT_x_DIR CMAKE_CURRENT_x_DIR 的区别

  • PROJECT_SOURCE_DIR 表示最近一次调用 project 的 CMakeLists.txt 所在的源码目录。CMAKE_CURRENT_SOURCE_DIR 表示当前 CMakeLists.txt 所在的源码目录。
  • CMAKE_SOURCE_DIR 表示最为外层 CMakeLists.txt 的源码根目录。利用 PROJECT_SOURCE_DIR 可以实现从子模块里直接获得项目最外层目录的路径。不建议用 CMAKE_SOURCE_DIR,那样会让你的项目无法被人作为子模块使用。

详见: https://cmake.org/cmake/help/latest/command/project.html

子模块里也可以用 project 命令,将当前目录作为一个独立的子项目

这样一来 PROJECT_SOURCE_DIR 就会是子模块的源码目录而不是外层了。这时候 CMake 会认为这个子模块是个独立的项目,会额外做一些初始化。他的构建目录 PROJECT_BINARY_DIR 也会变成 build/<源码相对路径>。

project 的初始化:LANGUAGES字段

project(项目名 LANGUAGES 使用的语言列表…) 指定了该项目使用了哪些编程语言。

目前支持的语言包括:

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。

**常见问题:LANGUAGES 中没有启用 C 语言,但是却用到了 C 语言

例如: CMakeLists.txt里面是:

cmake_minimum_required(VERSION 3.15)
project(hellocmake LANGUAGES CXX) // 这里启用的是CXX
add_executable(main main.c)   //这里使用的是main.c而不是main.cpp

解决:改成 project(项目名LANGUAGES C CXX) 即可

cmake_minimum_required(VERSION 3.15)
project(hellocmake LANGUAGES C CXX) // 这里启用的是CXX
add_executable(main main.c)   //这里使用的是main.c而不是main.cpp

project 的初始化:VERSION字段

cmake_minimum_required(VERSION 3.15)
project(hellocmake VERSION 0.2.3)

message("PROJECT_NAME: ${PROJECT_NAME}")
message("PROJECT_VERSION: ${PROJECT_VERSION}")
message("PROJECT_SOURCE_DIR: ${PROJECT_SOURCE_DIR}")
message("PROJECT_BINARY_DIR: ${PROJECT_BINARY_DIR}")
message("hellocmake_VERSION: ${hellocmake_VERSION}")
message("hellocmake_SOURCE_DIR: ${hellocmake_SOURCE_DIR}")
message("hellocmake_BINARY_DIR: ${hellocmake_BINARY_DIR}")
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(补丁版本号)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hPaaH69W-1651300710806)(C:\Users\zhen\AppData\Roaming\Typora\typora-user-images\image-20220427001312599.png)]

小技巧: CMake ${} 表达式可以嵌套

因为${PROJECT_NAME}求值的结果是 hellocmake

所以 ${${PROJECT_NAME}_VERSION}相当于 ${hellocmake_VERSION}

进一步求值的结果也就是刚刚指定的 0.2.3 了。

假如我们的工程很大,需要很多个CPP文件,总不能一个一个添加吧,对于这种情况 ,cmake提供了一个能够自动获取当前目录下所有CPP的函数:

aux_source_directory(目录 存放文件列表的变量)

add_subdirectory(目录)

将子目录添加到CMake中,会在子目录里面查找和编译

设置 C++ 标准:CMAKE_CXX_STANDARD变量

cmake_minimum_required(VERSION 3.15)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)

project(hellocmake LANGUAGES CXX)

add_executable(main main.cpp)

  • CMAKE_CXX_STANDARD 是一个整数,表示要用的 C++ 标准。比如需要 C++17 那就设为 17,需要 C++23 就设为 23。

  • CMAKE_CXX_STANDARD_REQUIRED 是 BOOL 类型,可以为 ON 或 OFF,默认 OFF。他表示是否一定要支持你指定的 C++ 标准:如果为 OFF 则 CMake 检测到编译器不支持 C++17 时不报错,而是默默调低到 C++14 给你用;为 ON 则发现不支持报错,更安全。

3. 添加链接库

a. main.cpp 调用mylib.cpp里面的函数

CMakeLists.txt

add_executable(main main.cpp mylib.cpp)

main.cpp

#include "mylib.h"

int main() {
    say_hello();
}

mylib.cpp

#include "mylib.h"
#include <cstdio>

void say_hello() {
    printf("hello, mylib!\n");
}

mylib.h

#pragma once

void say_hello();

b. mylib作为与一个静态库

修改CMakeLists.txt

add_library(mylib STATIC mylib.cpp) // static 编译为静态库libmylib.a

add_executable(main main.cpp)

target_link_libraries(main PUBLIC mylib)

c. mylib作为与一个动态库

修改CMakeLists.txt

add_library(mylib SHARED mylib.cpp) // 编译成 .so文件

add_executable(main main.cpp)

target_link_libraries(main PUBLIC mylib)

d. mylib作为与一个对象库

对象库类似于静态库,但不生成 .a 文件,只由 CMake 记住该库生成了哪些对象文件, 对象库是 CMake 自创的,绕开了编译器和操作系统的各种繁琐规则,保证了跨平台统一性。在自己的项目中,我推荐全部用对象库(OBJECT)替代静态库(STATIC)避免跨平台的麻烦。对象库仅仅作为组织代码的方式,而实际生成的可执行文件只有一个,减轻了部署的困难。

修改CMakeLists.txt

add_library(mylib OBJECT mylib.cpp)

add_executable(main main.cpp)

target_link_libraries(main PUBLIC mylib)

e. add_library无参数时,是静态库还是动态库?

会根据 BUILD_SHARED_LIBS 这个变量的值决定是动态库还是静态库。ON 则相当于 SHARED,OFF 则相当于 STATIC。

如果未指定 BUILD_SHARED_LIBS 变量,则默认为 STATIC。因此,如果发现一个项目里的 add_library 都是无参数的,意味着你可以用:cmake -B build -DBUILD_SHARED_LIBS:BOOL=ON 来让他全部生成为动态库。

要让 BUILD_SHARED_LIBS 默认为 ON,可以用下图这个方法:如果该变量没有定义,则设为 ON,否则保持用户指定的值不变。这样当用户没有指定 BUILD_SHARED_LIBS 这个变量时,会默认变成 ON。也就是说除非用户指定了 -DBUILD_SHARED_LIBS:BOOL=OFF 才会生成静态库,否则默认是生成动态库。

if (NOT DEFINED BUILD_SHARED_LIBS)
    set(BUILD_SHARED_LIBS ON)
endif()

add_library(mylib mylib.cpp)

add_executable(main main.cpp)

target_link_libraries(main PUBLIC mylib)

f. 想动态库链接静态库怎么处理。

我们一般使用动态库链接动态库时,可以这样处理:

add_library(otherlib STATIC otherlib.cpp)

add_library(mylib SHARED mylib.cpp)
target_link_libraries(mylib PUBLIC otherlib)

add_executable(main main.cpp)
target_link_libraries(main PUBLIC mylib)

但是会报错,解决: 让静态库编译时也生成位置无关的代码(PIC),这样才能装在动态库里

add_library(otherlib STATIC otherlib.cpp)
set_property(TARGET otherlib PROPERTY POSITION_INDEPENDENT_CODE ON) //只针对otherlib库

add_library(mylib SHARED mylib.cpp)
target_link_libraries(mylib PUBLIC otherlib)

add_executable(main main.cpp)
target_link_libraries(main PUBLIC mylib)

或者全局设置

set(CMAKE_POSITION_INDEPENDENT_CODE ON)
add_library(otherlib STATIC otherlib.cpp)

add_library(mylib SHARED mylib.cpp)
target_link_libraries(mylib PUBLIC otherlib)

add_executable(main main.cpp)
target_link_libraries(main PUBLIC mylib)

除了POSITION_INDEPENDENT_CODE 还有哪些这样的属性

  1. 采用 C++17 标准进行编译(默认 11)

set_property(TARGET main PROPERTY CXX_STANDARD 17)

  1. 如果编译器不支持 C++17,则直接报错(默认 OFF)

set_property(TARGET main PROPERTY CXX_STANDARD_REQUIRED ON)

  1. 在 Windows 系统中,运行时不启动控制台窗口,只有 GUI 界面(默认 OFF)

set_property(TARGET main PROPERTY WIN32_EXECUTABLE ON)

  1. 告诉编译器不要自动剔除没有引用符号的链接库(默认 OFF)

set_property(TARGET main PROPERTY LINK_WHAT_YOU_USE ON)

  1. 设置动态链接库的输出路径(默认 ${CMAKE_BINARY_DIR})

​ set_property(TARGET main PROPERTY LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)

  1. 设置静态链接库的输出路径(默认 ${CMAKE_BINARY_DIR})

​ set_property(TARGET main PROPERTY ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)

  1. 设置可执行文件的输出路径(默认 ${CMAKE_BINARY_DIR})

set_property(TARGET main PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)

另一种方式:set_target_properties批量设置多个属性

set_target_properties(main PROPERTIES
    CXX_STANDARD 17           # 采用 C++17 标准进行编译(默认 11)
    CXX_STANDARD_REQUIRED ON  # 如果编译器不支持 C++17,则直接报错(默认 OFF)
    WIN32_EXECUTABLE ON       # 在 Windows 系统中,运行时不启动控制台窗口,只有 GUI 界面(默认 OFF)
    LINK_WHAT_YOU_USE ON      # 告诉编译器不要自动剔除没有引用符号的链接库(默认 OFF)
    LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib   # 设置动态链接库的输出路径(默认 ${CMAKE_BINARY_DIR})
    ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib   # 设置静态链接库的输出路径(默认 ${CMAKE_BINARY_DIR})
    RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin   # 设置可执行文件的输出路径(默认 ${CMAKE_BINARY_DIR})
    )

另一种方式:通过全局的变量,让之后创建的所有对象都享有同样的属性

set(CMAKE_CXX_STANDARD 17)

set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_WIN32_EXECUTABLE ON)

set(CMAKE_LINK_WHAT_YOU_USE ON)

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)

set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)

再看一个链接动态库的例子:

现在我们的目录结构是这样的:

.
├── CMakeLists.txt
├── main.cpp
└── mylib
├── CMakeLists.txt
├── mylib.cpp
└── mylib.h

最顶层的CMakeLists.txt需要链接底层的mylb生成的动态库,我们要怎么做:

顶层CMakeLists.txt

cmake_minimum_required(VERSION 3.15)

add_subdirectory(mylib)

add_executable(main main.cpp)
target_link_libraries(main PUBLIC mylib)

底层CMakeLists.txt

add_library(mylib SHARED mylib.cpp mylib.h)

4. 链接第三方库

用 CMake 的 find_package 命令。

find_package(TBB REQUIRED) 会查找 /usr/lib/cmake/*.cmake 这个配置文件,并根据里面的配置信息创建 伪对象,之后通过 target_link_libraries 链接 对象就可以正常工作了。

例如我们链接tbb对象:

CMakeLists.txt

add_executable(main main.cpp)

find_package(TBB REQUIRED)
target_link_libraries(main PUBLIC TBB::tbb)

有些包有多个组件,需要指定:

find_package 生成的伪对象(imported target)都按照“包名::组件名”的格式命名。你可以在 find_package 中通过 COMPONENTS 选项,后面跟随一个列表表示需要用的组件。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-45CNhWR9-1651300710806)(C:\Users\zhen\AppData\Roaming\Typora\typora-user-images\image-20220428231622887.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z67eVGyH-1651300710806)(C:\Users\zhen\AppData\Roaming\Typora\typora-user-images\image-20220428231601451.png)]

5. 变量与缓存

重复执行 cmake -B build 会有什么区别?

可以看到第二次的输出少了很多,这是因为 CMake 第一遍需要检测编译器和 C++ 特性等比较耗时,检测完会把结果存储到缓存中,这样第二遍运行cmake -B build 时就可以直接用缓存的值,就不需要再检测一遍了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nzM88hYY-1651300710807)(C:\Users\zhen\AppData\Roaming\Typora\typora-user-images\image-20220429000559438.png)]

如何清除缓存?

最简单粗暴的方法是rm -rf build/ 删除build 目录,其实我们的缓存都存在CMakeCache.txt 这个文件里面,所以我们只需要删除这个文件即可。

设置缓存变量

变量缓存的意义在于能够把 find_package 找到的库文件位置等信息,储存起来。这样下次执行 find_package 时,就会利用上次缓存的变量,直接返回。避免重复执行 cmake -B 时速度变慢的问题。

语法:

set(变量名 “变量值” CACHE 变量类型 “注释”)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7ee2Ly1p-1651300710807)(C:\Users\zhen\AppData\Roaming\Typora\typora-user-images\image-20220429000927162.png)]

缓存的 myvar 会出现在 build/CMakeCache.txt

但是我们经常会遇到一个问题: 我修改了 CMakeLists.txt set **的值,却没有更新?**为了更新缓存变量,有的同学偷懒直接修改 CMakeLists.txt 里的值,这是没用的。因为 set(… CACHE …) 在缓存变量已经存在时,不会更新缓存的值!CMakeLists.txt 里 set 的被认为是“默认值”因此不会在第二次 set 的时候更新。

因此我们必须通过命令行-D参数, 例如: cmake -B build -Dmyvar=world

缓存变量除了 STRING 还有哪些类型?

STRING 字符串,例如 “hello, world”

FILEPATH 文件路径,例如 “C:/vcpkg/scripts/buildsystems/vcpkg.cmake”

PATH 目录路径,例如 “C:/Qt/Qt5.14.2/msvc2019_64/lib/cmake/”

BOOL 布尔值,只有两个取值:ON 或 OFF。

注意:TRUE 和 ON 等价,FALSE 和 OFF 等价;YES 和 ON 等价,NO 和 OFF 等价

案例:添加一个 BOOL 类型的缓存变量,用于控制要不要启用某特性

cmake_minimum_required(VERSION 3.15)
project(hellocmake LANGUAGES CXX)

add_executable(main main.cpp)

set(WITH_TBB ON CACHE BOOL "set to ON to enable TBB, OFF to disable TBB.")
if (WITH_TBB)
    target_compile_definitions(main PUBLIC WITH_TBB)
    find_package(TBB REQUIRED)
    target_link_libraries(main PUBLIC TBB::tbb)
endif()

CMake BOOL 类型缓存的 set 指令提供了一个简写:option

option(变量名 “描述” 变量值)

等价于:

set(变量名 CACHE BOOL 变量值 “描述”)

cmake_minimum_required(VERSION 3.15)
project(hellocmake LANGUAGES CXX)

add_executable(main main.cpp)

option(WITH_TBB "set to ON to enable TBB, OFF to disable TBB." ON)
if (WITH_TBB)
    target_compile_definitions(main PUBLIC WITH_TBB)
    find_package(TBB REQUIRED)
    target_link_libraries(main PUBLIC TBB::tbb)
endif()

我们可以在.cpp文件中使用WITH_TBB变量:

#include <cstdio>

int main() {
#ifdef WITH_TBB
    printf("TBB enabled!\n");
#endif
    printf("Hello, world!\n");
}

6. 跨平台和编译器

在 CMake 中给 .cpp 定义一个宏MY_MACRO

CMakeList.txt

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

main.cpp

#include <cstdio>

int main() {
#ifdef MY_MACRO
    printf("MY_MACRO defined! value: %d\n", MY_MACRO);
#else
    printf("MY_MACRO not defined!\n");
#endif
}

根据不同的操作系统,把宏定义成不同的值

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

if (CMAKE_SYSTEM_NAME MATCHES "Windows")
    target_compile_definitions(main PUBLIC MY_NAME="Bill Gates")
elseif (CMAKE_SYSTEM_NAME MATCHES "Linux")
    target_compile_definitions(main PUBLIC MY_NAME="Linus Torvalds")
elseif (CMAKE_SYSTEM_NAME MATCHES "Darwin")
    target_compile_definitions(main PUBLIC MY_NAME="Steve Jobs")
endif()

CMake还提供了一些简写变量:WIN32, APPLE, UNIX, ANDROID, IOS等

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

if (WIN32)
    target_compile_definitions(main PUBLIC MY_NAME="Bill Gates")
elseif (UNIX AND NOT APPLE)
    target_compile_definitions(main PUBLIC MY_NAME="Linus Torvalds")
elseif (APPLE)
    target_compile_definitions(main PUBLIC MY_NAME="Steve Jobs")
endif()

使用生成器表达式,简化成一条指令

语法:$<$<类型:值>:为真时的表达式>

比如 $<$<PLATFORM_ID:Windows>:MY_NAME=”Bill Gates”>

在 Windows 平台上会变为 MY_NAME=”Bill Gates”

其他平台上则表现为空字符串

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

target_compile_definitions(main PUBLIC
    $<$<PLATFORM_ID:Windows>:MY_NAME="Bill Gates">
    $<$<PLATFORM_ID:Linux>:MY_NAME="Linus Torvalds">
    $<$<PLATFORM_ID:Darwin>:MY_NAME="Steve Jobs">
    )

生成器表达式:如需多个平台可以用逗号分割

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

target_compile_definitions(main PUBLIC
    $<$<PLATFORM_ID:Windows>:MY_NAME="DOS-like">
    $<$<PLATFORM_ID:Linux,Darwin,FreeBSD>:MY_NAME="Unix-like">
    )

判断当前用的是哪一款 C++ 编译器

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

if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
    target_compile_definitions(main PUBLIC MY_NAME="gcc")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "NVIDIA")
    target_compile_definitions(main PUBLIC MY_NAME="nvcc")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    target_compile_definitions(main PUBLIC MY_NAME="clang")
elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
    target_compile_definitions(main PUBLIC MY_NAME="msvc")
endif()

7. 分支和判断

通常来说 BOOL 类型的变量只有 ON/OFF 两种取值。但是由于历史原因,TRUE/FALSE 和 YES/NO 也可以表示 BOOL 类型。

if 的特点:不需要加 ${},会自动尝试作为变量名求值

由于历史原因,if 的括号中有着特殊的语法,如果是一个字符串,比如 MYVAR,则他会先看是否有 ${MYVAR} 这个变量。如果有这个变量则会被替换为变量的值来进行接下来的比较,否则保持原来字符串不变。

cmake_minimum_required(VERSION 3.15)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS ON)

project(hellocmake LANGUAGES C CXX)


set(MYVAR OFF)
if (MYVAR)
    message("MYVAR is true")
else()
    message("MYVAR is false")
endif()

add_executable(main main.cpp)

8. 变量和作用域

变量的传播规则:父会传给子子不传给父

参考:
https://www.bilibili.com/video/BV16P4y1g7MH/?spm_id_from=333.788

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值