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