Getting started with Meson build system and C++

Hello everyone. First of all, pardon my writing style and possible typos, it is my first public blog post ever and english is not my native language. So be a bit forgiving :)

I am a curious person by nature and I have been trying all kind of build systems since ever, for years. I believe to have enough experience in all of them to do an informed assessment: I tried Autotools, Waf, Scons, Tup, Make, CMake and Meson. I have a moderately popular reply about build systems in StackOverflow here.

Today I would like to talk about a build system I have been using for a while and what brought me to it and to discard the alternatives.

Just be warned that if you have to use C++ in a multiplatform setup nowadays and you need flexibility for deployment and building, you should stick to either CMake or Meson. These are the systems that are becoming more popular and for good reasons: they can handle the complexity of real life projects.


What brought you to Meson, why not CMake?

The truth is that I have been using CMake for a while. When I am working for some business, CMake is still my first choice, for a pragmatic reason: if we have to use Linux + Mac + Windows and my workmates use IDEs, the project generation is still better in CMake. CMake has an impressive project generation story: from XCode to Visual Studio to Ninja, Make and others. This is something that helps me avoid frustrating my workmates.

After using CMake for a while, I became frustrated with a few things:

  1. the scripting language is terrible
  2. inconsistencies in the design that make the learning curve difficult, especially for other people that come to it
  3. documentation leaves a lot to be desired
  4. precompiled headers? good luck
  5. cross compilation

Point 1 has had endless discussions. CMake syntax problem is more serious than it could look at first: it has taken me forever (and I still do not get it right) how to manage conditionals correctly, variable interpolation can expand to nothing at the slightest typo. Yes, I know that Modern CMake is better, but still, you have to be quite careful. In Meson variables have a type, such as in Python. Meson DSL is quite similar to Python, but not the same exactly. This can catch a lot of these frustrations before you generate your project build.

As for point 2 you have all this Modern CMake in which the target_* oriented features should be used, but all globals still exist. Meson gets this right since the beginning.

Point 3: this is one of the most important points for me: documentation in CMake is manual-oriented, without examples. Meson documentation kicks the ass of CMake documentation and it saved plenty, I mean *plenty* of time for me. It has a good reference, a sane syntax that I can parse and enough examples to get started. This combined with a sane scripting language and a consistent design, at the end, were the deciding factors to adopt it over all the other tools, including CMake.

Point 4: no need to explain this one. This is really difficult to get right actually, more than it looks and Meson supports it out of the box.

Point 5: this could be subjective, but I had such a bad time trying to cross compile with CMake that when I did it with Meson it just looked so natural and better well thought. Though I cannot make an extensive assessment about this point.

What does Meson offer over CMake that is worth the change, besides what I mention above? Meson includes by default: targets support for sanitizers, unity builds, precompiled headers support out of the box, valgrind support, ccache autodetection, a saner way to manage options (which can be typed or restricted without additional parsing) and virtually every day to day use that you have to do of it is more consistent and makes you waste less time, something that, after my CMake experience, should not be underestimated. It also has some natural hooks for preconfiguration, postinstall and other common needs that are quite handy, without messing too much with custom solutions. It also supports many common needs such as the use of Boost, Qt5, WxWidgets and others out of the box. It is also extremely fast and well optimized.

Many projects are adopting Meson lately, especially those that come from the unix world, such as systemd, Xorg and some Gnome projects. I am pretty sure at this point that Meson is going to become, as a minimum, the standard build system for non-Windows platforms. You can see a list here. Other projects such as nlohmann json provide also meson support.


So how do I get started?

Meson depends on:

  • Python3
  • Ninja build system

I assume you will have both on your path after installation.

You will also need, of course, a C++ compiler. I work primarily in Mac and Linux and these systems are working well for me. I also tried Windows some time ago and the Ninja generator was working pretty well for my needs but I did not use it for a while.

In this post I will use the Meson Github project directly just as an example, since it is quite easy to clone and start from there, so you will also need git, something you should already installed in your machine if you develop software, anyway... So let’s go ahead.

Clone the repository and get the latest stable release (0.46.1 at the time of this writing):

git clone https://github.com/mesonbuild/meson
git fetch --all --tags
git checkout 0.46.1

Create a directory for your project, a file and generate the project meson.build file, which will be at the top level. The meson build files, which, by default, generate ninja build files, are called meson.build and, as in CMake, they can be recursively invoked when needed.

mkdir startingmeson
cd startingmeson
echo “int main() {}” >> main.cpp
/path/to/meson.py init

This will generate for you a meson.build file like this:

project(‘startingmeson’, ‘cpp’,
version : ‘0.1’,
default_options : [‘warning_level=3’, ‘cpp_std=c++14’])
executable(‘startingmeson’,
‘main.cpp’,
install : true)

Now you can inspect your meson.build file. Every project has a single project declaration. In the projects you can declare the list of languages used (in our case ‘cpp’), the default options, required meson version, project version, license and others. By default this project generated an executable but you can do the usual things such as adding libraries, custom targets, run targets, run commands, generators (for generating source files to be consumed by some target), etc.

Let’s generate and compile the project. From the root of your project:

mkdir build
/path/to/meson.py build
ninja -C build

This should give you a runnable executable in build/ that you can run in build/startingmeson.


Checking and changing your current configuration

You can check the configuration of a project by issuing meson configure builddir

To change the configuration you can do meson configure -Dvar=value builddir

I encourage you to play with it and take a look at what it is possible :)


Adding a dependency

Dependencies in meson are searched and added with dependency

Some dependencies have the additional keyword argument modules

The modules are received in the form of a list if there is one or more or, optionally, as a plain string if there is only one:

boost_dep = dependency(‘boost’, modules : [‘filesystem’])
project(‘startingmeson’, ‘cpp’,
version : ‘0.1’,
default_options : [‘warning_level=3’, ‘cpp_std=c++14’])
executable(‘startingmeson’,
‘main.cpp’,
dependencies : boost_dep,
install : true)

Adding dependencies in meson is always done through the dependency command: no matter the backend is pkg-config, wx-config, qmake or something else, on the frontend it always looks the same. There are also ways to declare your own dependencies and combine them. You can take a look at the documentation.

Go add some code to your main.cpp:

#include <iostream>
#include <boost/filesystem.hpp>
int main() {
std::cout << boost::filesystem::current_path() << std::endl;
}

Now just issue a ninja -C build

Done. You have your project with a boost dependency. This was all for the first tutorial.

Keep in mind that Meson can do plenty more:

  • use valgrind, sanitizers by default if valgrind, etc: if the appropiate autodetection succeeds, targets will be added
  • unity builds: speed up your compile times.
  • precompiled headers: same story, speed up compile times
  • execute tests
  • use subprojects
  • use wrap files for adding your dependencies in subprojects

And much, much more. In my current project I used Meson to write a web service + a React frontend to do all of the following and it worked for me:

  • download a project from git at configuration time, build it and consume it as a dependency afterwards
  • use wrap dependencies in my project and use them
  • add a subdir with a ReacJs project that downloads automatically with npm all dependencies and adds a custom_target for compiling the ReactJs project
  • use a c++ library and executable for a server backend for the ReactJS project, making use of cpp-netlib
  • generate some content files from emacs with an exporter from the command line to be served by my web server
  • detect all needed programs in this pipeline in meson.build files
  • add some options for configuring the project

It still has some rough edges, such as not being able to install Meson subprojects from wraps if this is not coded in the subproject itself. But these issues are being worked on. All in all, Meson was able to serve my needs, it has some out of the box features that I value a lot and it did not waste my time with its strange scripting language, something that really frustrated me from CMake. So if you are in the situation I am where you can afford trying Meson, I highly recommend it over everything else.

Soon I plan to open-source the project I just mentioned above, so stay tuned if you want to check how Meson was used to solve my build problems.

You can find the next part of this series here.

Good luck to everyone and thanks for reading!