Everything you need to know about cross compiling Rust programs!
If you want to set up your Rust toolchain as a cross compiler, you have come to the right place! I have documented all the necessary steps, plus the gotchas and common problems that you may find along the way.
Dear reader, if you spot a typo, a broken link, or a poorly worded/confusing sentence/paragraph please open an issue pointing out the problem and I'll update the text. Pull requests fixing typos or broken links are, of course, welcome!
Here are the commands necessary to set up a stable Rust toolchain as a cross compiler for ARMv7 (*) devices on a fresh Ubuntu Trusty install. The goal of this example is to show that cross compilation is easy to setup and even easier to perform.
(*) ARM v7, these instructions won't work to cross compile for the Raspberry Pi (1), that's an ARM v6 device.
# Install Rust. rustup.rs heavily recommended. See https://www.rustup.rs/ for details
# Alternatively, you can also use multirust. See https://github.com/brson/multirust for details
$ curl https://sh.rustup.rs -sSf | sh
# Step 0: Our target is an ARMv7 device, the triple for this target is `armv7-unknown-linux-gnueabihf`
# Step 1: Install the C cross toolchain
$ sudo apt-get install -qq gcc-arm-linux-gnueabihf
# Step 2: Install the cross compiled standard crates
$ rustup target add armv7-unknown-linux-gnueabihf
# Step 3: Configure cargo for cross compilation
$ mkdir -p ~/.cargo
$ cat >>~/.cargo/config <<EOF
> [target.armv7-unknown-linux-gnueabihf]
> linker = "arm-linux-gnueabihf-gcc"
> EOF
# Test cross compiling a Cargo project
$ cargo new --bin hello
$ cd hello
$ cargo build --target=armv7-unknown-linux-gnueabihf
Compiling hello v0.1.0 (file:///home/ubuntu/hello)
$ file target/armv7-unknown-linux-gnueabihf/debug/hello
hello: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=67b58f42db4842dafb8a15f8d47de87ca12cc7de, not stripped
# Test the binary
$ scp target/armv7-unknown-linux-gnueabihf/debug/hello me@arm:~
$ ssh me@arm:~ ./hello
Hello, world!
1. 2. 3. You are now cross compiling!
For more examples check the Travis CI builds.
The rest of the guide will explain and generalize each step performed in the previous example.
This guide is divided in two parts: The "main text" and advanced topics. The main text covers the
simplest case: cross compiling Rust programs that depend on the std
crate to a "supported
target" where official builds are available. The advanced topics section covers no_std
programs,
target specification files, how to cross compile the "standard crates" and troubleshooting common
problems.
The advanced topics section builds on top of the information explained in the main text. So, even if your use case is not the same as the one covered by the main text, you should still read the main text before jumping into the advanced topics section.
- Terminology
- Requirements
- Cross compiling with
rustc
- Cross compiling with
cargo
- Advanced topics
- FAQ
- License
Let's make sure we are talking the same language by defining some terms first!
In its most basic form, cross compiling involves two different systems/computers/devices. A host system where the program is compiled, and a target system where the compiled program gets executed.
For example, if you cross compile a Rust program on your laptop to execute it on a Raspberry Pi 2 (RPi2). Then your laptop is the host, and the RPi2 is the target.
However, a (cross) compiler doesn't produce a binary that only works on a single system (e.g. the RPi2). The produced binary can also be executed on several other systems (e.g. the ODROIDs) that share some characteristics like their architecture (e.g. ARM) and their Operating System (e.g. Linux). To refer to this set of systems with shared characteristics we use a string called a triple.
Triples are usually formatted as follows: {arch}-{vendor}-{sys}-{abi}
. For example, the triple
arm-unknown-linux-gnueabihf
refers to the systems that share these characteristics:
- architecture:
arm
. - vendor:
unknown
. In this case, no vendor was specified and/or is not important. - system:
linux
. - ABI:
gnueabihf
.gnueabihf
indicates that the system usesglibc
as its C standard library (libc) implementation and has hardware accelerated floating point arithmetic (i.e. an FPU).
And systems like the RPi2, the ODROIDs, and pretty much every ARMv7 dev board that runs GNU/Linux belongs to this triple.
Some triples omit the vendor or the abi component so they are actually "triples". An example of such
a triple is x86_64-apple-darwin
, where:
- architecture:
x86_64
. - vendor:
apple
. - system:
darwin
.
NOTE From now on, I'm going to overload the term target to mean a single target system, and also to refer to a set of systems with shared characteristics specified by some triple.
To compile a Rust program we need 4 things:
- Find out what's the triple for the target system.
- A
gcc
cross compiler, becauserustc
usesgcc
to "link" stuff together. - C dependencies, usually "libc", cross compiled for the target system.
- Rust dependencies, usually the
std
crate, cross compiled for the target system.
To find out the triple for your target, you first need to figure out these four bits of information about the target:
- Architecture: On UNIXy systems, you can find this with the command
uname -m
. - Vendor: On linux: usually
unknown
. On windows:pc
. On OSX/iOS:apple
- System: On UNIXy systems, you can find this with the command
uname -s
- ABI: On Linux, this refers to the libc implementation which you can find out with
ldd --version
. Mac and *BSD systems don't provide multiple ABIs, so this field is omitted. On Windows, AFAIK there are only two ABIs: gnu and msvc.
Next you need to compare this information against the targets supported by rustc
, and check if
there's a match. If you have a nightly-2016-02-14, 1.8.0-beta.1 or newer rustc
you can use the
rustc --print target-list
command to get the full list of supported targets. Here's the list of
supported targets as of 1.8.0-beta.1:
$ rustc --print target-list | pr -tw100 --columns 3
aarch64-apple-ios i686-pc-windows-gnu x86_64-apple-darwin
aarch64-linux-android i686-pc-windows-msvc x86_64-apple-ios
aarch64-unknown-linux-gnu i686-unknown-dragonfly x86_64-pc-windows-gnu
arm-linux-androideabi i686-unknown-freebsd x86_64-pc-windows-msvc
arm-unknown-linux-gnueabi i686-unknown-linux-gnu x86_64-rumprun-netbsd
arm-unknown-linux-gnueabihf i686-unknown-linux-musl x86_64-sun-solaris
armv7-apple-ios le32-unknown-nacl x86_64-unknown-bitrig
armv7-unknown-linux-gnueabihf mips-unknown-linux-gnu x86_64-unknown-dragonfly
armv7s-apple-ios mips-unknown-linux-musl x86_64-unknown-freebsd
asmjs-unknown-emscripten mipsel-unknown-linux-gnu x86_64-unknown-linux-gnu
i386-apple-ios mipsel-unknown-linux-musl x86_64-unknown-linux-musl
i586-unknown-linux-gnu powerpc-unknown-linux-gnu x86_64-unknown-netbsd
i686-apple-darwin powerpc64-unknown-linux-gnu x86_64-unknown-openbsd
i686-linux-android powerpc64le-unknown-linux-gnu
NOTE If you are wondering what's the difference between arm-unknown-linux-gnueabihf
and
armv7-unknown-linux-gnueabihf
, the arm
triple covers ARMv6 and ARMv7 processors whereas armv7
only supports ARMv7 processors. For this reason, the armv7
triple enables optimizations that are
only possible on ARMv7 processors. OTOH, if you use the arm
triple you would have to opt-in to
these optimizations by passing extra flags like -C target-feature=+neon
to rustc
. TL;DR For
faster binaries, use armv7
if your target has an ARMv7 processor.
If you didn't find a triple that matches your target system, then you are going to need to create a target specification file.
From this point forwards, I'll use the term $rustc_target to refer to the triple you found in
this section. For example, if you found that your target is arm-unknown-linux-gnueabihf
, then
whenever you see something like --target=$rustc_target
mentally expand the $rustc_target
bit so
you end with --target=arm-unknown-linux-gnueaibhf
.
Similarly, I'll use the $host term to refer to the host triple. You can find this triple in the
rustc -Vv
output under the host field. For example, my host system has triple
x86_64-unknown-linux-gnu
.
Here things get a little confusing.
gcc
cross compilers only target a single triple. And this triple is used to prefix all the
toolchain commands: ar
, gcc
, etc. This helps to distinguish a tool used for native compilation,
e.g. gcc
, from a cross compilation tool, e.g. arm-none-eabi-gcc
.
The confusing part is that triples can be quite arbitrary, so your C cross compiler will most likely
be prefixed with a triple that's different from $rustc_target. For example, in Ubuntu the cross
compiler for ARM devices is packaged as arm-linux-gnueabihf-gcc
, the same cross compiler is
prefixed as armv7-unknown-linux-gnueabihf-gcc
in Exherbo, and rustc
uses the
arm-unknown-linux-gnueabihf
triple for that target. None of these triples match, but they refer to
the same set of systems.
The best way to confirm that you have the correct cross toolchain for your target system is to cross compile a C program, preferably something not trivial, and test executing it on the target system.
As to where to get the C cross toolchain, that will depend on your system. Some Linux distributions provide packaged cross compilers. In other cases, you'll need to compile the cross compiler yourself. Tools like crosstool-ng can help with that endeavor. For Linux to OSX, check the osxcross project.
Some examples of packaged cross compilers below:
- For
arm-unknown-linux-gnueabi
, Ubuntu and Debian provide thegcc-*-arm-linux-gnueabi
packages, where*
is gcc version. Example:gcc-4.9-arm-linux-gnueabi
- For
arm-unknown-linux-gnueabihf
, same as above but replacegnueabi
withgnueabihf
- For OpenWRT devices, i.e. targets
mips-unknown-linux-uclibc
(15.05 and older) andmips-unknown-linux-musl
(post 15.05), use the OpenWRT SDK - For the Raspberry Pi, use the Raspberry tools.
Note that the C cross toolchain will ship with a cross compiled libc for your target. Make sure that:
- The toolchain libc matches the target libc. Example, if your target uses the musl libc, then your toolchain must also use the musl libc.
- The toolchain libc is ABI compatible with the target libc. This usually means that the toolchain libc must be older than the target libc. Ideally, both the toolchain libc and the target libc should have the exact same version.
From this point forwards, I'll use the term $gcc_prefix to refer to the prefix of the cross compilation tools (i.e. the cross toolchain) you installed in this section.
Most Rust programs link to the std
crate, so at the very least you'll need a cross compiled std
crate to cross compile your program. The easiest way to get it is from the official builds.
If you are using multirust, as of 2016-03-08, you can install these crates with a single command:
multirust add-target nightly $rustc_target
. If you are using rustup.rs, use the command:
rustup target add $rustc_target
. And if you are using neither, follow the instructions below to
install the crates manually.
The tarball you want is $date/rust-std-nightly-$rustc_target.tar.gz
. Where $date
usually matches
with the rustc
commit date shown in rustc -V
, although on occasion the dates may differ by one
or a few days.
For example, for a arm-unknown-linux-gnueabihf
target and a rustc
with version (rustc -V
)
rustc 1.8.0-nightly (3c9442fc5 2016-02-04)
this is the correct tarball:
http://static.rust-lang.org/dist/2016-02-04/rust-std-beta-arm-unknown-linux-gnueabihf.tar.gz
To install the tarball use the install.sh
script that's inside the tarball:
$ tar xzf rust-std-nightly-arm-unknown-linux-gnueabihf.tar.gz
$ cd rust-std-nightly-arm-unknown-linux-gnueabihf
$ ./install.sh --prefix=$(rustc --print sysroot)
WARNING The above command will output a message that looks like this: "creating uninstall script at /some/path/lib/rustlib/uninstall.sh". Do not run that script because it will uninstall the cross compiled standard crates and the native standard crates; leaving you with an unusable Rust installation and you won't be able to compile natively.
If for some reason you need to uninstall the crates you just installed, simply remove the following
directory: $(rustc --print sysroot)/lib/rustlib/$rustc_target
.
NOTE If you are using the nightly channel, every time you update your Rust install you'll have
to install a new set of cross compiled standard crates. To do so, simply download a new tarball and
use the install.sh
script as before. AFAICT the script will also take care of removing the old set
of crates.
This is the easy part!
Cross compiling with rustc
only requires passing a few extra flags to its invocation:
--target=$rustc_target
, tellsrustc
we are cross compiling for$rustc_target
.-C linker=$gcc_prefix-gcc
, instructsrustc
to use a cross linker instead of the native one (cc
).
Next, an example to test the cross compilation setup so far:
- Create a hello world program on the host
$ cat hello.rs
fn main() {
println!("Hello, world!");
}
- Cross compile the program on the host
$ rustc \
--target=arm-unknown-linux-gnueabihf \
-C linker=arm-linux-gnueabihf-gcc \
hello.rs
- Run the program on the target
$ scp hello me@arm:~
$ ssh me@arm ./hello
Hello, world!
To cross compile with cargo, we must first use its configuration system to set the proper linker
and archiver for the target. Once set, we only need to pass the --target
flag to cargo commands.
Cargo configuration is stored in a TOML file, the key we are interested in is
target.$rustc_target.linker
. The value to store in this key is
the same we passed to rustc
in the previous section. It's up to you to decide if you make this
configuration global or project specific.
Let's go over an example:
- Create a new binary Cargo project.
$ cargo new --bin foo
$ cd foo
- Add a dependency to the project.
$ echo 'clap = "2.0.4"' >> Cargo.toml
$ cat Cargo.toml
[package]
authors = ["me", "myself", "I"]
name = "foo"
version = "0.1.0"
[dependencies]
clap = "2.0.4"
- Configure the target linker and archiver only for this project.
$ mkdir .cargo
$ cat >.cargo/config <<EOF
> [target.arm-unknown-linux-gnueabihf]
> linker = "arm-linux-gnueabihf-gcc"
> EOF
- Write the application
$ cat >src/main.rs <<EOF
> extern crate clap;
>
> use clap::App;
>
> fn main() {
> let _ = App::new("foo").version("0.1.0").get_matches();
> }
> EOF
- Build the project for the target
$ cargo build --target=arm-unknown-linux-gnueabihf
- Deploy the binary to the target
$ scp target/arm-unknown-linux-gnueabihf/debug/foo me@arm:~
- Run the binary on the target.
$ ssh me@arm ./foo -h
foo 0.1.0
USAGE:
foo [FLAGS]
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
Right now, you can only cross compile the standard crates if your target is supported by the Rust
build system (RBS). You can find a list of all the supported targets in the mk/cfg
directory
(NOTE linked directory is not the latest revision). As of
rustc 1.8.0-nightly (3c9442fc5 2016-02-04)
, I see the following supported targets:
$ ls mk/cfg
aarch64-apple-ios.mk i686-pc-windows-msvc.mk x86_64-pc-windows-gnu.mk
aarch64-linux-android.mk i686-unknown-freebsd.mk x86_64-pc-windows-msvc.mk
aarch64-unknown-linux-gnu.mk i686-unknown-linux-gnu.mk x86_64-rumprun-netbsd.mk
arm-linux-androideabi.mk le32-unknown-nacl.mk x86_64-sun-solaris.mk
arm-unknown-linux-gnueabihf.mk mipsel-unknown-linux-gnu.mk x86_64-unknown-bitrig.mk
arm-unknown-linux-gnueabi.mk mipsel-unknown-linux-musl.mk x86_64-unknown-dragonfly.mk
armv7-apple-ios.mk mips-unknown-linux-gnu.mk x86_64-unknown-freebsd.mk
armv7s-apple-ios.mk mips-unknown-linux-musl.mk x86_64-unknown-linux-gnu.mk
armv7-unknown-linux-gnueabihf.mk powerpc64le-unknown-linux-gnu.mk x86_64-unknown-linux-musl.mk
i386-apple-ios.mk powerpc64-unknown-linux-gnu.mk x86_64-unknown-netbsd.mk
i686-apple-darwin.mk powerpc-unknown-linux-gnu.mk x86_64-unknown-openbsd.mk
i686-linux-android.mk x86_64-apple-darwin.mk
i686-pc-windows-gnu.mk x86_64-apple-ios.mk
NOTE If your target is not supported by the RBS, then you'll need to add support for your target to it. I won't go over the details of adding support for a new target, but you can use this PR as a reference.
NOTE If you are doing bare metal programming, building your own kernel or, in general, working
with #![no_std]
code, then you probably don't want to (and probably can't because there is no OS)
build all the standard crates, but just the core
crate and other freestanding crates. If that's
your case, read the Cross compiling no_std
code section instead of this one.
The steps for cross compiling the standard crates are not complicated, but the process of building
them does take a very long time because the RBS will bootstrap a new compiler, and then use that
bootstrapped compiler to cross compile the crates. Hopefully, the upcoming cargo-based build
system will open the possibility of making this much faster by letting you use your already
installed rustc
and cargo
to cross compile the standard crates.
Back to the instructions, first you need to figure out the commit hash of your rustc
. This
is listed under the output of rustc -Vv
. For example, this rustc
:
$ rustc -Vv
rustc 1.8.0-nightly (3c9442fc5 2016-02-04)
binary: rustc
commit-hash: 3c9442fc503fe397b8d3495d5a7f9e599ad63cf6
commit-date: 2016-02-04
host: x86_64-unknown-linux-gnu
release: 1.8.0-nightly
Has commit hash: 3c9442fc503fe397b8d3495d5a7f9e599ad63cf6
.
Next you need to fetch Rust source and check it out at that exact commit hash. Don't omit the checkout or you'll end with crates that are unusable by your compiler.
$ git clone https://github.com/rust-lang/rust
$ cd rust
$ git checkout $rustc_commit_hash
# Triple check the git checkout matches `rustc` commit hash
$ git rev-parse HEAD
$rustc_commit_hash
Next we prepare a build directory for an out of source build.
# Anywhere
$ mkdir build
$ cd build
$ /path/to/rust/configure --target=$rustc_target
configure
accepts many other configuration flags, check out configure --help
for more
information. Do note that by default, i.e. without any flag, configure
will prepare a fully
optimized build.
Next we kick off the build:
$ make -j$(nproc)
If you hit this error during the build:
make[1]: $rbs_prefix-gcc: Command not found
Don't panic!
This happens because the RBS expects a gcc with a certain prefix for each target, but this prefix
may not match the prefix of your installed cross compiler. For example, in my system, the installed
cross compiler is armv7-unknown-linux-gnueabihf-gcc
, but the RBS, when building for the
arm-unknown-linux-gnueabihf
target, expects the cross compiler to be named
arm-none-gnueabihf-gcc
.
This can be easily fixed with some shim binaries:
# In the build directory
$ mkdir .shims
$ cd .shims
$ ln -s $(which $gcc_prefix-ar) $rbs_prefix-ar
$ ln -s $(which $gcc_prefix-gcc) $rbs_prefix-gcc
$ cd ..
$ export PATH=$(pwd)/.shims:$PATH
Now you should be able to call both $gcc_prefix-gcc
and $rbs_prefix-gcc
. For example:
# My installed cross compiler
$ armv7-unknown-linux-gnueabihf-gcc -v
Using built-in specs.
COLLECT_GCC=armv7-unknown-linux-gnueabihf-gcc
COLLECT_LTO_WRAPPER=/usr/x86_64-pc-linux-gnu/libexec/gcc/armv7-unknown-linux-gnueabihf/5.3.0/lto-wrapper
Target: armv7-unknown-linux-gnueabihf
Configured with: (...)
Thread model: posix
gcc version 5.3.0 (GCC)
# The cross compiler that the RBS expects, which is supplied by the .shims directory
$ arm-linux-gnueabihf-gcc -v
Using built-in specs.
COLLECT_GCC=armv7-unknown-linux-gnueabihf-gcc
COLLECT_LTO_WRAPPER=/usr/x86_64-pc-linux-gnu/libexec/gcc/armv7-unknown-linux-gnueabihf/5.3.0/lto-wrapper
Target: armv7-unknown-linux-gnueabihf
Configured with: (...)
Thread model: posix
gcc version 5.3.0 (GCC)
You can now resume the build with make -j$(nproc)
.
Hopefully the build will complete successfully and your cross compiled crates will be available in
the $host/stage2/lib/rustlib/$rustc_target/lib
directory.
# In the build directory
$ ls x86_64-unknown-linux-gnu/stage2/lib/rustlib/arm-unknown-linux-gnueabihf/lib
liballoc-db5a760f.rlib librand-db5a760f.rlib stamp.arena
liballoc_jemalloc-db5a760f.rlib librbml-db5a760f.rlib stamp.collections
liballoc_system-db5a760f.rlib librbml-db5a760f.so stamp.core
libarena-db5a760f.rlib librustc_bitflags-db5a760f.rlib stamp.flate
libarena-db5a760f.so librustc_unicode-db5a760f.rlib stamp.getopts
libcollections-db5a760f.rlib libserialize-db5a760f.rlib stamp.graphviz
libcompiler-rt.a libserialize-db5a760f.so stamp.libc
libcore-db5a760f.rlib libstd-db5a760f.rlib stamp.log
libflate-db5a760f.rlib libstd-db5a760f.so stamp.rand
libflate-db5a760f.so libterm-db5a760f.rlib stamp.rbml
libgetopts-db5a760f.rlib libterm-db5a760f.so stamp.rustc_bitflags
libgetopts-db5a760f.so libtest-db5a760f.rlib stamp.rustc_unicode
libgraphviz-db5a760f.rlib libtest-db5a760f.so stamp.serialize
libgraphviz-db5a760f.so rustlib stamp.std
liblibc-db5a760f.rlib stamp.alloc stamp.term
liblog-db5a760f.rlib stamp.alloc_jemalloc stamp.test
liblog-db5a760f.so stamp.alloc_system
The next section will tell you how to install these crates in your Rust installation directory.
First, we need to take a closer look at your Rust installation directory, whose path you can get
with rustc --print sysroot
:
# I'm using rustup.rs, you'll get a different path if you used rustup.sh or your distro package
# manager to install Rust
$ tree -d $(rustc --print sysroot)
~/.multirust/toolchains/nightly
├── bin
├── etc
│ └── bash_completion.d
├── lib
│ └── rustlib
│ ├── etc
│ └── $host
│ └── lib
└── share
├── doc
│ └── (...)
├── man
│ └── man1
└── zsh
└── site-functions
See that lib/rustlib/$host
directory? That's where your native crates are stored. The cross
compiled crates must be installed right next to that directory. Following the example from the
previous section, the following command will copy the standard crates built by the RBS in the right
place.
# In the 'build' directory
$ cp -r \
$host/stage2/lib/rustlib/$target
$(rustc --print sysroot)/lib/rustlib
Finally, we check that the crates are in the right place.
$ tree $(rustc --print sysroot)/lib/rustlib
/home/japaric/.multirust/toolchains/nightly/lib/rustlib
├── (...)
├── uninstall.sh
├── $host
│ └── lib
│ ├── liballoc-fd663c41.rlib
│ ├── (...)
│ ├── libarena-fd663c41.so
│ └── (...)
└── $target
└── lib
├── liballoc-fd663c41.rlib
├── (...)
├── libarena-fd663c41.so
└── (...)
This way you can install crates for as many targets as you want. To "uninstall" the crates simply remove the $target directory.
A target specification file is a JSON file that provides detailed information about a target to the Rust compiler. This specification file has five required fields and several optional ones. All its keys are strings and its values are either strings or booleans. A minimal target spec file for Cortex M3 microcontrollers is shown below:
{
"0": "NOTE: I'll use these 'numeric' fields as comments, but they shouldn't appear in these files",
"1": "The next five fields are _required_",
"arch": "arm",
"llvm-target": "thumbv7m-none-eabi",
"os": "none",
"target-endian": "little",
"target-pointer-width": "32",
"2": "These fields are optional. Not all the possible optional fields are listed here, though",
"cpu": "cortex-m3",
"morestack": false
}
A list of all the possible keys and their effect on compilation can be found in the
src/librustc_back/target/mod.rs
file (NOTE: the linked file is not the latest revision).
There are two ways to pass these target specification files to rustc
, the first is pass the full
path via the --target
flag.
$ rustc --target path/to/thumbv7m-none-eabi.json (...)
The other is to simply pass the "file stem" of the file to --target
, but then the file must be
in the working directory or in the directory specified by the RUST_TARGET_PATH
variable.
# Target specification file is in the working directory
$ ls thumbv7m-none-eabi.json
thumbv7m-none-eabi.json
# Passing just the "file stem" works
$ rustc --target thumbv7m-none-eabi (...)
When working with no_std
code you only want a few freestanding crates like core
, and you are
probably working with a custom target, e.g. a Cortex-M microcontroller, so there are no official
builds for your target nor can you build these crates using the RBS.
A simple solution to get a cross compiled core
crate is to make your program/crate depend on the
rust-libcore
crate. This will make Cargo build the core
crate as part of the cargo build
process. However, this approach has two problems:
-
Virality: You can't make your crate depend on another
no_std
crate unless that crate also depends onrust-libcore
. -
If you want your crate to depend on another standard crate then a new
rust-lib$crate
crate would need to be created.
An alternative solution that doesn't have these problems is to use a "sysroot" that holds the cross
compiled crates. I'm implementing this approach in xargo
. For more details check the repository.
Anything that can go wrong, will go wrong -- Murphy's law
This section: What to do when things go wrong.
Symptom
$ cargo build --target $rustc_target
error: can't find crate for `$crate`
Cause
rustc
can't find the cross compiled standard crate $crate
in your Rust installation directory.
Solution
Check the Installing the cross compiled standard crates section and make sure the cross compiled
$crate
crate is in the right place.
Symptom
$ cargo build --target $rustc_target
error: the crate `$crate` has been compiled with rustc $version-$channel ($hash $date), which is incompatible with this version of rustc
Cause
The version of the cross compiled standard crates that you installed don't match your rustc
version.
Solution
If you are on the nightly channel and installed an official build, you probably got the date of the tarball wrong. Try a different date.
If you cross compiled the crates from source, then you checked out the wrong commit of the source.
You'll have the build the crates again, but making sure you check out the repository at the right
commit (it must match the commit-hash field of rustc -Vv
output).
Symptom
$ cargo build --target $rustc_target
/path/to/some/file.c:$line: undefined reference to `$symbol`
Cause
The scenario goes like this:
- The standard crates were cross compiled using a C cross toolchain "A".
- Then you cross compile a Rust program using C cross toolchain "B", this program was also linked to the standard crates produced in the previous step.
The problem occurs when the libc component of toolchain "A" is newer than the libc component of toolchain "B". In this case, the standard crates cross compiled with "A" may depend on libc symbols that are not available in "B"'s libc.
This error will also occur if "A"'s libc is different from "B"'s libc. Example: toolchain "A" is
mips-linux-gnu
and toolchain "B" is mips-linux-musl
.
Solution
If you observe this with a official build, that's a bug. It indicates that the Rust team must downgrade the libc component of the C cross toolchain they are using to build the standard crates.
If you are cross compiling the standard crates yourself, then it would be ideal if you use the same C cross toolchain to build the standard crates and to cross compile Rust programs.
Symptom
# On target
$ ./hello
./hello: can't load library 'libpthread.so.0'
Cause
Your target system is missing a shared library. You can confirm this with ldd
:
# Or `LD_TRACE_LOADED_OBJECTS=1 ./hello` on uClibc-based OpenWRT devices
$ ldd hello
libdl.so.0 => /lib/libdl.so.0 (0x771ba000)
libpthread.so.0 => not found
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x77196000)
libc.so.0 => /lib/libc.so.0 (0x77129000)
ld-uClibc.so.0 => /lib/ld-uClibc.so.0 (0x771ce000)
libm.so.0 => /lib/libm.so.0 (0x77103000)
All the missing libraries are marked with "not found".
Solution
Install the missing shared libraries in your target system. Continuing the previous example:
# target system is an OpenWRT device
$ opkg install libpthread
$ ./hello
Hello, world!
Symptom
# On target
$ ./hello
rustc: /path/to/$c_library.so: version `$symbol' not found (required by /path/to/$rust_library.so).
Cause
ABI mismatch between the library that was dynamically linked to the binary during cross compilation and the library that's installed in the target.
Solution
Update/change the library on either the host or the target to make them both ABI compatible. Ideally, the host and the target should have the same library version.
NOTE When I say the library on the host, I'm referring to the cross compiled library that
the $prefix_gcc-gcc
is linking into your Rust program. I'm not referring to the native
library that may be installed in the host.
Symptom
# on target
$ ./hello
Illegal instruction
Causes
NOTE You can also get an "illegal instruction" error if your program reaches an Out Of Memory (OOM) condition. In some systems, you will additionally see an "fatal runtime error: out of memory" message when you hit OOM. If you are sure that's not your case, then this is a cross compilation problem.
This occurs because your program contains an instruction that's not supported by your target system. Among the possible causes of this problem we have:
-
You are compiling for a hard float target, e.g.
arm-unknown-linux-gnueabihf
, but your target doesn't support hard float operations and it's actually a soft float target, e.g.arm-unknown-linux-gnueabi
. Solution: Use the right triple, in this example:arm-unknown-linux-gnueabi
. -
You are using the right soft float triple, e.g.
arm-unknown-linux-gnueabi
, for your target system. But your C cross toolchain was compiled with hard float support and is injecting hard float instructions into your binary. Solution: Get the correct toolchain, one that was built with soft float support. Hint: look for the flag--with-float
in the output of$gcc_prefix-gcc -v
.
Short answer: You don't.
It's hard to find a cross C toolchain (and cross compiled C libraries) between different OSes (except perhaps from Linux to Windows). A much simpler and less error prone way is to build natively for these targets because they are tier 1 platforms. You may not have direct access to all these OSes but that's not a problem because you can use CI services like Travis CI and AppVeyor. Check my rust-everywhere project for instructions on how to do that.
Short answer: cargo build --target x86_64-unknown-linux-musl
For targets of the form *-*-linux-gnu*
, rustc
always produces binaries dynamically linked to
glibc
and other libraries:
$ cargo new --bin hello
$ cargo build --target x86_64-unknown-linux-gnu
$ file target/x86_64-unknown-linux-gnu/debug/hello
target/x86_64-unknown-linux-gnu/debug/hello: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /usr/x86_64-pc-linux-gnu/lib/ld-linux-x86-64.so.2, for GNU/Linux 2.6.34, BuildID[sha1]=a3fa7281e9ded30372b5131a2feb6f1e78a6f1cd, not stripped
$ ldd target/x86_64-unknown-linux-gnu/debug/hello
linux-vdso.so.1 (0x00007fff58bf4000)
libdl.so.2 => /usr/x86_64-pc-linux-gnu/lib/libdl.so.2 (0x00007fc4b2d3f000)
libpthread.so.0 => /usr/x86_64-pc-linux-gnu/lib/libpthread.so.0 (0x00007fc4b2b22000)
libgcc_s.so.1 => /usr/x86_64-pc-linux-gnu/lib/libgcc_s.so.1 (0x00007fc4b290c000)
libc.so.6 => /usr/x86_64-pc-linux-gnu/lib/libc.so.6 (0x00007fc4b2568000)
/usr/x86_64-pc-linux-gnu/lib/ld-linux-x86-64.so.2 (0x00007fc4b2f43000)
libm.so.6 => /usr/x86_64-pc-linux-gnu/lib/libm.so.6 (0x00007fc4b2272000)
To produce statically linked binaries, Rust provides two targets:
x86_64-unknown-linux-musl
and i686-unknown-linux-musl
. The binaries produced for these targets
are statically linked to the MUSL C library. Example below:
$ cargo new --bin hello
$ cd hello
$ rustup target add x86_64-unknown-linux-musl
$ cargo build --target x86_64-unknown-linux-musl
$ file target/x86_64-unknown-linux-musl/debug/hello
target/x86_64-unknown-linux-musl/debug/hello: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=759d41b9a78d86bff9b6529d12c8fd6b934c0088, not stripped
$ ldd target/x86_64-unknown-linux-musl/debug/hello
not a dynamic executable
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.