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