The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
259 lines
11 KiB
259 lines
11 KiB
5 years ago
|
---
|
||
|
title: Design rationale
|
||
|
...
|
||
|
|
||
|
This is the original design rationale for Meson. The syntax it describes does not match the released version
|
||
|
==
|
||
|
|
||
|
A software developer's most important tool is the editor. If you talk
|
||
|
to coders about the editors they use, you are usually met with massive
|
||
|
enthusiasm and praise. You will hear how Emacs is the greatest thing
|
||
|
ever or how vi is so elegant or how Eclipse's integration features
|
||
|
make you so much more productive. You can sense the enthusiasm and
|
||
|
affection that the people feel towards these programs.
|
||
|
|
||
|
The second most important tool, even more important than the compiler,
|
||
|
is the build system.
|
||
|
|
||
|
Those are pretty much universally despised.
|
||
|
|
||
|
The most positive statement on build systems you can usually get (and
|
||
|
it might require some coaxing) is something along the lines of *well,
|
||
|
it's a terrible system, but all other options are even worse*. It is
|
||
|
easy to see why this is the case. For starters, commonly used free
|
||
|
build systems have obtuse syntaxes. They use for the most part global
|
||
|
variables that are set in random locations so you can never really be
|
||
|
sure what a given line of code does. They do strange and unpredictable
|
||
|
things at every turn.
|
||
|
|
||
|
Let's illustrate this with a simple example. Suppose we want to run a
|
||
|
program built with GNU Autotools under GDB. The instinctive thing to
|
||
|
do is to just run `gdb programname`. The problem is that this may or
|
||
|
may not work. In some cases the executable file is a binary whereas at
|
||
|
other times it is a wrapper shell script that invokes the real binary
|
||
|
which resides in a hidden subdirectory. GDB invocation fails if the
|
||
|
binary is a script but succeeds if it is not. The user has to remember
|
||
|
the type of each one of his executables (which is an implementation
|
||
|
detail of the build system) just to be able to debug them. Several
|
||
|
other such pain points can be found in [this blog
|
||
|
post](http://voices.canonical.com/jussi.pakkanen/2011/09/13/autotools/).
|
||
|
|
||
|
Given these idiosyncrasies it is no wonder that most people don't want
|
||
|
to have anything to do with build systems. They'll just copy-paste
|
||
|
code that works (somewhat) in one place to another and hope for the
|
||
|
best. They actively go out of their way not to understand the system
|
||
|
because the mere thought of it is repulsive. Doing this also provides
|
||
|
a kind of inverse job security. If you don't know tool X, there's less
|
||
|
chance of finding yourself responsible for its use in your
|
||
|
organisation. Instead you get to work on more enjoyable things.
|
||
|
|
||
|
This leads to a vicious circle. Since people avoid the tools and don't
|
||
|
want to deal with them, very few work on improving them. The result is
|
||
|
apathy and stagnation.
|
||
|
|
||
|
Can we do better?
|
||
|
--
|
||
|
|
||
|
At its core, building C and C++ code is not a terribly difficult
|
||
|
task. In fact, writing a text editor is a lot more complicated and
|
||
|
takes more effort. Yet we have lots of very high quality editors but
|
||
|
only few build systems with questionable quality and usability.
|
||
|
|
||
|
So, in the grand tradition of own-itch-scratching, I decided to run a
|
||
|
scientific experiment. The purpose of this experiment was to explore
|
||
|
what would it take to build a "good" build system. What kind of syntax
|
||
|
would suit this problem? What sort of problems would this application
|
||
|
need to solve? What sort of solutions would be the most appropriate?
|
||
|
|
||
|
To get things started, here is a list of requirements any modern cross-platform build system needs to provide.
|
||
|
|
||
|
### 1. Must be simple to use
|
||
|
|
||
|
One of the great virtues of Python is the fact that it is very
|
||
|
readable. It is easy to see what a given block of code does. It is
|
||
|
concise, clear and easy to understand. The proposed build system must
|
||
|
be syntactically and semantically clean. Side effects, global state
|
||
|
and interrelations must be kept at a minimum or, if possible,
|
||
|
eliminated entirely.
|
||
|
|
||
|
### 2. Must do the right thing by default
|
||
|
|
||
|
Most builds are done by developers working on the code. Therefore the
|
||
|
defaults must be tailored towards that use case. As an example the
|
||
|
system shall build objects without optimization and with debug
|
||
|
information. It shall make binaries that can be run directly from the
|
||
|
build directory without linker tricks, shell scripts or magic
|
||
|
environment variables.
|
||
|
|
||
|
### 3. Must enforce established best practices
|
||
|
|
||
|
There really is no reason to compile source code without the
|
||
|
equivalent of `-Wall`. So enable it by default. A different kind of
|
||
|
best practice is the total separation of source and build
|
||
|
directories. All build artifacts must be stored in the build
|
||
|
directory. Writing stray files in the source directory is not
|
||
|
permitted under any circumstances.
|
||
|
|
||
|
### 4. Must have native support for platforms that are in common use
|
||
|
|
||
|
A lot of free software projects can be used on non-free platforms such
|
||
|
as Windows or OSX. The system must provide native support for the
|
||
|
tools of choice on those platforms. In practice this means native
|
||
|
support for Visual Studio and XCode. Having said IDEs invoke external
|
||
|
builder binaries does not count as native support.
|
||
|
|
||
|
### 5. Must not add complexity due to obsolete platforms
|
||
|
|
||
|
Work on this build system started during the Christmas holidays of 2012.
|
||
|
This provides a natural hard cutoff line of 2012/12/24. Any
|
||
|
platform, tool or library that was not in active use at that time is
|
||
|
explicitly not supported. These include Unixes such as IRIX, SunOS,
|
||
|
OSF-1, Ubuntu versions older than 12/10, GCC versions older than 4.7
|
||
|
and so on. If these old versions happen to work, great. If they don't,
|
||
|
not a single line of code will be added to the system to work around
|
||
|
their bugs.
|
||
|
|
||
|
### 6. Must be fast
|
||
|
|
||
|
Running the configuration step on a moderate sized project must not
|
||
|
take more than five seconds. Running the compile command on a fully up
|
||
|
to date tree of 1000 source files must not take more than 0.1 seconds.
|
||
|
|
||
|
### 7. Must provide easy to use support for modern sw development features
|
||
|
|
||
|
An example is precompiled headers. Currently no free software build
|
||
|
system provides native support for them. Other examples could include
|
||
|
easy integration of Valgrind and unit tests, test coverage reporting
|
||
|
and so on.
|
||
|
|
||
|
### 8. Must allow override of default values
|
||
|
|
||
|
Sometimes you just have to compile files with only given compiler
|
||
|
flags and no others, or install files in weird places. The system must
|
||
|
allow the user to do this if he really wants to.
|
||
|
|
||
|
Overview of the solution
|
||
|
--
|
||
|
|
||
|
Going over these requirements it becomes quite apparent that the only
|
||
|
viable approach is roughly the same as taken by CMake: having a domain
|
||
|
specific language to declare the build system. Out of this declaration
|
||
|
a configuration is generated for the backend build system. This can be
|
||
|
a Makefile, Visual Studio or XCode project or anything else.
|
||
|
|
||
|
The difference between the proposed DSL and existing ones is that the
|
||
|
new one is declarative. It also tries to work on a higher level of
|
||
|
abstraction than existing systems. As an example, using external
|
||
|
libraries in current build systems means manually extracting and
|
||
|
passing around compiler flags and linker flags. In the proposed system
|
||
|
the user just declares that a given build target uses a given external
|
||
|
dependency. The build system then takes care of passing all flags and
|
||
|
settings to their proper locations. This means that the user can focus
|
||
|
on his own code rather than marshalling command line arguments from
|
||
|
one place to another.
|
||
|
|
||
|
A DSL is more work than the approach taken by SCons, which is to
|
||
|
provide the system as a Python library. However it allows us to make
|
||
|
the syntax more expressive and prevent certain types of bugs by
|
||
|
e.g. making certain objects truly immutable. The end result is again
|
||
|
the same: less work for the user.
|
||
|
|
||
|
The backend for Unix requires a bit more thought. The default choice
|
||
|
would be Make. However it is extremely slow. It is not uncommon on
|
||
|
large code bases for Make to take several minutes just to determine
|
||
|
that nothing needs to be done. Instead of Make we use
|
||
|
[Ninja](https://ninja-build.org/), which is extremely fast. The
|
||
|
backend code is abstracted away from the core, so other backends can
|
||
|
be added with relatively little effort.
|
||
|
|
||
|
Sample code
|
||
|
--
|
||
|
|
||
|
Enough design talk, let's get to the code. Before looking at the
|
||
|
examples we would like to emphasize that this is not in any way the
|
||
|
final code. It is proof of concept code that works in the system as it
|
||
|
currently exists (February 2013), but may change at any time.
|
||
|
|
||
|
Let's start simple. Here is the code to compile a single executable binary.
|
||
|
|
||
|
```meson
|
||
|
project('compile one', 'c')
|
||
|
executable('program', 'prog.c')
|
||
|
```
|
||
|
|
||
|
This is about as simple as one can get. First you declare the project
|
||
|
name and the languages it uses. Then you specify the binary to build
|
||
|
and its sources. The build system will do all the rest. It will add
|
||
|
proper suffixes (e.g. '.exe' on Windows), set the default compiler
|
||
|
flags and so on.
|
||
|
|
||
|
Usually programs have more than one source file. Listing them all in
|
||
|
the function call can become unwieldy. That is why the system supports
|
||
|
keyword arguments. They look like this.
|
||
|
|
||
|
```meson
|
||
|
project('compile several', 'c')
|
||
|
sourcelist = ['main.c', 'file1.c', 'file2.c', 'file3.c']
|
||
|
executable('program', sources : sourcelist)
|
||
|
```
|
||
|
|
||
|
External dependencies are simple to use.
|
||
|
|
||
|
```meson
|
||
|
project('external lib', 'c')
|
||
|
libdep = find_dep('extlibrary', required : true)
|
||
|
sourcelist = ['main.c', 'file1.c', 'file2.c', 'file3.c']
|
||
|
executable('program', sources : sourcelist, dep : libdep)
|
||
|
```
|
||
|
|
||
|
In other build systems you have to manually add the compile and link
|
||
|
flags from external dependencies to targets. In this system you just
|
||
|
declare that extlibrary is mandatory and that the generated program
|
||
|
uses that. The build system does all the plumbing for you.
|
||
|
|
||
|
Here's a slightly more complicated definition. It should still be
|
||
|
understandable.
|
||
|
|
||
|
```meson
|
||
|
project('build library', 'c')
|
||
|
foolib = shared_library('foobar', sources : 'foobar.c',\
|
||
|
install : true)
|
||
|
exe = executable('testfoobar', 'tester.c', link : foolib)
|
||
|
add_test('test library', exe)
|
||
|
```
|
||
|
|
||
|
First we build a shared library named foobar. It is marked
|
||
|
installable, so running `ninja install` installs it to the library
|
||
|
directory (the system knows which one so the user does not have to
|
||
|
care). Then we build a test executable which is linked against the
|
||
|
library. It will not be installed, but instead it is added to the list
|
||
|
of unit tests, which can be run with the command `ninja test`.
|
||
|
|
||
|
Above we mentioned precompiled headers as a feature not supported by
|
||
|
other build systems. Here's how you would use them.
|
||
|
|
||
|
```meson
|
||
|
project('pch demo', 'cxx')
|
||
|
executable('myapp', 'myapp.cpp', pch : 'pch/myapp.hh')
|
||
|
```
|
||
|
|
||
|
The main reason other build systems can not provide pch support this
|
||
|
easily is because they don't enforce certain best practices. Due to
|
||
|
the way include paths work, it is impossible to provide pch support
|
||
|
that always works with both in-source and out-of-source
|
||
|
builds. Mandating separate build and source directories makes this and
|
||
|
many other problems a lot easier.
|
||
|
|
||
|
Get the code
|
||
|
--
|
||
|
|
||
|
The code for this experiment can be found at [the Meson
|
||
|
repository](https://sourceforge.net/p/meson/code/). It should be noted
|
||
|
that it is not a build system. It is only a proposal for one. It does
|
||
|
not work reliably yet. You probably should not use it as the build
|
||
|
system of your project.
|
||
|
|
||
|
All that said I hope that this experiment will eventually turn into a
|
||
|
full blown build system. For that I need your help. Comments and
|
||
|
especially patches are more than welcome.
|