Skip to content

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

https://github.com/mesonbuild/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:

python3 -m pip install meson

If you build with Ninja, you can download Ninja directly from the Ninja GitHub releases page or via PyPI:

python3 -m pip install ninja

For more information about installing the Meson build system, read the official Meson website below.

https://mesonbuild.com/Getting-meson.html

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.

https://mesonbuild.com/Users.html

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.

http://mesonbuild.com

2.Disadvantages

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
meson wrap search 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
mkdir -p subprojects
meson wrap install gtest

At this point, the third-party libraries your project depends on are all installed in the subprojects directory.

➜  gtest_div tree -L 1 subprojects
subprojects
├── gtest.wrap

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

double divide(double a, double b);

Put the .h and .cc files in the src directory:

src
├── division.cc
└── division.h

Then create a test directory at the same level.

test
└── division_test.cc

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:

project('gtest', 'cpp',
  version : '1.14.0',
  default_options : ['cpp_std=c++14'],
  license : 'bsd')

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:

e = executable('prog', 'testprog.c')
test('name of test', e)

You can use meson test or ninja test; after the run a test log is produced, located at:

build/meson-logs/testlog.txt

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 ...

meson setup build

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.

subprojects
├── googletest-1.14.0  # here
├── gtest.wrap
└── packagecache # here

4.3.2 Compile

We can use either of the following two commands. The first:

meson compile -C build/

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:

➜  gtest_div ninja -C build
ninja: Entering directory `build'
[5/5] Linking target division_test

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

meson test

Or:

ninja test

To specify a directory:

meson test -C build/

or 

ninja test -C build/

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

Comments