Having found myself using a Chromebook with Arch Linux, I acutely felt the lack of official Crystal builds for ARM. After spending an hour or so looking online for a comprehensive guide to getting a working compiler on ARM, I think I now know the general idea. I will lay it out here, for myself, as well as for others who may be in the same situation.

To compile Crystal, you need Crystal

To compile Crystal without bootstrapping it from older versions, you need a different machine which is capable of cross-compilation. Fortunately for me, my friends and I have previously set up a server hosting several x86_64 virtual machines, one of which we dedicated to cross-compiling various programs.

To get started, I had to download the compiler on that machine:

sudo pacman -S crystal

Note that this isn't the ARM Chromebook. Running this command on the Chromebook would not work.

Building on the x86_64 Machine

After getting the compiler, I also needed to download the compiler source. This was done using git:

git clone https://github.com/crystal-lang/crystal.git

I also installed llvm6, which is required on both the machine that's building the compiler and the machine for which the compiler is being built:

sudo pacman -S llvm6

From here on in, I ran commands from inside the directory that was downloaded via git clone:

cd crystal

Finally, I didn't want to compile the "master" version of the compiler. It wasn't a release! To check out the latest release (0.27.2 at the time of writing), I used git:

git checkout 0.27.2

Now, I had the compiler and the source. I was ready to compile the source to get myself a nice ARM Crystal compiler. But how? The official guide specified two options for cross compilation:

  • --cross-compile - This option is basically a flag. You just add it to the command to enable cross compilation.
  • --target=<llvm target triple> - This specifies the target architecture you're building for.

In order to get the second option right, I had to know the LLVM target triple for my target machine. To find it, I ran the following command on that machine:

gcc -dumpmachine

This produced the output armv7l-unknown-linux-gnueabihf. This was exactly what I needed to know!

Finally, looking through the Makefile in the repository, I found three more flags that are used by default in the process:

  • -D without_openssl
  • -D without_zlib
  • --release - for faster compiler

To compile the compiler, I had to compile the src/compiler/crystal.cr file. With all these options, the command came out to be:

crystal build src/compiler/crystal.cr --cross-compile --target=armv7l-unknown-linux-gnueabihf -D without_openssl -D without_zlib --release

There is only one more trick to cross-compiling Crystal: although the official guide specifies the options --cross-compile and --target=..., and although you can just attempt to use the crystal command on the source file, this won't work. You need to use the wrapper script that Crystal provides. I had to replace crystal with ./bin/crystal:

./bin/crystal build src/compiler/crystal.cr --cross-compile --target=armv7l-unknown-linux-gnueabihf -D without_openssl -D without_zlib --release

With this, I finally obtained a crystal.o file. After downloading this onto my target machine, and making sure to copy the command the compiler printed out, I was ready to proceed.

Compiling on the Target ARM Machine

Just like with the x86_64 machine, I needed llvm6:

sudo pacman -S llvm6

I also needed the Crystal source, again!

git clone https://github.com/crystal-lang/crystal.git && cd crystal
git checkout 0.27.2

Finally, I needed a few more libraries. These are gc (the Garbage Collector Crystal uses) and libevent:

sudo pacman -S gc libevent

With these dependencies installed, I could compile the last two files needed to build a working compiler:

make deps

After this, the command I noted down from earlier (on the x86_64 machine) was all that was left (note that I placed crystal.o in the clone of the Crystal repository):

cc 'crystal.o' -o 'crystal'  -rdynamic src/llvm/ext/llvm_ext.o `/usr/bin/llvm-config --libs --system-libs --ldflags 2> /dev/null` -lstdc++ -lpcre -lm -lgc -lpthread src/ext/libcrystal.a -levent -lrt -ldl -L/usr/lib -L/usr/local/lib

This produced a fresh, new crystal executable!

I was not done. The executable couldn't compile a basic "Hello, world"! This is because I once again needed the wrapper script. This script searches the .build directory for the Crystal executable, and so I placed it there:

mkdir .build
mv crystal .build

Finally, I made sure to add the ./bin directory to my PATH.

Shards

Crystal is not complete without its package manager, shards. This program doesn't need to be cross compiled, but it does come separately from the compiler. First, I cloned the repository:

git clone https://github.com/crystal-lang/shards.git

Then, I installed libyaml, which is necessary to compile shards:

sudo pacman -S libyaml

And finally, I ran the Makefile provided in the repository:

make

I once again added the ./bin directory to my path. And finally, a working Crystal environment!