A few months ago, I saw this post by Chris Krycho on Hackernews, which points out just how easy it can be to share Rust binaries with friends. The use case really spoke to me — I spend a lot of time building prototypes and I often need to share my prototypes with a few non-technical people. The typical sharing workflow looks like this:
- I package my prototype as a Python script with a bunch of dependencies into a simple command line tool.
- I send the package over to a collaborator.
- Collaborator is missing roughly half of the dependencies.
- I debate teaching collaborator about virtual environments or anaconda. Meanwhile, collaborator is busy typing things like
sudo pip install randompackage
into their terminal.
- Collaborator says, “it’s still not working”. Turns out collaborator only has Python 2.7 but the script is meant for Python 3.
- The process repeats until we get the script running.
After reading Chris’s post, I started looking into what would be involved in cross compiling from Ubuntu (my preferred OS) to OS X (the preferred OS of most of my collaborators). I was discouraged–it looked complicated and I wasn’t sure it could be done without having a running version of OS X with Xcode. Dismayed, I dropped the idea entirely. But the burden of sharing my work continues to weigh on me while my love for the Rust langauge grows ever stronger. Finally, this weekend I decided enough was enough and I was going to attempt to figure out how to cross compile from Ubuntu to OS X.
And it turns out that it’s not nearly as hard as I expected! Most of the key ideas have already been fully explained by Brian Pearce in this post. Brian cross compiles from a Vagrant Ubuntu build environment running on an OS X host to an OS X binary. But Brian’s post isn’t quite enough for the devoted Linux user — it still assumes you’re running a copy of OS X somewhere (namely on the Vagrant host).
Below, I give a complete account of how to cross compile a Rust library from Ubuntu 14.04 for an OS X target. I emphasize again that most of the ideas are not mine but come from Brian’s post as well as from the work of the osxcross project. I’ve just added a few details to complete the build without having to run OS X anywhere.
Prerequisites
Make sure you’ve got the following dependencies installed directly from apt-get:
sudo apt-get update sudo apt-get install clang autotools-dev automake cmake libfuse-dev
Note, a difference from Brian’s post is that we require libfuse
. This allows osxcross to work directly with the necessary Xcode disk image so that we need only have the Xcode image and not a full OS X system to build the OS X SDK. Next, install rustup:
curl https://sh.rustup.rs -sSf | sh
You’ll then want to run (if this is the first time using rustup):
source .cargo/env
Next clone the osxcross project:
git clone https://github.com/tpoechtrager/osxcross
Finally, download version 7.3 of Xcode. For this you’ll need to signin in with any valid Apple ID. You want to obtain a .dmg
file, e.g. Xcode_7.3.1.dmg
.
Bootstrapping the OS X cross compile toolchain
First, we’ll use osxcross to build an OS X SDK package for cross compilation. Change into the osxcross root directory and run:
./tools/gen_sdk_package_p7zip.sh Xcode_7.3.1.dmg
providing the full path to your Xcode .dmg
file. If all goes well, you osxcross will have built a packaged OS X SDK into the root of the osxcross project. Move this package into the tarballs directory:
mv MacOSX10.11.sdk.tar.xz tarballs/
Now we build osxcross:
OSX_VERSION_MIN=10.7 ./build.sh
This creates the full OS X cross compiler tool chain, allowing us to compile Rust targets just like we would on a native OS X system. To make the toolchain available, you need to add it to your path. For this, you might consider edit your ~/.profile
from:
# this line was automatically added to .profile by rustup: export PATH="$HOME/.cargo/bin:$PATH"
to:
export PATH="$HOME/.cargo/bin:$HOME/projects/osxcross/target/bin:$PATH"
Cross compiling
Now we are almost ready to cross compile. Change into your Rust project directory and use Rustup to configure an OS X target:
rustup target add x86_64-apple-darwin
Finally, we need to tell rustc
where to find the linker. Add or edit a .cargo/config
file to the root of your Rust project to contain the following line:
[target.x86_64-apple-darwin] linker = "x86_64-apple-darwin15-clang"
We can then cross compile our Rust code with:
cargo build --release --target=x86_64-apple-darwin
Your shiny new binary can be found under target/x86_64-apple-darwin/release
.
Hooray and happy sharing!