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.

TL;DR

  • 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 ./configure; make approach. This essentially consists of a shell script, configure, and a set of Makefiles. 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 radare2/Rizin case, configure is generated by another shell script, acr, by parsing a configure.acr file. 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).
  • configure script needs a sh shell, 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.so is compiled involves looking at the Makefile in libr/io, which includes config.mk that 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.mk you 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.mk mentioned 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 meson.build:

    library('io',
      ['file1.cpp', 'file2.cpp'],
      dependencies: [util_dep],
      install: true,
      soversion: rz_asm_lib.version()
    )
    

    You don’t need to know how meson is going to build your library, but it is going to do it by compiling two source files (e.g. file1.cpp and 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 make multiple 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 capstone target).

  • meson can run everywhere python3 can. 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_dep variable properly in one of your meson.build files and you reference it wherever it is needed, leaving all the details to meson itself. 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_dep to 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 meson there 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.

  • Many complex low-level pure C projects recently switched to Meson: Mesa, Wayland, PipeWire, QEMU, and many others. We are not alone in this!

Examples of using meson

Development process

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

Moreover, running 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 ./build/binrz/rizin/rizin.

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 not /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 use /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. PATH, 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 ./build-dev/binrz/rizin/rizin and ./build-pr/binrz/rizin/rizin.

Conclusion

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.