Cross-compiling Rust for the Raspberry Pi on macOS
The following will take you down the rabbit hole of cross-compiling a simple binary (written in Rust) for your Raspberry Pi 2. Using macOS. And a tool called crosstool-NG. It’s more than a few steps long, so buckle up and let’s get started!
A note: if you’re interested in a (hopefully) simpler solution and are comfortable using Docker, you can try following these instructions for cross-compiling via Docker container. I haven’t tried it myself, so I make no guarantees.
I’ll assume you’ve already installed Rust. Create your “Hello, world!” project in whatever Rust workspace you choos (for this example, I’ll say it’s /Users/USER/rust/
):
cargo new hello --bin
cd hello
You can cargo run
and see it compile and spit out our favorite programming phrase:
Compiling hello v0.1.0 (file:///Users/USER/rust/hello)
Finished dev [unoptimized + debuginfo] target(s) in 1.16 secs
Running `target/debug/hello`
Hello, world!
Great! Here’s where we start getting to the good stuff. You need to now add a new toolchain “target” (via rustup
). Specifically the armv7-unknown-linux-gnueabihf
target. Adding this target will give you a version of the rust-std
library that allows you to compile binaries for the Pi. To get the target, run:
rustup target add armv7-unknown-linux-gnueabihf
You can verify that it’s ready for use with a rustup show
(don’t mind the few extra toolchains I have installed):
Default host: x86_64-apple-darwin
installed toolchains
--------------------
stable-x86_64-apple-darwin (default)
nightly-x86_64-apple-darwin
1.21.0-x86_64-apple-darwin
installed targets for active toolchain
--------------------------------------
armv7-unknown-linux-gnueabihf
x86_64-apple-darwin
active toolchain
----------------
stable-x86_64-apple-darwin (default)
rustc 1.21.0 (3b72af97e 2017-10-09)
Now run cargo build
(because you can’t cargo run
ARMv7 binaries on macOS) with your new target:
cargo build --target=armv7-unknown-linux-gnueabihf
And you get…oh:
Compiling hello v0.1.0 (file:///Users/USER/rust/hello)
error: linking with `cc` failed: exit code: 1
...
... (long block of output)
error: aborting due to previous error
error: Could not compile `hello`.
No dice. But why? I asked this question on the #rust IRC channel and got my answer: the missing piece was a cross-compiling C toolchain. Specifically, the C linker. You see, the way I understand it is that the rust-std
library relies on glibc
for things like syscalls and other low-level stuff (if this is a gross misstatement, please correct me!). In order to cross-compile a Rust binary, one needs the appropriate C toolchain to be present as well. And this is where crosstool-NG comes into play.
crosstool-NG is in the toolchain building business. You’re going to use it to build yourself a toolchain for linking against ARMv7-compatible glibc
, which will in turn allow you to successfully build your Rust binary for the Pi.
Start by going to the macOS section of this page for brew install
ing the prerequisites. Next, follow the instructions to clone the repo to a good location and bootstrap
it:
cd /Users/USER
git clone https://github.com/crosstool-ng/crosstool-ng
cd crosstool-ng
./bootstrap
Now configure the installation and run it. To set where the tool goes on install, I ran ./configure
with the --prefix
option set to $PWD
(which should expand to /Users/USER/crosstool-ng
):
./configure --prefix=$PWD
make
make install
export PATH="${PATH}:${PWD}/bin"
If all things went as expected, you should be able to run ct-ng version
and verify the tool’s ready to go.
Cool! Before you can run the tool, you’ll need to configure it to build your ARMv7 toolchain. Luckily, crosstool-NG comes with some preset configurations, namely armv7-rpi2-linux-gnueabihf
. Run:
ct-ng armv7-rpi2-linux-gnueabihf
There should be some output indicating that it’s now configured for armv7-rpi2-linux-gnueabihf
. You just need to tell ct-ng
where the toolchain ought to go. We do this with menuconfig
:
mkdir /Users/USER/ct-ng-toolchains
cd /Users/USER/ct-ng-toolchains
ct-ng menuconfig
It can be overwhelming, as there are a ton of options, but stick to the Paths and misc options --->
menu option. Highlight it and hit Enter.
Under *** crosstool-NG behavior ***
, scroll down until you see this long string:
(${CT_PREFIX:-${HOME}/x-tools}/${CT_HOST:+HOST-${CT_HOST}/}${CT_TARGET}) Prefix directory
Let’s update it with our custom directory. Hit Enter, delete the contents, and replace it with /Users/USER/ct-ng-toolchains
. When you’re finished, hit Enter to confirm, scroll over and save, and then exit the configurator.
If you’ve made it this far, pat yourself on the back; you’re about to kick-off your toolchain creation! It only takes one command:
ct-ng build
You can step away from you’re computer if you want, because this is gonna take a bit (on my Macbook Pro, it took about ~30 minutes). Since there’s a few things that could go wrong here, I can’t offer many tips for troubleshooting beforehand. But feel free to reach out to me if you can’t figure out an issue on your own.
If it worked successfully, huzzah! You should see a great many binaries now in /Users/USER/ct-ng-toolchains/armv7-rpi2-linux-gnueabihf/bin
, namely armv7-rpi2-linux-gnueabihf-gcc
.
For cargo to build using your new cross-compiler, you must:
- add the
bin
folder listed above to your PATH:export PATH="${PATH}:/Users/USER/ct-ng-toolchains/armv7-rpi2-linux-gnueabihf/bin"
- update (or create) your global
/Users/USER/.cargo/config
file with:[target.armv7-unknown-linux-gnueabihf] linker = "armv7-rpi2-linux-gnueabihf-gcc"
Return to your Rust project and rerun cargo build
:
cd /Users/USER/rust/hello
cargo build --target=armv7-unknown-linux-gnueabihf
The output should be something similar to:
Compiling hello v0.1.0 (file:///Users/USER/rust/hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.85 secs
Boom! What a ride. If you’ve got SSH configured properly between your Mac and your Pi, you can SCP your file over and run the binary remotely:
scp target/armv7-unknown-linux-gnueabihf/debug/hello pi@192.168.3.155:
ssh pi@192.168.3.155 'chmod +x ~/hello && ~/hello'
Hello, world!
Congrats, now go out and make some kick-ass Rust applications for your Pi! Thanks for following along and please let me know (via email, GH issue, etc.) if you have any questions, comments, or just found this helpful.
-Adrian