Cross compilation to OSX with Rust

It started with Muxed. I was excited to get a working first build, but despite my development machine being a MacBook Air, I develop most in VM’s using vagrant. My native builds were all linux based. It would be easy enough to move the code to an OSX machine and compile there but I do not like build tools on my native machine. And that would simply be too easy.

Overall documentation on the complete process did not seem readily available. So there was a lot of question-asking, googling, and piecing together differents parts of solutions.

Let’s meet Rustup and deal with some disambiguation. The original Rustup was a bash script to help install the rust compiler. Since then the name has been re-purposed and now become the Rust toolchain installer. The new Rustup offers us the beginning of support for cross compilation.

But today we are focusing on a pre Rustup Toolchain cross compilation build process.

Let’s layout some assumptions and dependencies before we start:

This guide unfortunately requires two systems to build the first time but a single system to continue cross compiling from. It is possible to do with Unix only, but I could not make it work. Assume all instructions are being executed on the Unix system unless otherwise noted.

  • Ubuntu 15.10 Wily Werewolf (Inside Vagrant/VirtualBox)
  • Mac OSX 10.9.5

Build tools on Ubuntu:

  • curl
  • git
  • autotools-dev
  • automake
  • cmake
  • clang

Step 1. Install the stdlib

To cross compile you require the compiled stdlib for the architecture type you want to compile to. You will want to get the same stdlib as the version of rustc you are running. If rustc -v outputs 1.10 then you want stdlib 1.10. Luckily the Rust community makes these readily available for you. Head on over to the distribution directoy and find the package you’re looking for based on architecture type, and version.

In our case it will be: rust-std-1.10.0-x86_64-apple-darwin.

Now fetch this package on to the unix system, untar it, and install it. Its location does not matter much and is not required to be in your project:

$ wget https://static.rust-lang.org/dist/rust-std-1.10.0-x86_64-apple-darwin.tar.gz
$ tar -xvxf rust-std-1.10.0-x86_64-apple-darwin
$ cd rust-std-1.10.0-x86_64-apple-darwin
$ sudo ./install.sh
install: installing component 'rust-std-x86_64-apple-darwin'

   std is standing at the ready.

Step 2. Acquire an Xcode Package

Note: If you will be performing step 3 on an OSX system, and already have xcode installed, you can skip this step.

Download Xcode. This will require an apple developer login. An active subscription is not required.

The version of Xcode required at the time of writing is 7.3. You can find links to all the available versions of Xcode here.

Step 3. Package the SDK

This is the part where you may deviate if desired.

OSXCross has an entire section on how to package the sdk for different OSX versions, while on different systems. Like I mentioned before, I could not manage sdk extraction on Unix, and ended up having to do this part on OSX.

If you do not already have Xcode installed on your OSX system, install your chosen package.

Clone the OSXCross repo to your OSX system, move into its directory, and call the extraction tool:

$ git clone git@github.com:tpoechtrager/osxcross.git
$ cd osxcross
$ ./tools/gen_sdk_package.sh

My system output two packages: MacOSX10.10.sdk.tar.xz and MacOSX10.9.sdk.tar.xz.

Copy the desired package to your Unix system. I moved MacOSX10.10.sdk.tar.xz to the homdir on my Unix VM.

Step 4. Install OSXCross

Now we also need OSXCross on the Unix system. And we need to move the MacOSX10.10.sdk.tar.xz package into osxcross’ tarballs directory.

$ git clone git@github.com:tpoechtrager/osxcross.git
$ cd osxcross
$ mv ~/MacOSX10.10.sdk.tar.xz ./tarballs

Now we install OSXCross:

$ OSX_VERSION_MIN=10.7 ./build.sh

> All done! Now you can use o32-clang(++) and o64-clang(++) like a normal compiler.

> (more output)

As OSXCross will remind you, we also need to add the linkers to your path:

Do not forget to add

/home/vagrant/osxcross/target/bin

to your PATH variable.

Step 5. Add the target to Cargo

I read this wrong. So. Many. Times. So let’s be careful here.

We are going to add the linker in our cargo config. This is the configuration for cargo. Not the Cargo.toml for your project.

The configuration file can be in any project, or subdirectory of the project. Cargo will recursively look for a .cargo/config file.

Add the following:

[target.x86_64-apple-darwin]
linker = "x86_64-apple-darwin14-clang"

[target.x86_64-unknown-linux-gnu]

This tells cargo which linker to use for the defined target.

Not defining a linker for the linux-gnu target sets it to use the default system.

Step 6. Cross Compile Time!

Assuming you have made it this far you can now cross compile. Head to your project root and run cargo with the --target option, where the value matches the target in the Cargo config:

$ cargo build --target x86_64-apple-darwin

Success! You hopefully just cross compiled your application or library!

Kermit the frog doing a happy dance!