Cross-compiling¶
Building¶
This requires less setup than a full test environment (below), but doesn’t allow running binaries for the target.
Toolchain Setup¶
You need a compiler for the target.
For a x86_64-unknown-linux-gnu target and a MacOS host, you can install a cross toolchain:
brew install messense/macos-cross-toolchains/x86_64-unknown-linux-gnu
If you have x86_64-unknown-linux-gnu-binutils already installed, you may need to
also run brew link --overwrite x86_64-unknown-linux-gnu.
Running a build¶
./x.py build --target x86_64-unknown-linux-gnu
Testing Other Targets¶
It’s often possible to test targets other than your host tuple.
For MacOS hosts, we use Apple’s container tool, while for Windows hosts we use WSL2. With these tools we can set up a Linux environment using the same architecture as the host machine, which we can then use to run tests for other targets using QEMU.
Additionally, Ferrocene supports testing a number of targets which are not supported by upstream. When testing locally, special tools or configuration may be required.
In general, any “bare-metal” target listed in Compilation targets overview requires special setup inside a Linux based environment, native or one supporting nested virtualization (such as Lima or WSL2.)
Host Setup¶
Unless otherwise noted, all bare-metal targets are tested via QEMU on a Linux host. On macOS, a
Linux VM must be set up using a tool like Apple’s container. On Windows, WSL2 must be used.
Apple Silicon macOS (aarch64-apple-darwin)¶
First, install container, following the instructions on
that project’s Github page. This tool has a similar
command-line interface to Docker, but with some small differences.
Note
container layers a Docker-like environment on top of a Linux VM, rather than
running on a VM directly. This means that you cannot use binfmt-misc like you can on
bare-metal Linux or on WSL. Instead, you need to build a patched version of QEMU to handle
proper inheritance of emulation across processes, which is included in the following steps.
Next, you will need to acquire a base image. We have a private repository of images at
harbor.infra.ferrous-systems.net which are used for CI and so have most of what we need
pre-installed; here we use the ubuntu-24-main image from ferrocene-images/ci
Log in to the registry:
container registry login --username <email> --password-stdin harbor.infra.ferrous-systems.net
# Paste the "CLI secret" for your harbor account. This can be found on the User Profile
# page on harbor. Then press Enter then Ctrl-D (both are necessary)
# Logging in may take a couple of minutes, but will print "Login succeeded" when it's done
Next, configure and create a container:
container create -t --cpus 10 --memory 16g --name ferrocene-dev \
harbor.infra.ferrous-systems.net/ferrocene-images/ci:ubuntu-24-main
Adjust the CPU and memory allocations as appropriate to your device, remembering to leave some memory free for the host. The above numbers are selected for an M5 Macbook Air, which has 24GB of total memory, split as 16GB for the VM + 8GB for the host.
If you use 1Password, and want to forward your SSH agent into the container, run the following instead:
container create -t --cpus 10 --memory 16g --name ferrocene-dev \
-v $SSH_AUTH_SOCK:/tmp/ssh-auth.sock \
-e SSH_AUTH_SOCK="/tmp/ssh-auth.sock" \
harbor.infra.ferrous-systems.net/ferrocene-images/ci:ubuntu-24-main
Note: The -t argument is extremely important! Without it, the container will immediately exit
when you run container start ferrocene-dev later on
Now start the container and install some packages we will need:
container start ferrocene-dev
container exec -it --uid 0 ferrocene-dev /bin/bash
# This will open a root shell in the container. In that shell, run:
unminimize # This is a script from Ubuntu which expands the minimal environment used for CI
# into a more user-friendly environment for development work
apt install sudo less nano libglib2.0-dev flex bison
# Then close the root shell
If not using the Ferrocene CI image, you will also need to install the packages listed for
x86-64 Linux (glibc) (x86_64-unknown-linux-gnu) on the Setting up a local development environment
page.
Push information we’ll need for logging into things:
container cp ~/.aws/config ferrocene-dev:/home/ci/.aws/config
# If you did not forward the SSH agent above, upload your keys directly:
container cp ~/.ssh/id_ed25519 ferrocene-dev:/home/ci/.ssh/id_ed25519
container cp ~/.ssh/id_ed25519.pub ferrocene-dev:/home/ci/.ssh/id_ed25519.pub
Now open a normal user shell in the container to finish setup:
container exec -it ferrocene-dev /bin/bash
# In the shell this opens:
# First fix the permissions of the files uploaded above
sudo chown -R ci:ci ~/.aws
# If you used 1Password:
sudo chown ci:ci /tmp/ssh-auth.sock
sudo chmod 0600 /tmp/ssh-auth.sock
# If you uploaded your SSH keys:
sudo chown -R ci:ci ~/.ssh
chmod 0600 ~/.ssh/id_ed25519
# Build our patched version of qemu
cd ~
git clone git@github.com:ferrocene/qemu.git -b ferrocene/release/11.0.1-patched --depth 1
mkdir qemu-build
cd qemu-build
../qemu/configure --disable-system --prefix=/opt/qemu-ferrocene
ninja
sudo ninja install
# Login to AWS
aws sso login --profile ferrocene-ci --use-device-code
# Check out ferrocene
cd ~
git clone git@github.com:ferrocene/ferrocene.git
cd ferrocene
ferrocene/ci/scripts/setup-uv.sh
git submodule update --init --recursive
# Set up bootstrap.toml
nano bootstrap.toml
# and check that everything is set up properly
./x test bootstrap
Finally, to run cross-platform tests, the following steps need to be done per target inside the container:
Note
These instructions must be used instead of the ones in the “Target Procedures” section below
Install the appropriate cross-toolchain. E.g. for x86-64 Linux, run:
sudo apt install gcc-x86-64-linux-gnu g++-x86-64-linux-gnu
Then, to run the tests against the desired target, run:
./x build remote-test-server --target x86_64-unknown-linux-gnu
QEMU_LD_PREFIX=/usr/x86_64-linux-gnu /opt/qemu-ferrocene/bin/qemu-x86_64 \
build/aarch64-unknown-linux-gnu/stage2-tools-bin/remote-test-server -v \
--bind 127.0.0.1:12345
# This should print "starting test server" and then wait...
# So open a new shell and run:
container exec -it ferrocene-dev /bin/bash
cd ferrocene
TEST_DEVICE_ADDR=127.0.0.1:12345 ./x test library/core library/alloc \
--target x86_64-unknown-linux-gnu
Note
The above commands are for the x86-64 Linux (glibc) (x86_64-unknown-linux-gnu) target.
For other targets, you will need to change QEMU_LD_PREFIX, the qemu executable,
and the --target argument to the commands. The /build/aarch64-unknown-linux-gnu
part does not change, as this uses the tuple for the VM we are building from
(which will always be aarch64 Linux in this section)
For targets which do not have an OS (generally with a -none somewhere in the target tuple)
the procedure is a little different:
./x build remote-test-server --target thumbv7em-ferrocene.facade-eabihf # Replace 'none' with 'ferrocene.facade'
opt/qemu-ferrocene/bin/qemu-x86_64 \
build/aarch64-unknown-linux-gnu/stage2-tools-bin/remote-test-server -v \
--bind 127.0.0.1:12345
# This should print "starting test server" and then wait...
# So open a new shell and run:
container exec -it ferrocene-dev /bin/bash
cd ferrocene
TEST_DEVICE_ADDR=127.0.0.1:12345 \
RUSTDOCFLAGS="--cfg=ferrocene_facade_secretsauce -Z unstable-options --test-args '--exclude-should-panic'" \
./x test library/core library/alloc \
--target thumbv7em-ferrocene.facade-eabihf
Note
When running a large number of tests, the remote test server seems to return occasional spurious
errors. These can be distinguished from real test failures by the error message
client.read_exact(&mut header) failed with Connection reset by peer (os error 104)
Some test failures can cause the remote test server to crash, which causes all future tests
to be rejected. This can be detected by seeing tests fail with the error message
TcpStream::connect(device_address) failed with Connection refused (os error 111)
Note
The path to the remote-test-server binary does not depend on the target platform,
so you will need to rerun ./x build remote-test-server --target <target> each
time you want to switch target
Warning
It is recommended to not share build/ directories between multiple hosts, both for performance and correctness. To avoid this,
you should cd ~ in the guest and clone a new copy of the Ferrocene repository into the dedicated guest storage.
Please ensure you always work from the guest-local repository.
x86-64 Windows (x86_64-pc-windows-msvc)¶
Setup WSL2, if you don’t have it:
wsl --install --distribution Ubuntu-24.04
Ensure nestedVirtualization is set in the guest /etc/wsl.conf, here is an example
configuration:
[user]
default=ana
[boot]
systemd=true
[wsl2]
nestedVirtualization=true
If you changed your configuration, make sure to restart the environment with wsl --shutdown.
Shell into the guest:
wsl
You can also point Visual Studio Code WSL extension at it.
Finally, ensure the guest is configured according to Setting up a local development environment as well as the x86-64 Linux (glibc) (x86_64-unknown-linux-gnu) on this page.
Warning
It is recommended to not share build/ directories between multiple hosts, both for performance and correctness. To avoid this,
you should cd ~ in the guest and clone a new copy of the Ferrocene repository into the dedicated guest storage.
Please ensure you always work from the guest-local repository.
x86-64 Linux (glibc) (x86_64-unknown-linux-gnu)¶
You need to have all the normal prerequisites from Setting up a local development environment installed, as well as a few extras:
sudo apt install qemu-user-static binfmt-support
Note
These packages must also be installed in the VMs used on Windows.
Target Procedures¶
Currently bare metal targets have a similar procedure for testing.
Note
Currently, these targets use our secret sauce. This will eventually be an open source component, but for now, it’s our little bit of arcane magic.
Armv8-A bare-metal (hard-float) (aarch64-unknown-none)¶
Warning
In a Armv8-A Linux (glibc) environment – such as a guest on Apple Silicon macOS – you must skip to the final step, running the tests using:
export QEMU_CPU=cortex-a53
./x test --stage 1 --target aarch64-unknown-ferrocene.facade library/core
Incorrectly configuring your Armv8-A Linux (glibc) environment using the other steps will damage to the environment and result in “Too many levels of symbolic links” errors.
Install the necessary packages:
sudo apt install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu libc6-dev-arm64-cross qemu-system-aarch64
If you don’t already have a /usr/share/binfmts/qemu-aarch64 file, create one:
package qemu-aarch64
interpreter /usr/bin/qemu-aarch64-static
magic \x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00
mask \xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff
credentials no
preserve no
fix_binary no
Then make sure it’s imported:
sudo update-binfmts --import qemu-aarch64
In order to avoid build errors such as “--fix-cortex-a53-843419 is only supported on AArch64
targets,” ensure the following is in your config.toml:
[target."aarch64-unknown-ferrocene.facade"]
cc = "aarch64-linux-gnu-gcc"
profiler = false
After, you can run the tests:
export QEMU_CPU=cortex-a53
./x test --stage 1 --target aarch64-unknown-ferrocene.facade library/core
Armv7E-M bare-metal (hard-float) (thumbv7em-none-eabihf) & Armv7E-M bare-metal (soft-float) (thumbv7em-none-eabi)¶
Install the necessary packages:
sudo apt install gcc-arm-none-eabi
If you don’t already have a /usr/share/binfmts/qemu-arm file, create one:
package qemu-arm
interpreter /usr/bin/qemu-arm-static
magic \x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00
mask \xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff
credentials no
preserve no
fix_binary no
Then make sure it’s imported:
sudo update-binfmts --import qemu-arm
Now set the target:
export TARGET="thumbv7em-ferrocene.facade-eabihf"
# or
export TARGET="thumbv7em-ferrocene.facade-eabi"
In order to test this target, the build process will acquire a copy of our secret sauce from AWS. Ensure you’re authenticated, following the section in Setting up a local development environment if your environment is not yet set up.
Ensure the following is in your config.toml:
[target."thumbv7em-ferrocene.facade-eabi"]
cc = 'arm-none-eabi-gcc'
profiler = false
[target."thumbv7em-ferrocene.facade-eabihf"]
cc = 'arm-none-eabi-gcc'
profiler = false
You can now run the tests:
export QEMU_CPU=cortex-m4
./x test --stage 1 --target $TARGET library/core