Meson — a new build system

Divide and Conquer

To compile your source code, you first need to create a new subdirectory that stores all the output from the compiler. This build directory is mandatory: Meson insists on a separate directory and cannot be tricked into storing the object files in the source directory.

This apparent tutelage has several advantages: For one, it keeps the files output from the compiler separate from the source code, improving understandability, especially of complex projects, and making sure the object files do not fall into the clutches of a version control system. On the other hand, you can simultaneously create several independent builds with different configurations. Thus, one build directory could contain the release version while a second houses a debug version.

How you name these build directories, and where they are, is up to you. In most cases, the default build directory is located at the top level of the source directory and is simply called build:

cd helloworld/src
mkdir build

The developer has to let Meson set up the new build directory (see also Figure 1):

Figure 1: If you call meson from the build directory, as shown here, you simply need to specify the source code directory.
meson ~/helloworld/src ~/helloworld/src/build

The first parameter specifies the directory containing the source code, which also houses the meson.build file. The second parameter reveals the build directory. In it, Meson now creates all the necessary configuration files or build files for Ninja. From this, the developer can now build the actual program:

cd build
ninja

Ninja automatically integrates all available processor cores. If you later want to recompile your program, a call to ninja in the build directory is all it takes. Further commands are not required. Meson has set up Ninja so that it automatically detects source code file changes and only rebuilds these – even with a subsequent edit of the meson.build file.

By default, the compiler inserts debug information in the binaries and outputs all warnings. If the GCC compiler is used, Meson enables the -g and -Wall parameters. If you need an optimized program for a release, you will want to call Meson with the --buildtype=release parameter. How to take further control over the compilation process is reveals in the "Full Control" box.

Full Control

Meson decides for itself which compiler to invoke and with what parameters. However, developers occasionally want to control the compilation process themselves – for example, to put together a package for a specific distribution. Again, this is no problem. You can specify which compiler to use with environment variables. For example, to use Clang instead of GCC:

CC=clang meson ~/helloworld/src   \
  ~/helloworld/src/build

You can even specify or override compiler flags by setting the appropriate environment variables before calling meson. The --buildtype=plain also disables Meson's own defaults:

CFLAGS="-o2" meson  \
  --buildtype=plain  \
  ~/helloworld/src ~/helloworld/build

If you want Ninja to install the compiled program, the target directory can be set in the DESTDIR environmental variable:

DESTDIR=/opt/helloworld ninja install

Or, you can simply specify a different prefix with: ninja install --prefix /usr

On Linux, Meson always uses Ninja as the back end. A project for Visual Studio 2010 is created by the --backend=vs2010 parameter; Meson generates the equivalent for Xcode with --backend=xcode. Ninja can also install the compiled program directly. To this end, you just call ninja install.

Object of Desire

The programming language used by Meson in meson.build is object-oriented. Although developers cannot implement classes themselves, some functions do return an object. For example, shared_library() returns an object that represents the corresponding build target. This can be captured in a variable:

lib = static_library('hello', ['lib1.c','lib2.c'])

You then need to pass the object as an argument to another function – for example, to executable() which links the program directly against the library:

executable('hello', 'main.c', link_with : lib)

If you use the functions defined in lib2.c, you will probably want to check them with a test program first. You do not need to link this against the complete dynamic library – just against the lib2.c object file.

Luckily, the object returned by shared_library() itself has an extract_objects method, which in turn returns an object file. You need to invoke this method in dot notation as in many object-oriented languages:

obj = lib.extract_objects('lib2.c')

The returned object can now be linked with the program:

executable('hello', 'main.c', objects : obj)

Many other objects support methods that programmers can call and use in a similar way.

Walking the Tree

If the source code is stored in subdirectories, the subdir function changes to one of the subdirectories – in the following example, to the gui/ directory:

subdir('gui')

Meson then evaluates the configuration file meson.build in this directory. However, it must not use a project function, because the parent meson.build already defines a project. If the header files for C or C++ projects are stored in the include subdirectory, you can add them to the compiler's search path as follows:

header = include_directories('include')
executable('hello', 'main.c',include_dirs : header)

Most major projects use external libraries like zlib or the Gtk toolkit. To include such a library, only two lines are needed:

gtk = dependency('gtk+-3.0')
executable('hello', 'main.c', deps : gtk)

The dependency() function first checks to see whether the library is installed on the system. If it is missing, it aborts the build process; otherwise, Meson links it with the hello program.

This automatic mechanism works with all libraries that supply a pkg-config. Fortunately, Meson natively supports some particularly well-known frameworks without this file. Currently, these include Boost, Qt5, Google Test (gtest), and Google Mock (gmock).

In the case of Boost, you can thus easily select the desired modules using the following:

boost = dependency('boost', modules : ['thread', 'utility'])

Meson supports if conditions that allow for more elaborate tests. Listing 2 shows an example: In a C project, it checks whether the sys/fstat.h header file is available. To be able to do this, Meson is handed the compiler in the first line. The second line then asks the compiler whether the header file exists (see Figure 2).

Listing 2

Conditional Branching

 

Figure 2: Ninja tells you the result of the if statement in its own output later on. In this case, this leads to an error in building the no_hello program.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Gnome Recipes

    Cutting recipes out of magazines and attaching them to the fridge is a thing of the past. Today, Gnome Recipes is your friendly kitchen helper.

  • Fedora 26 Beta Comes with New Features

    The workstation comes with the latest Gnome desktop environment and support for many application build systems.

  • Buildroot

    Whether you need a tiny OS for 1MB of flash memory or a complex Linux with a graphical stack, you can quickly set up a working operating system using Buildroot.

  • NEWS

    Updates on technologies, trends, and tools

  • Qmake for Qt

    Qt’s own build system Qmake is often overlooked for larger projects, but many experienced developers appreciate Qmake support for shadow builds and pre- or post-build dependencies.

comments powered by Disqus