At the time of this writing, Rust and Cargo are available on FreeBSD/amd64 and FreeBSD/i386 only, whether it is from rustup or from the FreeBSD ports tree. Here is how I could bootstrap Rust and Cargo for FreeBSD/aarch64 from FreeBSD/amd64.
To be able to cross-compile anything, you need a userland for the target.
The easiest is to get is from a published release. I took the arm64 base.txz archive from 11.0-RELEASE.
Once fetched, simply extract it somewhere. The following paragraphs refer to this location with /path/to/aarch64/installworld.
This is quite straightforward to get with a buildworld. From a checkout/clone of the FreeBSD base source tree:
export TARGET=arm64
# export TARGET_ARCH=aarch64 # Not needed for aarch64.
# Only required if you build as an unprivileged user.
export MAKEOBJDIRPREFIX=/path/to/obj/dir
make -j16 buildworld
sudo -E make installworld DESTDIR=/path/to/aarch64/installworld
-
I used clang 3.7 from the Ports tree as the compiler for both the host and the target, as well as a cross-compiled binutils, also from the Ports tree:
pkg install \ llvm37 \ aarch64-binutilsaarch64-none-elf-gccandaarch64-none-elf-binutilsare available from the Ports tree too, but I couldn't get them to work. -
You need wrappers around
clang37andclang++37so they use the target userland prepared in the previous step and compile for the target. I created those two scripts named after the Rust target triple (aarch64-unknown-freebsd) we want to bootstrap:-
$HOME/bin/aarch64-unknown-freebsd-clang:#!/bin/sh exec clang37 \ --sysroot=/path/to/aarch64/installworld \ --target=aarch64-unknown-freebsd \ "$@"
-
$HOME/bin/aarch64-unknown-freebsd-clang++:#!/bin/sh exec clang++37 \ --sysroot=/path/to/aarch64/installworld \ --target=aarch64-unknown-freebsd \ -stdlib=libc++ \ "$@"
-
-
Binutils are supposed to be prefixed with the same target triple as well. So:
cd $HOME/bin for file in /usr/local/bin/aarch64-freebsd-*; do \ target=$(basename "$file"); \ ln -s "$file" "aarch64-unknown-freebsd-${target#aarch64-freebsd-}"; \ done
-
Verify that cross-compilation actually works. I used the following two Hello World:
-
Hello World in C (
hello.c):#include <stdio.h> int main(int argc, char *argv[]) { printf("Hello World!\n"); return (0); }
-
Hello World in C++ (
hello.cpp):#include <iostream> int main() { std::cout << "Hello World!" << std::endl; return 0; }
Then compile them and verify the produced binary:
aarch64-unknown-freebsd-clang -o hello-c hello.c aarch64-unknown-freebsd-clang++ -o hello-c++ hello.cpp file hello-*hello-c: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 12.0 (1200020), FreeBSD-style, not stripped hello-c++: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /libexec/ld-elf.so.1, for FreeBSD 12.0 (1200020), FreeBSD-style, not stripped -
I recommend you also run the compiled executables on the target host if possible.
Finally, we can work on the real goal!
I used the x.py Python script available at the root of Git clone of Rust. This script takes command line arguments and a configuration file, mostly to setup cross-compilation settings.
-
Here is the configuration file I used, which I put in
aarch64.tomlbesidex.py:[build] # In addition to the build triple, other triples to produce full compiler # toolchains for. Each of these triples will be bootstrapped from the build # triple and then will continue to bootstrap themselves. This platform must # currently be able to run all of the triples provided here. host = ["x86_64-unknown-freebsd", "aarch64-unknown-freebsd"] # In addition to all host triples, other triples to produce the standard library # for. Each host triple will be used to produce a copy of the standard library # for each target triple. target = ["aarch64-unknown-freebsd"] # Indicate whether submodules are managed and updated automatically. submodules = false [rust] # The "channel" for the Rust build to produce. The stable/beta channels only # allow using stable features, whereas the nightly and dev channels allow using # nightly features channel = "nightly" [host.aarch64-unknown-freebsd] cc = "aarch64-unknown-freebsd-clang" cxx = "aarch64-unknown-freebsd-clang++" [target.aarch64-unknown-freebsd] cc = "aarch64-unknown-freebsd-clang" cxx = "aarch64-unknown-freebsd-clang++"
You can look at
src/bootstrap/config.tomlfor additional settings and comments. -
Then I started
x.pylike this:CC_aarch64_unknown_freebsd=aarch64-unknown-freebsd-clang \ CXX_aarch64_unknown_freebsd=aarch64-unknown-freebsd-clang++ \ ./x.py build --config aarch64.toml -j 8
The environment variables named
CC_*andCXX_*repeat cross-compilation settings for some sub-components in C.The build will fail: this should be a good indicator of the work needed to support the new target. For
aarch64-unknown-freebsd, I prepared the following patches:- rust-lang/rust#39491: it mainly adds the target triple to the supported targets. It's later printed by
rustc --print target-listfor instance. - rust-lang/libc#512: it adds C/Rust type conversion. I took the existing FreeBSD files as a base and looked at the target's
/usr/include/machine/_types.hamong other headers.
src/liblibcis a Git submodule, that's why there are two patches. Be sure to have thesubmodules = falsein the toml file, otherwise,x.pyresets all submodules to their expected commit, meaning you'll loose your patch. - rust-lang/rust#39491: it mainly adds the target triple to the supported targets. It's later printed by
-
Once the build is complete, you can run the following command to package the compiler and the standard library (simply replace the
buildcommand bydist):CC_aarch64_unknown_freebsd=aarch64-unknown-freebsd-clang \ CXX_aarch64_unknown_freebsd=aarch64-unknown-freebsd-clang++ \ ./x.py dist --config aarch64.toml -j 8
The compiler and the standard library archives for both the host and the target will be available in
build/dist:$ ls -1 build/dist/ ... rust-std-nightly-aarch64-unknown-freebsd.tar.gz rust-std-nightly-x86_64-unknown-freebsd.tar.gz rustc-nightly-aarch64-unknown-freebsd.tar.gz rustc-nightly-x86_64-unknown-freebsd.tar.gz ... -
You should test them on the target. You can uncompress the two archives (
rustc-nightly-$triple.tar.gzandrust-std-nightly-$triple.tar.gz) and use theinstall.shscript provided. Here is a handy Hello World:// hello.rs fn main() { println!("Hello World!"); }
rustc -o hello hello.rs ./hello
This part probably requires no patch at all, just commands to run.
-
Install
rustcandrust-stdarchives from above on the build host (so the x86_64 archives :). They are needed because they know how to produce code for the new target. -
Install the bootstrapped
rust-stdfor the target host (also created during the previous steps) on your build host. -
Clone Cargo and init/update Git submodules
-
Create or update the global Cargo configuration (
$HOME/.cargo/config) with the cross-compilation settings:[target.aarch64-unknown-freebsd] linker = "aarch64-unknown-freebsd-clang"
-
Configure the build:
./configure --enable-optimize --release-channel=nightly --target=aarch64-unknown-freebsd
-
Build:
CC_aarch64_unknown_freebsd=aarch64-unknown-freebsd-clang \ CXX_aarch64_unknown_freebsd=aarch64-unknown-freebsd-clang++ \ OPENSSL_DIR=/usr/local \ gmake
Note that you need GNU Make and OpenSSL from the Ports tree, and the same
CC_*/CXX_*target-specific variables.The
libssh2port must not be installed by the way, otherwise Cargo picks it (instead of the embbeded copy) and the build may fail. -
The build will fail when building
libcbecause it needs the same patches as the previous step. So go to the Cargo registry and apply the patch you already prepared above:cd ~/.cargo/registry/src/github.com-1ecc6299db9ec823/libc-0.2.20 patch -p1 < /path/to/libc.patch
-
Resume the build.
-
Create the package:
CC_aarch64_unknown_freebsd=aarch64-unknown-freebsd-clang \ CXX_aarch64_unknown_freebsd=aarch64-unknown-freebsd-clang++ \ OPENSSL_DIR=/usr/local \ gmake dist
-
Done! The result is in:
$ ls -1 target/aarch64-unknown-freebsd/release/dist ... cargo-nightly-aarch64-unknown-freebsd.tar.gz
-
Now, you can test it on the target host. Simply install it using the same
install.shscript from the archive and do Cargo's Hello World:cargo new hello_world --bin cd hello_world cargo run
To completely verify the produced bootstrapped Rust/Cargo, it's good to try to build Rust using the result of the two previous steps on the target host directly.
-
Install the bootstrapped
rustc,rust-stdandcargocreated during the previous steps on the target host. -
Obviously, you need a patched clone of Rust, including the patched libc.
-
You'll need the following
aarch64.toml:[build] # Instead of downloading the src/nightlies.txt version of Cargo specified, use # this Cargo binary instead to build all Rust code cargo = "/path/to/bootstrapped/cargo" # Instead of downloading the src/nightlies.txt version of the compiler # specified, use this rustc binary instead as the stage0 snapshot compiler. rustc = "/path/to/bootstrapped/rustc" # Indicate whether submodules are managed and updated automatically. submodules = false [rust] # The "channel" for the Rust build to produce. The stable/beta channels only # allow using stable features, whereas the nightly and dev channels allow using # nightly features channel = "nightly"
-
Run
x.pywith this new toml file:./x.py build --config aarch64.toml -j 16
Was this by chance on a Raspberry Pi running FreeBSD? 😃 I'm currently working on getting that set up myself and I'm excited to try these steps out.