Meson 构建 C++ 项目¶
最近在使用一些开源项目的时候,发现都采用了meson+ninja的构建方式,摒弃了CMake,Makefile的方式,本文将会引入Meson,从入门安装到完整编写一个meson项目。
下面,我们来进入正文。
1.Meson安装¶
Meson 可以通过 PyPi 获得,因此可以使用 pip3 install meson 进行安装。确保在不同系统中使用正确的 pip 命令,务必使用 Python 3 版本的 pip。
如果希望,也可以使用标准的 Python 命令在本地安装:
对于使用 Ninja 进行构建的情况,可以直接从 Ninja GitHub 发布页面或通过 PyPi 下载 Ninja:
有关安装 Meson build 的更多信息,请阅读下方Meson官方网站。
2.Meson Vs Cmake¶
既然大家都用CMake,为何还要用Meson呢,他们各自优劣是什么呢?
在性能方面,Meson 通常以其快速的构建速度而闻名。Meson 的设计旨在优化构建速度,并且在增量构建方面表现特别出色。Meson 使用 Ninja 构建系统作为默认后端,Ninja 本身也以快速和高效而著称。
相比之下,CMake 的构建速度可能会受到项目规模的影响,尤其是在较大的项目中。CMake 使用各种生成器(如Makefile),而不同生成器的性能也可能有所不同。
当然也可以使用CMake + Ninja去提高编译速度。
从开源项目角度来看Meson,其使用场景也是非常的多,例如:PostgreSQL、QEMU、GNOME Software、GTK+,等等
2.1 Meson的优势与劣势¶
1.优势
- 基于Python,使用简单
- 支持交叉编译
- 所有对象都是强类型的:由于对象是诸如 'dependency'、'include directory' 等实体,因此很难出现字符串替换错误。
- 非常方便的引用第三发库
- 非常非常快,特别是在增量构建时。
- 文档比cmake好n倍。
2.劣势
- 可能不如CMake成熟
- 有Python3 + Ninja的依赖
- 项目生成器,IDE支持Meson不如CMake
- vscode支持
- https://github.com/mesonbuild/vscode-meson
- 可用模块有限
- 需要自己将非meson项目提交到wrapdb
- https://mesonbuild.com/Wrapdb-projects.html
2.2 CMake的优势与劣势¶
1.优势
- IDE支持友好,团队协作比较友
- 成熟,几乎成为C++项目的标准
2.缺点
- 交叉编译困难
- 编译速度相对比较慢
3.Meson WrapDB¶
由于googletest不是meson项目,那如何使用meson去依赖它呢?
同理,grpc、abseil-cpp使用方式类似。
这里就要先引入WrapDB,Meson WrapDB 是 Meson 构建系统的一个组件,用于管理和提供第三方项目的构建描述文件,通常称为 "wrap" 文件。这些 wrap 文件包含了配置和构建第三方项目所需的信息,以便能够轻松地集成到使用 Meson 构建系统的其他项目中。
可以通过search命令或者下方网站查询第三方库。
https://mesonbuild.com/Wrapdb-projects.html#meson-wrapdb-packages
我们以gtest使用为例:
- 搜索gtest
如果该命令返回包装的项目名称,那么便可以进行下一步了:
- 在自己的项目创建subprojects目录,通过install安装gtest
此时你的项目所依赖的第三方库均已在subprojects目录安装好了。
此时会生成gtest.wrap,里面的内容会包含下面的内容。
注意:此文件是上方install之后产生的文件,建议不要修改。
[wrap-file]
directory = googletest-1.14.0
source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz
source_filename = gtest-1.14.0.tar.gz
source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7
patch_filename = gtest_1.14.0-1_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-1/get_patch
patch_hash = 2e693c7d3f9370a7aa6dac802bada0874d3198ad4cfdf75647b818f691182b50
source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-1/gtest-1.14.0.tar.gz
wrapdb_version = 1.14.0-1
[provide]
gtest = gtest_dep
gtest_main = gtest_main_dep
gmock = gmock_dep
gmock_main = gmock_main_dep
4.如何编写一个meson项目?¶
接下来,来到实践环节,如何编写一个meson项目呢?
下面以a/b除法测试为例,使用gtest进行单元测试的一个小项目。
4.1 编写代码¶
.h与.cc放到src目录:
随后同级目录创建test目录。
4.2 meson项目管理¶
在项目根路径添加meson.build文件,内容如下:
project('gtest_div', 'cpp')
gtest_proj = subproject('gtest')
gtest_dep = gtest_proj.get_variable('gtest_main_dep')
div_src = files('src/division.cc')
tests_src = files('test/division_test.cc')
e = executable(
'division_test',
tests_src + div_src,
dependencies : [ gtest_dep],
override_options : ['cpp_std=c++14']
)
# https://mesonbuild.com/Unit-tests.html#testing-tool
test('this is a division test', e)
可以看到这个内容跟python的语法基本一致,熟悉python的看这个代码就非常容易了,这里着重讲一下上面比较重要的部分。
- 引入第三方库
例如:你引入的第三方库的项目叫做gtest,如下:
那么便可以通过subproject获取到这个project,然后通过gtest_proj.get_variable便可以获取到gtest的依赖信息,例如:googletest里面是这样声明的:
gtest_main_dep = declare_dependency(
include_directories : gtest_incdir,
sources : [gtest_libsources, gtest_mainsources],
dependencies : dependency('threads')
)
- executable
executable跟cmake里面的add_executable非常类似,生成可执行文件division_test。
- test
单元测试语法:
可以使用meson test或者ninja test,运行完之后会产生测试日志,位于:
4.3 构建与运行Meson项目¶
经过上面的操作,一个meson项目准备就绪。接下来,我们来构建与运行这个项目。
4.3.1 配置构建目录¶
此时类似于cmake的mkdir build && cmake ..。
运行如下:
➜ gtest_div meson setup build
The Meson build system
Version: 1.2.3
Source dir: /Users/light/work/practice/gtest_div
Build dir: /Users/light/work/practice/gtest_div/build
Build type: native build
Project name: gtest_div
Project version: undefined
C++ compiler for the host machine: c++ (clang 12.0.5 "Apple clang version 12.0.5 (clang-1205.0.22.11)")
C++ linker for the host machine: c++ ld64 650.9
Host machine cpu family: x86_64
Host machine cpu: x86_64
Downloading gtest source from https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz
Download size: 867764
Downloading: ..........
Downloading gtest patch from https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-1/get_patch
Download size: 2440
Downloading: ..........
Executing subproject gtest
gtest| Project name: gtest
gtest| Project version: 1.14.0
gtest| C++ compiler for the host machine: c++ (clang 12.0.5 "Apple clang version 12.0.5 (clang-1205.0.22.11)")
gtest| C++ linker for the host machine: c++ ld64 650.9
gtest| Run-time dependency threads found: YES
gtest| Dependency threads found: YES unknown (cached)
gtest| Dependency threads found: YES unknown (cached)
gtest| Dependency threads found: YES unknown (cached)
gtest| Build targets in project: 0
gtest| Subproject gtest finished.
Build targets in project: 1
gtest_div undefined
Subprojects
gtest: YES
Found ninja-1.11.1 at /usr/local/bin/ninja
此时我们可以在subprojects看到缓存的gtest项目。
4.3.2 编译¶
我们可以使用下面两个命令,第一种:
运行日志:
➜ gtest_div meson compile -C build/
INFO: autodetecting backend as ninja
INFO: calculating backend command to run: /usr/local/bin/ninja -C /Users/light/work/practice/gtest_div/build
ninja: Entering directory `/Users/light/work/practice/gtest_div/build'
[5/5] Linking target division_test
第二种:
这两种命令本质是一样,都会用ninja来编译,当然也可以不用-C指定目录,那么此时需要cd build && ninja或者cd build && meson compile。
4.3.3 运行与测试¶
此时会生成上division_test可执行程序,运行如下:
➜ gtest_div ./build/division_test
Running main() from ../subprojects/googletest-1.14.0/googletest/src/gtest_main.cc
[==========] Running 2 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 2 tests from DivisionTest
[ RUN ] DivisionTest.Divide
[ OK ] DivisionTest.Divide (0 ms)
[ RUN ] DivisionTest.DivideByZero
[ OK ] DivisionTest.DivideByZero (0 ms)
[----------] 2 tests from DivisionTest (0 ms total)
[----------] Global test environment tear-down
[==========] 2 tests from 1 test suite ran. (0 ms total)
[ PASSED ] 2 tests.
由于在meson.build中添加单元测试test,所以我们可以使用
或者:
如果指定目录:
运行日志:
➜ gtest_div meson test -C build
ninja: Entering directory `/Users/light/work/practice/gtest_div/build'
ninja: no work to do.
1/1 this is a division test OK 0.01s
Ok: 1
Expected Fail: 0
Fail: 0
Unexpected Pass: 0
Skipped: 0
Timeout: 0
Full log written to /Users/light/work/practice/gtest_div/build/meson-logs/testlog.txt