现代CMake高级教程 - 第 2 章:项目配置变量

双笙子佯谬老师的【公开课】现代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_COMMANDcmake-gui 或 ccmake 的全路径。
CMAKE_EXECUTABLE_SUFFIX该平台上可执行程序的后缀。
CMAKE_SIZEOF_VOID_Pvoid 指针的大小。
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()
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值