--- 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=` - 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!