---
title: Setting Up Crystal on ARM
date: 2019-03-02 06:47:50.027
tags: ["Crystal", "ARM"]
---
Having found myself using a Chromebook with Arch Linux, I acutely felt the lack of official [Crystal](https://crystal-lang.org) 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!
