接现代CMake学习笔记(一),捋一捋CMake中的一些容易被忽略的问题。先列一下本文内容,然后再就用一个最简单的C++例子,看看能发散多少
知识点:
cmake -h
查看生成器列表与默认值。-G
用于选择生成器cmake --build .
隐藏不同生成器的执行差异-S
-B
与--build
实现了在源码目录下直接执行out-of-source-build,不需要切换目录- 务必为 cmake指定构建类型 ,如果你不喜欢薛定谔的猫的话。它默认既不是Release也不是Debug
- Debug和Release的选择,区分单配置和多配置的生成器。单配置在config时指定,多配置在build时指定
以上几点合并起来——如果我要编译release版本程序,且生成产物放置到my-release-dir
下,只需要在源码目录下执行如下命令:
- 对单配置生成器(以Ninja为例)
1 2 |
|
- 对多配置生成器(以Visual Studio 16 2019为例)
1 2 |
|
言归正传,如下正文:
CMake简单使用?使用简单?
CMake用起来很简单,只需要写一个代码文件和工程文件:
- main.cpp 文件
1 2 3 4 5 |
|
- CMakeLists.txt
1 2 |
|
然后就可以放飞自我,在众多平台下构建了:
1 2 |
|
这时得到一个名为hello
可执行程序,任务完成。
- 第一条命令:为make生成Makefile文件或为其他工具生成工程文件,生成在当前目录下
- 第二条命令:调用make或其他工具,在当前目录进行构建
就这么简单!
就这么简单??不妨看看这里面到底有多少注意点...
make? cmake --build?
在上面的例子中,我们使用了cmake --build .
而不是直接 make
。明明下面这样更简单啊。
而且很多资料也是这么用的,不是么?
1 2 |
|
因为cmake 有不同的生成器,针对不同的生成器,需要我们需调用不同的命令make/nmake/jom/ninja/msbuild/...
最终写起来很费心,用--build
这个选项,就不用分情况讨论了。
注:构建时使用
-v
或--verbose
参数,有时候很有用cmake --build -v .
生成器(Generator)
可用的的生成器有哪些,默认是哪个?可如下命令可以查看。
1 |
|
比如,cmake在Ubuntu下的结果如下:(从中可见,该配置下默认是Unix Makefiles)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
要切换不同的Generator,可使用 -G
参数,比如,工程可以这么构建:
1 2 |
|
另外,如果要改变默认的生成器,可以设置环境变量(就不用每次用-G
指定了):
1 |
|
注:如果用conan的话,使用如下环境变量
1 |
|
只用一个生成器?
CMake本身是跨平台的方案,如果只考虑一个环境。cmake不一定简单了。
而且如果只用一个平台,且用的Visual Studio或XCode的话,也确实没必要碰cmake。
对上面例子来说,我们只要写一个 main.cpp 文件,然后:
1 |
|
或者
1 |
|
不就得了?? 既不需要工程文件,也不需要执行执行两次命令。多简单
即使文件比较多,要高级一点,也就写个Makefile文件,比如
1 2 |
|
每次构建也只需要调用下面一条命令就够了
1 |
|
嗯,这样确实不需要cmake,且似乎更简单。
尽管如此,cmake还是有一定优势,毕竟可以逃避学更底层的一些东西,比如Makefile的规则。
In-source build?Out-of-source build?
在上面例子中,构建的中间产物和目标产物都和源码在一个目录下(即In-source-build)。
尽管命令简单,但缺点也比较明显。
- 污染源码目录,不方便源码管控。需要精细配置 .gitignore等文件
- 如果一套源码在不同的编译器下编译,不便于隔离。
- ...
建议的操作方式是,构建和源码目录隔离,单独创建构建目录(Out-of-source-build)。
其实操作不复杂
1 2 |
|
- 第一条命令:为make生成Makefile文件或为其他工具生成工程文件。使用
-S
指定源码目录,-B
指定构建目录 - 第二条命令:调用make或其他工具,在指定目录下进行构建
这样源码目录没那么乱了:
1 2 3 4 5 6 7 8 9 |
|
当前,可以构建目录和源码目录平行,这样...
1 2 3 4 5 6 7 8 |
|
注意,我们在这儿没有显式切换目录,如果你看过老版本的CMake,操作主要在构建目录下进行,写下来会像下面这样:
1 2 3 4 |
|
Debug? Release ?(单配置生成器 single-configuration)
这个一个坑!!!
我们某个程序Linux下某程序运行很慢,怀疑没用release模式构建。同事排查后:里面没有Debug信息,肯定是Release。
对于单配置的生成器(比如Makefile 和 Ninja)来说,构建类型通过CMAKE_BUILD_TYPE
来指定,它的典型值如下:
Debug
,Release
,RelWithDebInfo
MinSizeRel
比如我们要构建release和debug:
1 2 3 4 5 |
|
如果不加type,默认是那个呢?
默认是什么东西?
- https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
手册中说:
1 |
|
手册中说,默认经常是空值,通常不符合预期。
以Ubuntu为例,默认时,编译选项中既没有
-g
来生成调试信息,也没有-O3
等优化选项。完全就是玩具,不要再生产环境用!!(其实这也不全是cmake的锅,前面我们直接调g++不也没有加选项嘛,但是cmake这样弄还是有点接受不了)
另外,Debug、Release 到底是大写、小写还是首字符大写。官方也没有确定的说法。
查看不同类型编译选项
要查看编译选项,可以在CMakeLists.txt中添加
1 2 3 |
|
运行cmake,在Linux下大致是下面这样的结果
1 2 3 |
|
或者直接运行 cmake -LA .
来查看当前项目所有变量值
1 2 3 4 5 6 7 |
|
Debug? Release ?(多配置生成器 Multi-configuration)
对于 Visual Studio
、Xcode
与 Ninja Multi-Config
这些多配置生成器来说,在config阶段生效的 CMAKE_BUILD_TYPE
变量没有任何作用,会直接被忽略掉。
配置时,不需要指定类型,但是构建时,需要通过--config
来指定。
1 2 |
|
那么config后面可以跟什么呢,手册中说预定义这么几个:
Debug
Release
RelWithDebInfo
MinSizeRel
这个列表可以使用CMAKE_CONFIGURATION_TYPES
可以手动修改/指定。
1 |
|
注意:单配置生成器会直接忽略
CMAKE_CONFIGURATION_TYPES
选项。
对于config值,同前面单配置的坑一样,手册中强调:
1 |
|
- 默认值通常不是上面几个值中的一个,而是一个空值。
- 不要将默认值 等同于 Debug
- 你应该始终显式指定构建类型
比如我们要构建release和debug:
1 2 3 |
|
如何默认Release构建?
如果只是自己用,设置如下环境变量就行了:
1 |
|
如果不想让项目每个参与者都设置环境变量,那么,可以像下面这样改项目的CMakeLists.txt文件:
1 2 3 4 |
|
注意:里面用到了全局属性GENERATOR_IS_MULTI_CONFIG
来判定生成器类型,这一属性是CMAKE 3.9 引入的。
参考
- https://blog.feabhas.com/2021/07/cmake-part-2-release-and-debug-builds/
- https://stackoverflow.com/questions/23995019/what-is-the-modern-method-for-setting-general-compile-flags-in-cmake
- https://stackoverflow.com/questions/16851084/how-to-list-all-cmake-build-options-and-their-default-values
- https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html
- https://cmake.org/cmake/help/latest/variable/CMAKE_CONFIGURATION_TYPES.html
- https://blog.csdn.net/weixin_39766005/article/details/122439200
- https://www.kitware.com/cmake-and-the-default-build-type/
- https://www.scivision.dev/cmake-default-build-type/