Cross-compiling Crystal for the Raspberry Pi (Updated for V1.4+)

Since the first time I wrote about cross compiling Crystal apps for the Raspberry Pi, many great things have happened with the Crystal language, including the official release of V1.0. Unfortunately, an official build for ARM processors isn’t among those changes. Thankfully though, there have been changes to the Crystal compiler that make cross compiling easier than in previous versions. So here I’ll outline the steps needed.

For this blog, I’m cross compiling from a Macbook Pro running Catalina, to a Raspberry Pi 2 running Ubuntu Server. There may be changes based on which OS you’re using on either your development or target device, but hopefully they’ll be minor. At the very least, the steps should be the same for any Debian based distro. If you have any issues with it, please let me know in the comments.

Preparing the Pi

As with previous versions, the first thing we should do is install any necessary packages on the Pi.

$ apt install llvm libgc-dev libpcre3-dev libevent-dev

For sake of completion, here’s what all of these packages do. Feel free to skip this part over if you’re not interested in these details. You’ll still be able to complete the process.

  • llvm – A set of compiler and toolchain technologies. The Crystal compiler uses the llvm to build efficient native code. In our case, it’s able to give information required for cross compiling code for another processor architecture.
  • libgc-dev – The Boehm–Demers–Weiser garbage collector, which the garbage collector used by the Crystal language. This library is one of the reasons why Crystal needs an operating system to run, and can’t be used on bare metal platforms.
  • libpcre3-dev – Perl Compatible Regular Expressions library. A common regular expression library used by the Crystal language.
  • libevent-dev – This library provides asynchronous event notifications and a mechanism for event based callbacks.

If you’ve read the previous article before coming here, this is the first big update. There’s no longer any need to have a libcrystal.a file with the newer versions of the compiler. So now we can move on to determining in the target flag we use when cross compiling.

$ llvm-config --host-target

This command provides a string describing the target flag that we’ll need when running the compiler on our main computer. In my case, it returned armv7l-unknown-linux-gnueabihf, but depending on what version of the Pi you’re using, yours may be slightly different.

Cross-Compiling in macOS

I’m using the same one line app as the previous article, saved as hello.cr:

puts "Hello, world!"

The command to compile is similar to last time:

$ crystal build hello.cr --cross-compile --target "armv7l-unknown-linux-gnueabihf"

The italicized part of this command should be replaced with whatever you saw on your Pi when you ran the llvm-config --host-target command. This command produces the hello.o file, and provides a shell script to link the file. For me, the instruction looked like this:

cc hello.o -o hello -rdynamic -L/usr/local/lib -lpcre -lm -lgc -lpthread -L/usr/local/Cellar/libevent/2.1.12/lib -levent -lrt -lpthread -ldl

If you followed the previous article to this one, you’ll notice that this output is much simpler, and doesn’t have any references to libcrystal.a. This is a key improvement over previous versions of the compiler, removing an entire step on preparing the Pi.

The other key improvement is that this shell script is exactly what you need to run on the Pi to link your hello.o file, so make sure you don’t erase it. Personally, I save the script to a .sh file, and use scp to copy it along with the .o file.

Linking on the Pi

We’ve reached the final step, but this is also the step where we find out if we made any mistakes.

Copy your hello.o file over to your Pi, and run the shell script that you were provided by the Crystal compiler. If there are any necessary libraries that you forgot to install on the Pi during the first section, this is where you’ll get an error, about an unknown flag. Unfortunately, there’s no simple library to tell you what library you need to install for each flag, so you’ll have to look it up for yourself.

An important thing to note here is that this shell script is unique to the Crystal program that we’re compiling. This means that larger, more complex Crystal programs may have additional flags in this script, and thus, you’ll have to install the proper libraries to your Pi before running this script.

If everything went according to plan, you should have your hello file in your directory. Run it, and you’ll get your expected output:

Hello, world!

Congratulations. You’ve now cross compiled a Crystal program and run it on your Raspberry Pi.

Conclusion

There’s one more caveat that I want to mention. Since there’s no official Crystal build for ARM processors, there are still a few bugs when cross compiling. Apps that run perfectly fine on my Mac, often produce unexpected outputs on my Pi.

The good news is that while ARM isn’t an officially supported platform, the Crystal core team does know that they’ll have to adopt it sooner or later (especially with the M1 MBPs on the market), and do welcome issues raised on Github regarding any problems.