如何优雅的使用CMakePresets
前提
- CMake>=3.21
引入
通常情况下, 配置一个cmake项目是这样的
git clone http://www.gayhub.com/balabala/bala.git
cd balabala/
mkdir build && cd build
cmake -S .. -B . -DCMAKE_INSTALL_PREFIX=/path/to/install -DDEBUG_INFO=ON -DUSE_BOOST_STL=ON -DNO_COMPILE_VISION=ON \
-DSOME_LIB_PATH=/path/to/some/lib -DANOTHER_LIB_PATH=/path/to/another/lib
make -j10
make install
这里, 随着我们的项目的扩大, 以及对不同平台的交叉编译的配置需求, 我们就需要不断的增加在cmake配置时引入的选项,
比如我们希望对这个编译结果使用这个版本的库, 对另一个编译结果使用另一个版本的库. 有的时候我们希望编译某个库,
有的时候我们不想编译某个库. 这在我们的项目中是很常见的.而且随着我们的项目规模的增加, 这种选项往往会越来越多.
很多时候我们会通过ccmake/cmake-gui的方式来对选项进行配置. 而这种方式往往很难分享给其他人, 而且随着交叉编译的规模增加,
这种手工配置的方式就不再适合我们了.
CMakePresets在这时候就起到了一个比较关键的作用, 在形式上, CMakePresets其实与VSCode的配置文件类似, 但是与VSCode不同,
CMakePreset并不关心你使用何种IDE. 只要你在命令行中指定对应的presets即可.
而这时候我们又会引入一个疑问, 既然仍然需要指定presets, 那岂不和我们直接用命令行的方式没有区别?
CMakePresets对此的解决方法是, 使用预设的继承, 宏以及不同级别之间的组合的方法, 来减少重复的配置选项.
例子
废话少说, 让我们用一个简单的例子来看看CMakePresets.json是什么结构.
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 19,
"patch": 0
},
"configurePresets": [
{
"name": "default",
"hidden": true,
"cacheVariables": {
"DEBUG_INFO": "ON",
"USE_BOOST_STL": "ON",
"NO_COMPILE_VISION": "ON",
"SOME_LIB_PATH": "/path/to/some/lib",
"ANOTHER_LIB_PATH": "/path/to/another/lib"
},
"environment": {},
"vendor": {
"jetbrains.com/clion": {
"toolchain": "System"
}
}
},
{
"name": "debug",
"inherits": "default",
"displayName": "configure for debug",
"binaryDir": "${sourceDir}/build",
"installDir": "/path/to/install",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"inherits": "default",
"displayName": "configure for debug",
"binaryDir": "${sourceDir}/build-release",
"installDir": "/path/to/install",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
],
"buildPresets": [
{
"name": "build-debug",
"configurePreset": "debug",
"configuration": "Debug",
"jobs": 10,
"verbose": true
}
]
}
这个preset文件与上面的命令的作用是完全一致的.
通过观察我们不难发现, 文件中的cacheVariables字段的内容起到的作用就相当于在cmake的配置命令中的 -D{cmd}
的作用.
每个配置项中的binaryDir
字段的作用与cmake命令中的 -B
的作用是相同的.
配置项中的 installDir
字段的作用与cmake命令中的 -DCMAKE_INSTALL_PREFIX
作用相同.
在 configurePresets
中, 第一个对象的 hidden
字段被我们设置为true. 这一字段表示这个项目并不是真正的configurePreset,
而是作为其他的configurePreset的基础出现的. (作用类似Java中的abstract)
而第二个对象中, 其 inherits
字段被设置为了default, 这一字段的作用为说明当前配置项继承自哪个配置项.
在这里我们将其继承自 default
, 这时候我们的各种参数会默认被设置为 default
中的提供的值. 与类的继承完全相同,
当我们在子类中指定某个字段的值的时候, 这个值就不再是父类中给出的值, 而是我们指定的值了.
第二个选项的 displayName
字段比较有趣, 这个字段的内容用于说明当前配置项, 同样在cmake命令行中, 这个配置项的内容也会被展示在命令行输出中.
比如说, 我们在CMakePresets.json所在的目录下执行如下命令.
$ cmake --list-presets
Available configure presets:
"debug" - configure for debug
"release" - configure for release
可以看到, 我们使用了一个列出所有presets的命令, 这时候cmake就会把displayName
中指定的值给打印出来了.
在这个例子中, 我们可以使用如下的命令来替代上面的又臭又长的命令
cmake --preset debug
cmake在执行这段命令后会自动检测所在文件夹的CMakePresets.json文件, 然后找到对应的preset进行configure.
当然, CMake同样提供了一个使用buildConfigure中的预设来进行编译的命令. 在上面的例子中, 我们执行如下命令.
cmake --build --preset build-debug
就可以让cmake自动的调用名称为 build-debug
的编译预设.
一些宏
除了继承, CMakePresets还提供了一些比较方便使用的宏选项. 这个例子中, binaryDir
中就使用了 ${sourceDir}
宏.
这个宏会被展开为当前文件所在的目录路径.
这对于我们想在当前文件夹下的子文件夹下编译的时候会非常方便, 也省去了在不同机器上编译时的重新编写的成本.
环境变量
在CMake的configure过程中, CMake往往会调用一些环境变量, 而很多环境变量会随着不同的配置过程发生变化(
比如不同的github账号之类的), 这时候configurePreset中的environment字段就起到了作用.
vendor
不同的编译器厂商的自定义配置, 我习惯使用jetbrain, 所以这里我会让CMake去自动搜索CLion中提供的名为System的toolchain.
总结
CMakePresets为我们的交叉编译提供了一个非常强大而方便的集成. 通过CMakePresets我们可以简化不同的配置项目的配置命令与配置过程.
参考: