Introduction to meson build system
Intro
I started using meson exclusively for any new C or C++ project I create. It’s much more convenient and less cumbersome than CMake. In this post, I’ll try to give a short introduction to meson and the reasons I like it.
My typical meson project layout
My projects usually contain small libraries with a set of tests or executables relying on a bunch of libraries. For the purpose of presentation, I’ll start with a demo project, let’s call it libmagick. I’m gonna start with my typical project layout:
|
|
Right now, all these files are empty. I’m gonna start with some basic code just to get things going.
magic.h
|
|
… and the corresponding implementation file:
magic.cpp
|
|
It’s time to establish the build system. Usually, meson is able to autodetect most of the details but I like to give it a little extra nudge by specifying the language explicitly:
meson init -l cpp .
Great. Now, I’ve got a sample project file. The generated file is for an executable and I’m building a library so, I’ll adjust it accordingly:
|
|
Everything is nice and simple. Meson is a declarative language so,
libmagick_inc
defines an include directory path which is later
used when declaring a target; a shared library. It’s possible to build the
project now:
meson setup bld
meson compile -C bld
That’s it!
3rdParty dependencies and testing
Usually, you’ll want to write some tests with your code. To do that, some
testing framework is preferable. This is simple with meson. Meson
manages dependencies as subprojects.
Subproject has to live under subprojects
directory so, I’m gonna create it
now:
mkdir subprojects
Before I continue, I’m gonna try to clarify some meson parlance first.
What is a dependency?
This is described in meson documentation thoroughly but in simple terms, in
meson, you declare a dependency just as like any other target (executable or
library), the difference is that the dependency wraps the target link
paths, include paths and all other necessary details under a single declaration - which makes it super convenient to use later on. I’m gonna declare my
library libmagick_lib
as a meson dependency to demonstrate what I mean.
What is a subproject?
It can be anything really, it can be a git submodule, a separate project under
subprojects
directory a tar archive or a so called
wrap. Meson wrap
files define subprojects as a set of dependencies they provide and a way to
obtain the project - it can be fetched from external git repo, a tarball, svn.
Meson comes with a so called WrapDB which is simply a collection of wrap files for most popular projects. WrapDB is searchable. I’m gonna use it to install google test:
$ meson wrap search gtest
gtest
$ meson wrap install gtest
Installed gtest version 1.11.0 revision 2
I’ve got gtest
installed now. But what exactly happened? meson
downloaded a
wrap file for me:
$ ls -l subprojects/
total 8
-rw-r--r-- 1 tomasz staff 541 1 Oct 12:30 gtest.wrap
Here’s what’s in the file:
|
|
It contains an URL to gtest
, which will be used to obtain the package and a
set of dependencies that this library provides. I can now write some tests:
magick_test.cpp
|
|
Great! I’m gonna create a new empty meson.build
file under tests
directory
to be able to build my test:
|
|
I’m gonna need to add this subdirectory to the top-level meson file as well:
|
|
My project tree looks like so now:
|
|
It seems like the puzzle is coming along together. My meson.build
for
tests
is using gtest
subproject and obtains the declarations of gtest
and
gtest_main
dependencies from it. These are the same dependencies that are
visible in the wrap file. There’s one more improvement to be made to that
file. You might have noticed that my test is just linking directly with my
library and is referring directly to library’s include paths. Now, there’s
nothing wrong with it but it can be done better by defining the libmagick
library as a dependency, just as I mentioned before. In the top-level
meson.build
, I’m gonna add the following declarations:
|
|
This creates libmagick_dep
dependency. From now on, this can be used in
exactly the same way as gtest
, so the declaration of my test target can be
modified the following way:
|
|
Now, if I try to run
meson compile -C bld
… a lot of stuff will happen. First, meson will pull gtest
and build it and
then build my library and test:
|
|
The contents of subprojects
directory changed:
|
|
This is where meson downloaded gtest
and this is where it stores a
pre-compiled, cached version of it. These files need not to be ever committed
to git so, I like to add this simple rule to my .gitignore
to prevent
committing them:
|
|
This works well if you only use wrap files. You may have to be more specific
if you’ve got any submodules under subprojects
or manage your dependencies in
any other way.
There’s a convenient way to run all tests with meson as well:
meson test -C bld
… and here’s the output from my test:
|
|
More on testing
Meson has a couple of cool features when it comes to testing. One of my favourites is:
meson test -C bld --gdb
This gets me directly to gdb
. It’s not very useful on its own as most of the
time I want to debug a specific test. To do that, I usually combine it with a
couple of more flags:
meson test -C bld \
--gdb "magick test" \
--test-args "\-\-gtest_filter=MagickTest.test_ifNumbersAddUp"
Different types of builds
By default, meson builds in debug mode. This is clearly visible on binaries:
|
|
This can be easily changed using meson setup
invocation:
# configure build type
meson setup --builttype release bld
… or on already existing build dir:
# clear the build directory
meson setup --wipe bld
# configure build type
meson configure --buildtype release bld
Now when building, it’s clearly visible that optimisations are being enabled:
meson compile -v -C bld
|
|
In fact, meson configure
gives control over a lot more options.
Optimisations can be customised as well:
meson configure --optimization s bld
or cpp standard:
meson configure -Dcpp_std=c++20 bld
The configuration summary is presented when building:
meson compile --clean -C bld
|
|
Conclusion
For me, meson is a perfect fit. It’s an ideal non-distracting solution for building. It’s feature rich and I don’t miss anything from CMake at all.
Example project discussed in this post can be found on twdev gitlab account.