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 还有哪些这样的属性
- 采用 C++17 标准进行编译(默认 11)
set_property(TARGET main PROPERTY CXX_STANDARD 17)
- 如果编译器不支持 C++17,则直接报错(默认 OFF)
set_property(TARGET main PROPERTY CXX_STANDARD_REQUIRED ON)
- 在 Windows 系统中,运行时不启动控制台窗口,只有 GUI 界面(默认 OFF)
set_property(TARGET main PROPERTY WIN32_EXECUTABLE ON)
- 告诉编译器不要自动剔除没有引用符号的链接库(默认 OFF)
set_property(TARGET main PROPERTY LINK_WHAT_YOU_USE ON)
- 设置动态链接库的输出路径(默认 ${CMAKE_BINARY_DIR})
set_property(TARGET main PROPERTY LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
- 设置静态链接库的输出路径(默认 ${CMAKE_BINARY_DIR})
set_property(TARGET main PROPERTY ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
- 设置可执行文件的输出路径(默认 ${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