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 a 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!