Last updated:2026-10-12
Building C++ Projects with Meson¶
Recently, while using some open-source projects, I noticed that they all adopted the meson+ninja build approach, abandoning CMake and Makefiles. This article introduces Meson, from getting it installed to writing a complete Meson project.
Now, let's get into the main content.
1.Installing Meson¶
Meson is available from PyPI, so you can install it with pip3 install meson. Make sure you use the correct pip command on different systems — it must be the Python 3 version of pip.
If you prefer, you can also install it locally with the standard Python command:
If you build with Ninja, you can download Ninja directly from the Ninja GitHub releases page or via PyPI:
For more information about installing the Meson build system, read the official Meson website below.
2.Meson vs CMake¶
Since everyone uses CMake, why bother with Meson — and what are their respective strengths and weaknesses?
In terms of performance, Meson is generally known for its fast build speed. Meson is designed to optimize build speed and performs especially well on incremental builds. Meson uses the Ninja build system as its default backend, and Ninja itself is also known for being fast and efficient.
By comparison, CMake's build speed can be affected by project size, especially in larger projects. CMake uses various generators (such as Makefiles), and different generators may also perform differently.
Of course, you can also use CMake + Ninja to speed up compilation.
Looking at Meson from the open-source project perspective, it is used in a great many places, for example: PostgreSQL, QEMU, GNOME Software, GTK+, and so on.
2.1 Meson's Advantages and Disadvantages¶
1.Advantages
- Python-based and easy to use
- Supports cross compilation
- All objects are strongly typed: since objects are entities such as 'dependency' and 'include directory', it is hard to make string-substitution errors.
- Extremely convenient for pulling in third-party libraries
- Very, very fast, especially on incremental builds.
- Documentation is n times better than CMake's.
2.Disadvantages
- May not be as mature as CMake
- Has Python 3 + Ninja dependencies
- Project generators and IDE support for Meson lag behind CMake
- VS Code support
- https://github.com/mesonbuild/vscode-meson
- Limited modules available
- You need to submit non-Meson projects to WrapDB yourself
- https://mesonbuild.com/Wrapdb-projects.html
2.2 CMake's Advantages and Disadvantages¶
1.Advantages
- Friendly IDE support and comfortable team collaboration
- Mature; it has practically become the standard for C++ projects
2.Disadvantages
- Cross compilation is difficult
- Compilation is relatively slow
3.Meson WrapDB¶
Since googletest is not a Meson project, how do we depend on it from Meson?
Likewise, grpc and abseil-cpp are used in a similar way.
This is where WrapDB comes in. Meson WrapDB is a component of the Meson build system that manages and provides build description files for third-party projects, commonly called "wrap" files. These wrap files contain the information needed to configure and build third-party projects so that they can be easily integrated into other projects using the Meson build system.
You can look up third-party libraries with the search command or on the website below.
https://mesonbuild.com/Wrapdb-projects.html#meson-wrapdb-packages
Let's take gtest as an example:
- Search for gtest
If the command returns the name of the wrapped project, you can move on to the next step:
- Create a subprojects directory in your own project and install gtest via install
At this point, the third-party libraries your project depends on are all installed in the subprojects directory.
This generates gtest.wrap, whose content includes the following.
Note: this file is generated by the install step above; it is recommended that you do not modify it.
[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.How Do You Write a Meson Project?¶
Next comes the hands-on part: how do you write a Meson project?
Below is a small project that uses gtest for unit testing, taking a/b division tests as an example.
4.1 Write the Code¶
Put the .h and .cc files in the src directory:
Then create a test directory at the same level.
4.2 Meson Project Management¶
Add a meson.build file at the project root with the following content:
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)
As you can see, this is essentially the same as Python syntax, so anyone familiar with Python will find this code very easy to read. Here we will focus on the more important parts above.
- Pulling in third-party libraries
For example, suppose the third-party library you pull in is a project called gtest, like this:
You can then obtain this project via subproject, and gtest_proj.get_variable gives you gtest's dependency information. For example, this is how it is declared inside googletest:
gtest_main_dep = declare_dependency(
include_directories : gtest_incdir,
sources : [gtest_libsources, gtest_mainsources],
dependencies : dependency('threads')
)
- executable
executable is very similar to add_executable in CMake; it produces the division_test executable.
- test
Unit test syntax:
You can use meson test or ninja test; after the run a test log is produced, located at:
4.3 Building and Running a Meson Project¶
With the steps above, a Meson project is ready. Next, let's build and run this project.
4.3.1 Configure the Build Directory¶
This is similar to CMake's mkdir build && cmake ...
It runs as follows:
➜ 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
At this point, we can see the cached gtest project under subprojects.
4.3.2 Compile¶
We can use either of the following two commands. The first:
Run log:
➜ 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
The second:
These two commands are essentially the same — both compile with ninja. Of course, you can also skip -C to specify the directory, in which case you need cd build && ninja or cd build && meson compile.
4.3.3 Run and Test¶
This produces the division_test executable, which runs as follows:
➜ 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.
Since we added a unit test with test in meson.build, we can use
Or:
To specify a directory:
Run log:
➜ 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
Related Topics¶
- Building C++ Projects with Bazel | C++那些事
- Debugging Bazel Builds with LLDB | C++那些事
- CMake Essentials in Practice | C++那些事
- Getting Started with the Conan C++ Package Manager | C++那些事
- C++ Insights Compile-Time Observation | C++那些事
- Interactive C++ in Jupyter Notebook | C++那些事
- C++ string to int | C++那些事
- The C++ dbg-macro Debugging Macro | C++那些事
- GCC Compilation Flags Explained | C++那些事
- Debugging C++ Programs with GDB | C++那些事