双笙子佯谬老师的【公开课】现代CMake高级教程课程笔记
第 2 章:项目配置变量
CMAKE_BUILD_TYPE
构建的类型,调试模式还是发布模式。
CMAKE_BUILD_TYPE 是 CMake 中一个特殊的变量,用于控制构建类型,他的值可以是:
- Debug 调试模式,完全不优化,生成调试信息,方便调试程序
- Release 发布模式,优化程度最高,性能最佳,但是编译比 Debug 慢
- MinSizeRel 最小体积发布,生成的文件比 Release 更小,不完全优化,减少二进制体积
- RelWithDebInfo 带调试信息发布,生成的文件比 Release 更大,因为带有调试的符号信息
默认情况下 CMAKE_BUILD_TYPE 为空字符串,这时相当于 Debug。
cmake_minimum_required(VERSION 3.15)
project(hellocmake LANGUAGES CXX)
set(CMAKE_BUILD_TYPE Release)
add_executable(main main.cpp)
在 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)
set(CMAKE_BUILD_TYPE Release)
endif()
这里通过 if (NOT CMAKE_BUILD_TYPE) 判断是否为空,如果空则自动设为 Release 模式。大多数 CMakeLists.txt 的开头都会有这样三行,为的是让默认的构建类型为发布模式(高度优化)而不是默认的调试模式(不会优化)。
绝大多数 CMakeLists.txt 开头都会有的部分,可以说是“标准模板”了。
project
初始化项目信息,并把当前 CMakeLists.txt 所在位置作为根目录。
这里初始化了一个名称为 hellocmake 的项目,为什么需要项目名?对于 MSVC,他会在 build 里生成 hellocmake.sln 作为“IDE 眼中的项目”。
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)
CMAKE_CURRENT_SOURCE_DIR 表示当前源码目录的位置,例如 ~/hellocmake。
CMAKE_CURRENT_BINARY_DIR 表示当前输出目录的位置,例如 ~/hellocmake/build。
project:初始化项目信息,并把当前 CMakeLists.txt 所在位置作为根目录
[main] Configuring folder: CMakeLession
[proc] Executing command: /usr/bin/cmake --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++ -S/mnt/h/Code/lessonCode/CMakeLession -B/mnt/h/Code/lessonCode/CMakeLession/build -G Ninja
[cmake] PROJECT_NAME: hellocmake
[cmake] PROJECT_SOURCE_DIR: /mnt/h/Code/lessonCode/CMakeLession
[cmake] PROJECT_BINARY_DIR: /mnt/h/Code/lessonCode/CMakeLession/build
[cmake] CMAKE_CURRENT_SOURCE_DIR: /mnt/h/Code/lessonCode/CMakeLession
[cmake] CMAKE_CURRENT_BINARY_DIR: /mnt/h/Code/lessonCode/CMakeLession/build
[cmake] Not searching for unused variables given on the command line.
[cmake] -- Configuring done
[cmake] -- Generating done
[cmake] -- Build files have been written to: /mnt/h/Code/lessonCode/CMakeLession/build
和子模块的关系: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,那样会让你的项目无法被人作为子模块使用。
$tree
.
├── CMakeLists.txt
├── main.cpp
└── mylib
└── CMakeLists.txt
# 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)
# 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}")
输出:
[main] Configuring folder: CMakeLession
[proc] Executing command: /usr/bin/cmake --no-warn-unused-cli -DCMAKE_EXPORT_COMPILE_COMMANDS:BOOL=TRUE -DCMAKE_BUILD_TYPE:STRING=Debug -DCMAKE_C_COMPILER:FILEPATH=/usr/bin/clang -DCMAKE_CXX_COMPILER:FILEPATH=/usr/bin/clang++ -S/mnt/h/Code/lessonCode/CMakeLession -B/mnt/h/Code/lessonCode/CMakeLession/build -G Ninja
[cmake] PROJECT_NAME: hellocmake
[cmake] PROJECT_SOURCE_DIR: /mnt/h/Code/lessonCode/CMakeLession
[cmake] PROJECT_BINARY_DIR: /mnt/h/Code/lessonCode/CMakeLession/build
[cmake] CMAKE_CURRENT_SOURCE_DIR: /mnt/h/Code/lessonCode/CMakeLession
[cmake] CMAKE_CURRENT_BINARY_DIR: /mnt/h/Code/lessonCode/CMakeLession/build
[cmake] Not searching for unused variables given on the command line.
[cmake] mylib got PROJECT_NAME: hellocmake
[cmake] mylib got PROJECT_SOURCE_DIR: /mnt/h/Code/lessonCode/CMakeLession
[cmake] mylib got PROJECT_BINARY_DIR: /mnt/h/Code/lessonCode/CMakeLession/build
[cmake] mylib got CMAKE_CURRENT_SOURCE_DIR: /mnt/h/Code/lessonCode/CMakeLession/mylib
[cmake] mylib got CMAKE_CURRENT_BINARY_DIR: /mnt/h/Code/lessonCode/CMakeLession/build/mylib
[cmake] -- Configuring done
[cmake] -- Generating done
[cmake] -- Build files have been written to: /mnt/h/Code/lessonCode/CMakeLession/build
其他相关变量:
- PROJECT_SOURCE_DIR:当前项目源码路径(存放 main.cpp 的地方)
- PROJECT_BINARY_DIR:当前项目输出路径(存放 main.exe 的地方)
- CMAKE_SOURCE_DIR:根项目源码路径(存放 main.cpp 的地方)
- CMAKE_BINARY_DIR:根项目输出路径(存放 main.exe 的地方)
- PROJECT_IS_TOP_LEVEL:BOOL 类型,表示当前项目是否是(最顶层的)根项目
- PROJECT_NAME:当前项目名
- CMAKE_PROJECT_NAME:根项目的项目名
详见:https://cmake.org/cmake/help/latest/command/project.html
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 语言
cmake_minimum_required(VERSION 3.15)
project(hellocmake LANGUAGES CXX)
add_executable(main main.c)
#include <stdio.h>
int main(void)
{
printf("Hello, world from C!\n");
return 0;
}
输出:
[main] Building folder: CMakeLession
[build] Starting build
[proc] Executing command: /usr/bin/cmake --build /mnt/h/Code/lessonCode/CMakeLession/build --config Debug --target all --
[build] [1/1 0% :: 0.024] Re-running CMake...
[build] -- Configuring done
[build] CMake Error: CMake can not determine linker language for target: main
[build] -- Generating done
[build] CMake Generate step failed. Build files cannot be regenerated correctly.
[build] FAILED: build.ninja
[build] /usr/bin/cmake --regenerate-during-build -S/mnt/h/Code/lessonCode/CMakeLession -B/mnt/h/Code/lessonCode/CMakeLession/build
[build] ninja: error: rebuilding 'build.ninja': subcommand failed
[proc] The command: /usr/bin/cmake --build /mnt/h/Code/lessonCode/CMakeLession/build --config Debug --target all -- exited with code: 1 and signal: null
[build] Build finished with exit code 1
解决:改成 project(项目名 LANGUAGES C CXX) 即可
cmake_minimum_required(VERSION 3.15)
project(hellocmake LANGUAGES C CXX)
add_executable(main main.c)
也可以先设置 LANGUAGES NONE,之后再调用 enable_language(CXX),这样可以把 enable_language 放到 if 语句里,从而只有某些选项开启才启用某语言之类的
cmake_minimum_required(VERSION 3.15)
project(hellocmake LANGUAGES NONE)
enable_language(CXX)
add_executable(main main.cpp)
CMAKE_CXX_STANDARD
设置 C++ 标准。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 则发现不支持报错,更安全。
CMAKE_CXX_EXTENSIONS 也是 BOOL 类型,默认为 ON。设为 ON 表示启用 GCC 特有的一些扩展功能;OFF 则关闭 GCC 的扩展功能,只使用标准的 C++。要兼容其他编译器(如 MSVC)的项目,都会设为 OFF 防止不小心用了 GCC 才有的特性。
此外,最好是在 project 指令前设置 CMAKE_CXX_STANDARD 这一系列变量,这样 CMake 可以在 project 函数里对编译器进行一些检测,看看他能不能支持 C++17 的特性。
cmake_minimum_required(VERSION 3.15)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSINS ON)
project(hellocmake LANGUAGES CXX)
https://crascit.com/2015/03/28/enabling-cxx11-in-cmake/
常见误区:
请勿直接修改 CMAKE_CXX_FLAGS 来添加 -std=c++17(你在百度 CSDN 学到的用法),请使用 CMake 帮你封装好的 CMAKE_CXX_STANDARD(从业人员告诉你的正确用法)。
为什么百度不对:你 GCC 用户手动指定了 -std=c++17,让 MSVC 的用户怎么办?
此外 CMake 已经自动根据 CMAKE_CXX_STANDARD 的默认值 11 添加 -std=c++11 选项了,你再添加个 -std=c++17 选项不就冲突了吗?所以请用 CMAKE_CXX_STANDARD。
VERSION
project(项目名 VERSION x.y.z) 可以把当前项目的版本号设定为 x.y.z。之后可以通过 PROJECT_VERSION 来获取当前项目的版本号。
- PROJECT_VERSION_MAJOR 获取 x(主版本号)。
- PROJECT_VERSION_MINOR 获取 y(次版本号)。
- PROJECT_VERSION_PATCH 获取 z(补丁版本号)。
cmake_minimum_required(VERSION 3.15)
project(hellocmake VERSION 0.2.3)
message("PROJECT_NAME: ${PROJECT_NAME}")
message("PROJECT_VERSION: ${PROJECT_VERSION}")
message("PROJECT_VERSION_MAJOR: ${PROJECT_VERSION_MAJOR}")
message("PROJECT_VERSION_MINOR: ${PROJECT_VERSION_MINOR}")
message("PROJECT_VERSION_PATCH: ${PROJECT_VERSION_PATCH}")
一些没什么用,但 CMake 官方不知为何就是提供了的项目字段…
cmake_minimum_required(VERSION 3.15)
project(hellocmake
DESCRIPTION "A free, open-source, online modern C++ source"
HOMEPAGE_URL https://github.com/RainbowXie)
message("PROJECT_NAME: ${PROJECT_NAME}")
message("PROJECT_DESCRIPTION: ${PROJECT_DESCRIPTION}")
message("PROJECT_HOMEPAGE_URL: ${PROJECT_HOMEPAGE_URL}")
add_executable(main mian.cpp)
输出:
[cmake] PROJECT_NAME: hellocmake
[cmake] PROJECT_DESCRIPTION: A free, open-source, online modern C++ source
[cmake] PROJECT_HOMEPAGE_URL: https://github.com/RainbowXie
项目名的另一大作用:会设置另外 <项目名>_SOURCE_DIR 等变量
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}")
输出:
[cmake] PROJECT_NAME: hellocmake
[cmake] PROJECT_VERSION: 0.2.3
[cmake] PROJECT_SOURCE_DIR: /mnt/h/Code/lessonCode/CMakeLession
[cmake] Not searching for unused variables given on the command line.
[cmake] -- Configuring done
[cmake] PROJECT_BINARY_DIR: /mnt/h/Code/lessonCode/CMakeLession/build
[cmake] hellocmake_VERSION: 0.2.3
[cmake] hellocmake_SOURCE_DIR: /mnt/h/Code/lessonCode/CMakeLession
[cmake] hellocmake_BINARY_DIR: /mnt/h/Code/lessonCode/CMakeLession/build
[cmake] -- Generating done
[cmake] -- Build files have been written to: /mnt/h/Code/lessonCode/CMakeLession/build
小技巧:CMake 的 ${} 表达式可以嵌套
因为 ${PROJECT_NAME} 求值的结果是 hellocmake
所以 ${${PROJECT_NAME}_VERSION} 相当于 ${hellocmake_VERSION}
进一步求值的结果也就是刚刚指定的 0.2.3 了。
message("hellocmake_VERSION: ${${PROJECT_NAME}_VERSION}")
message("hellocmake_SOURCE_DIR: ${${PROJECT_NAME}_SOURCE_DIR}")
message("hellocmake_BINARY_DIR: ${${PROJECT_NAME}_BINARY_DIR}")
输出:
[cmake] hellocmake_VERSION: 0.2.3
[cmake] hellocmake_SOURCE_DIR: /mnt/h/Code/lessonCode/CMakeLession
[cmake] hellocmake_BINARY_DIR: /mnt/h/Code/lessonCode/CMakeLession/build
cmake_minimum_required
指定最低所需的 CMake 版本。假如你写的 CMakeLists.txt 包含了 3.15 版本才有的特性,如果用户在老版本上使用,就会出现各种奇怪的错误。因此最好在第一行加个 cmake_minimum_required(VERSION 3.15) 表示本 CMakeLists.txt 至少需要 CMake 版本 3.15 以上才能运行,如果用户的 CMake 版本小于 3.15,会出现“CMake 版本不足”的提示。
假设我现在构建一个需要版本 3.99 的 CMake 项目,会正常报错提示版本过低,而不是等到某处用到 3.99 版本才有的特性时才出错。
输出:
[cmake] CMake Error at CMakeLists.txt:1 (cmake_minimum_required):
[cmake] CMake 3.99 or higher is required. You are running version 3.22.1
可以通过 CMAKE_VERSION 这个变量来获得当前 CMake 版本号;CMAKE_MINIMUM_REQUIRED_VERSION 获取 cmake_minimum_required 中指定的“最小所需版本号”。
cmake_minimum_required(VERSION 3.15)
project(hellocmake LANGUAGES CXX)
message("CMAKE_VERSION: ${CMAKE_VERSION}")
message("CMAKE_MINIMUM_REQUIRED_VERSION: ${CMAKE_MINIMUM_REQUIRED_VERSION}")
输出:
[cmake] CMAKE_VERSION: 3.22.1
[cmake] CMAKE_MINIMUM_REQUIRED_VERSION: 3.15
注意:cmake_minimum_required 不仅是“最小所需版本”,虽然名字叫 minimum_required,实际上不光是 >= 3.15 就不出错这么简单。根据你指定的不同的版本号,还会决定接下来一系列 CMake 指令的行为。此外,你还可以通过 3.15…3.20 来表示最高版本不超过 3.20。这会对 cmake_policy 有所影响,稍后再提。
cmake_minimum_required(VERSION 3.15...3.20)
https://runebook.dev/zh-CN/docs/cmake/command/cmake_minimum_required#policy-settings
其他常见变量
https://blog.csdn.net/fuyajun01/article/details/8891749
变量 | 说明 |
---|---|
CMAKE_BUILD_TOOL | 执行构建过程的工具。该变量设置为 CMake 构建时输出所需的程序。对于 VS 6,CMAKE_BUILD_TOOL 设置为 msdev,对于 Unix,它被设置为 make 或 gmake。对于 VS 7,它被设置为 devenv。对于 Nmake 构建文件,它的值为 nmake。 |
CMAKE_DL_LIBS | 包含 dlopen 和 dlclose 的库的名称。 |
CMAKE_COMMAND | 指向 cmake 可执行程序的全路径。 |
CMAKE_CTEST_COMMAND | 指向 ctest 可执行程序的全路径。 |
CMAKE_EDIT_COMMAND | cmake-gui 或 ccmake 的全路径。 |
CMAKE_EXECUTABLE_SUFFIX | 该平台上可执行程序的后缀。 |
CMAKE_SIZEOF_VOID_P | void 指针的大小。 |
CMAKE_SKIP_RPATH | 如果为真,将不添加运行时路径信息。默认情况下是如果平台支持运行时信息,将会添加运行时信息到可执行程序当中。这样从构建树中运行程序将很容易。为了在安装过程中忽略掉 RPATH,使用 CMAKE_SKIP_INSTALL_RPATH。 |
CMAKE_GENERATOR | 构建工程的产生器。它将产生构建文件 (e.g. “Unix Makefiles”, “Visual Studio 2019”, etc.) |
一个标准的 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 PROJET_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()
if (WIN32)
add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES)
endif()
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()