As developers, we think it is essential to have a building system that eases our work, allows us to compile Rizin quickly on a wide range of devices, is easy to understand and to modify, and provides a nice set of features one would usually expect from a full-fledged building system. Since its inception, Rizin has focused on improving its Meson build files and making its support first-class while deprecating the original building system used in radare2. In the following article, we will explain the reasons behind this choice and the key benefits of Meson.
- Meson is declarative and easy to understand
- Ninja is fast, no files are recompiled if not necessary
- Meson keeps your source directory clean with out-of-source builds
- Meson makes it easy to build and run multiple versions of Rizin
- Meson simplifies dependency handling and switching from internal dependencies to system-provided ones
A bit of context
Historically radare2 has been compiled with the usual
approach. This essentially consists of a shell script,
configure, and a set
configure allows the user to customize the compilation and
installation process performed by
make by setting, for example, the
destination directories where executables, libraries, etc. are installed on
the system. It is also used to enable or disable specific features (e.g. the
debugger) or to check for the existence of specific libraries, header files,
functions, compiler or linker arguments.
To some, this may be very similar to what is done by Autotools. However, in
configure is generated by another shell script,
by parsing a
acr is a tool developed by the original
author of radare2, and it is an Autoconf replacement.
During the years some attempts were made to introduce other build systems, like Jam and a NodeJS-based build system. It was only in 2017 that radare2 started introducing Meson. Since then, many people have improved this system to compile on several platforms and making sure it is (almost) feature-wise on par with the ACR/Make build system.
Rizin has chosen to deprecate the use of ACR/Make and switch to Meson as the main build system. We believe this will make the overall build process more standard, easy to understand, and easy to integrate with other tools/libraries. Other very valid alternatives such as CMake were considered, however we preferred to keep working with Meson, which was already tested and tried with Rizin for a long time, rather than starting completely from scratch with another build system.
Problems with ACR/Make
There are of course several reasons for this choice, so let’s first see what we have identified as the problems of the historical approach:
- ACR is essentially a one-person project, with mostly only radare2 and other radare-related tools using it. This by itself is not a bad thing, but it comes with the downside that you find no help or documentation online and if you have issues or missing features, you have to rely on one person only who understand its internals. Moreover, the features you find are usually just the ones used by radare2 project (e.g. not long ago, it was not possible to easily check if the compiler supported a particular compilation flag, because it was never necessary for radare2).
configurescript needs a
shshell, which makes it hard to use on platforms such as Windows. There are of course ways to use it, but they may involve installing MinGW or similar, which may not be ideal for Windows users who usually work within Visual Studio.
- Makefiles can be written in a very flexible way and they can be used to
perform any sort of action, from simply compiling a C file to running
scp, various scripts, and much more. Flexibility shall not be abused though. Otherwise, it may become hard to understand how things are actually done. For example, understanding how
librz_io.sois compiled involves looking at the Makefile in
libr/io, which includes
config.mkthat setups some variables based on other variables defined in the Makefile and then it includes
rules.mk, which uses those variables to actually compile the library. Inside
rules.mkyou find, hidden with various environment variables, the commands used to build the object files, and then the library. You can look at the compilation command here, which we think is hard to grasp from a quick look even for people familiar with radare2/Rizin codebase (you may wonder where to find
config.mkmentioned above: it is auto-generated).
- It is “low-level”, which means that the Makefiles define the specific commands, flags, and options that you have to use to actually compile a binary, a library, or an object file. This provides a lot of power, but it may also be overwhelming having to remember to add specific compilation/linking flags for compiling a single file. For example, it is not possible yet to compile radare2/Rizin within a directory with spaces in the name due to limitations within GNU Make.
- ACR/Make cannot be used as-is to compile Rizin on Windows systems.
What we like about the Meson Build System
It is declarative, which means you don’t have to remember or care about how to actually compile a shared library or a static library on Linux, Windows, BSD, etc. or how to link an executable with some other libraries or make sure include paths are right. As an example, look at this piece of
library('io', ['file1.cpp', 'file2.cpp'], dependencies: [util_dep], install: true, soversion: rz_asm_lib.version() )
file2.cpp), name the library
io(e.g. on Linux the library would be called
libio.so, but the full name and the extensions might be different on Windows) and give it the proper API version, make sure the dependency specified by
util_dep, whatever it is, is used to compile this library, by adding the proper include paths and link directives.
It is fast. This is extremely important for developers, as while developing a feature or fixing a bug they may need to compile Rizin multiple times and we want this process to be as fast as possible. Meson/Ninja performs quite well compared to other build systems (https://mesonbuild.com/Simple-comparison.html). It forces you to list all source files used to compile a target and it is able to automatically compute other dependencies between targets. In ACR/Make, due to its complexity as implemented in radare2/Rizin and to the low-level approach, it is easy to mess with the dependencies between targets and to recompile multiple times the same files even when there are no changes. For example, until very recently, running
makemultiple times caused the recompilation of several objects even if no file was changed (in last few months this problem was caused by wrong dependencies of
sdb, in the past due to wrong dependencies of the
mesoncan run everywhere
python3can. This includes a very wide range of platforms nowadays. It automatically provides a very powerful scripting language, python, that you are guaranteed to find on the build machine. Moreover, it can be used with various backends, like Ninja, Visual Studio and Xcode, which means it can be used to generate a Visual Studio solution that you can import there.
It forces you to build out-of-source, meaning that no changes (mostly) will be done to your source directory, which must contain only the source files of your project and not be mixed with other auto-generated files like executables or object files. This also allows you to have the project compiled with different options or with slightly different code, cleanly separated in different directories.
Due to its declarative nature, it does not matter whether a dependency is in a path or another or if it comes from the system or it was bundled with the source code. You just define
capstone_depvariable properly in one of your
meson.buildfiles and you reference it wherever it is needed, leaving all the details to
mesonitself. This encourages splitting the repository into sub-projects when it makes sense, in contrast with the ACR/Make system where even a small change to e.g. SDB path would require rewriting several Makefiles. If in the future some systems will ship their own version of SDB, we would just need to change few lines in the definition of
sdb_depto actually take the system library instead of the bundled one and no other place would need to be changed to make sure everything is compiled/linked with the right headers/libraries.
In case of problems with
mesonthere is a healthy community out there ready to help you, a nice and extensive documentation and active developers that improve the system with new releases. New developers who want to work on our build system can easily find other examples online and have available documentation to get them up to speed.
Examples of using meson
As a developer when you download Rizin, you can install it for your user in
~/.local, so you don’t need root access to install files. You can do this
meson --prefix=~/.local build; ninja -C build install. After that, you
can change the source code however you need and then run
ninja again with
ninja -C build. Only the changed files are re-built.
ninja by default builds files with explicit RPATHs, which
means that the executables and libraries contain direct references to the
paths of dependent libraries they are linked against so the loader can then
always find them without having to specify
LD_LIBRARY_PATH or similar. For
this reason, most of the times you will not need to re-install the Rizin
files, but while developing you can just run rizin from
RPATH are not, of course, always good. Indeed they are usually removed during
the installation process. However, when you install Rizin in a place that is
/usr, we have chosen to keep RPATHs to make the installation process as
simple as possible, without requiring users to mess with their environment to
make sure the binaries can find the proper libraries. Packagers, who usually
/usr as a prefix, should not be affected by this decision, but they can
anyway disable it by specifying
-Dlocal=false when running meson.
Reviewing a PR and testing changes
When testing a PR with a fix or comparing multiple changes, you need to have
access to multiple versions of Rizin. Doing this with ACR/Make is of course
possible, but it usually involves installing everything in separated
directories and making sure your environment variables (e.g.
LD_LIBRARY_PATH, etc.) are correctly set. With meson, you can build one
version (e.g. from
dev branch) with
meson --prefix=~/.local build-dev; ninja -C build-dev, then switch branch with
git checkout my-other-branch
and build Rizin again with
meson --prefix=~/.local build-pr; ninja -C build-pr. Due to the RPATH used by default, as mentioned above, each build
directory can be used without installation to actually run the Rizin tools.
At that point, you can quickly compare the results of
Of course it’s not all perfect with meson either. Right now the meson build system is missing some features that were only available with ACR/Make.
To uninstall Rizin you have to run
ninja -C build uninstall from the same
build directory you used to run the
install step, otherwise, it will not
uninstall files. However if during
install step we add any custom
installation script (e.g. to sign your rizin binary in macOS), there is no
counter part to actually have an uninstall script. That said, nothing
prevents us from having a custom target similar to what ACR/Makefile system
does to manually remove, with a script, the installed files, but we believe
proper file tracking should be done by distributions and packages.
Meson is quite new and, although rare, you may find issues from time to time. That said, its community is healthy and active so you can count on them to fix these problems as soon as possible or provide help, also thanks to the many big projects that have switched to meson in the last years.
All in all, we hope to make it easier for our developers and users to build Rizin. We are trying to build a good Reverse Engineering Framework and we want to focus our efforts on this rather than dealing with the limitations of a niche build system.
If you find issues or find particular installation setups difficult or missing, feel free to open a bug in GitHub and we will be happy to either guide you through a solution or develop the fix according to our roadmap.