Using Rust with Buildroot (full build)
In a previous article, we've seen how to add support for the Rust programming language in Buildroot, using the pre-built binaries.
This time, we will add support for Rust by building a cross-compiler in the Buildroot environment. We will use the same example as previously, based on a QEMU ARM Versatile Express system.
Build System Image
First, grab the Buildroot source code and initialize the configuration:
# Clone Buildroot repository git clone https://git.buildroot.net/buildroot cd buildroot # Configure for the desired system make O=$HOME/build/demo-rust/qemu/arm qemu_arm_vexpress_defconfig
Then, edit the configuration:
Go to the "Toolchain" menu and select "glibc" instead of "uclibc" as the targeted C library. Also select support for C++. Save your configuration and exit, then start the build:
Build Rust Compiler and Standard Library
Download the Source Code
First, grab the source code of the Rust compiler and extract it:
# Grab the latest source code pushd dl wget https://static.rust-lang.org/dist/rustc-1.7.0-src.tar.gz popd # Create destination for source code mkdir -p $HOME/build/demo-rust/qemu/arm/build/host-rust-1.7.0 # Extract source code tar -xzf dl/rustc-1.7.0-src.tar.gz \ -C $HOME/build/demo-rust/qemu/arm/build/host-rust-1.7.0 \ --strip-components=1
Configuration of the Cross-Compilation
Internally, the Rust compiler uses LLVM as its backend. LLVM expects the name of the toolchain to match the GNU triplet: cpu-manufacturer-kernel. As explained in the Autotools documentation:
Currently configuration names are permitted to have four parts on systems which distinguish the kernel and the operating system, such as GNU/Linux. In these cases, the configuration name is cpu-manufacturer-kernel-operating_system.
But as the "manufacturer" field is generally set to "unknown", the GNU tools allow it to be omitted, thus resulting in the ambiguous "x86_64-linux-gnu" reported by gcc -dumpmachine on Debian Jessie (instead of "x86_64-unknown-linux-gnu"). On Fedora, the result is "x86_64-redhat-linux" (this time, no system).
To make the build system aware of the cross-compiler generated by Buildroot (which does not have an ambiguous name), and the targeted machine, some new files shoud be added.
To do so, go to the Rust source code directory:
Target Declaration
The first file needed is a Makefile fragment,
mk/cfg/arm-buildroot-linux-gnueabihf.mk
, which declares the new target. This
file can easily be created by copying one of the default files, which closely
matches our target:
Target Specification
Then, the build system should know about the architecture of the new target (CPU type, etc): this is the target specification. There are two ways to do this.
The first method would be to add a new Rust source file, based on an existing one, to specifiy the details of the target:
sed -e 's/unknown/buildroot/g' \ src/librustc_back/target/arm_unknown_linux_gnueabihf.rs \ > src/librustc_back/target/arm_buildroot_linux_gnueabihf.rs
The result file looks like this:
pub fn target() -> Target { let base = super::linux_base::opts(); Target { llvm_target: "arm-buildroot-linux-gnueabihf".to_string(), target_endian: "little".to_string(), target_pointer_width: "32".to_string(), arch: "arm".to_string(), target_os: "linux".to_string(), target_env: "gnueabihf".to_string(), target_vendor: "buildroot".to_string(), options: TargetOptions { features: "+v6,+vfp2".to_string(), .. base } } }
To build this new module, the following patch should be applied:
Index: host-rust-1.7.0/src/librustc_back/target/mod.rs =================================================================== --- host-rust-1.7.0.orig/src/librustc_back/target/mod.rs +++ host-rust-1.7.0/src/librustc_back/target/mod.rs @@ -417,6 +417,7 @@ impl Target { powerpc64le_unknown_linux_gnu, arm_unknown_linux_gnueabi, arm_unknown_linux_gnueabihf, + arm_buildroot_linux_gnueabihf, aarch64_unknown_linux_gnu, x86_64_unknown_linux_musl,
This method would not be very practical for Buildroot, because a set of patches to declare all the supported targets should be provided and applied.
The second method is better. Instead of adding a new Rust module, it is possible to provide a JSON file which contains the same information, as explained in the unofficial documentation of rustc target management and RFC 0131.
As show in src/librustc_back/target/mod.rs
, the JSON file should be stored
in a directory listed in the list of colon-separated values set via the
RUST_TARGET_PATH
environment variable. The chosen location is
$HOME/build/demo-rust/qemu/arm/host/etc/rustc
.
To create the new target specification JSON file, execute:
mkdir -p $HOME/build/demo-rust/qemu/arm/host/etc/rustc cat <<EOF > $HOME/build/demo-rust/qemu/arm/host/etc/rustc/arm-buildroot-linux-gnueabihf.json { "llvm-target": "arm-buildroot-linux-gnueabihf", "target-endian": "little", "target-pointer-width": "32", "target-env": "gnueabihf", "target-vendor": "buildroot", "arch": "arm", "os": "linux", "features": "+v6,+vfp2", "dynamic-linking": true, "executables": true, "morestack": true, "linker-is-gnu": true, "has-rpath": true, "pre-link-args": [ "-Wl,--as-needed" ], "position-independent-executables": true, "archive-format": "gnu" } EOF
Now, configure and build:
export PATH=$HOME/build/demo-rust/qemu/arm/host/usr/bin:$PATH export RUST_TARGET_PATH=$HOME/build/demo-rust/qemu/arm/host/etc/rustc ./configure --prefix=$HOME/build/demo-rust/qemu/arm/host/usr \ --localstatedir=$HOME/build/demo-rust/qemu/arm/host/var/lib \ --sysconfdir=$HOME/build/demo-rust/qemu/arm/host/etc \ --target=arm-buildroot-linux-gnueabihf make -j8 VERBOSE=1 make install popd
The build takes a long time, as LLVM is compiled with all support for all
architectures, and rustc
is not very speedy at compiling itself. When
everything is finished, check the installation is OK:
$ ls -l $HOME/build/demo-rust/qemu/arm/host/usr/bin rust-gdb rustc rustdoc $ $HOME/build/demo-rust/qemu/arm/host/bin/rustc --version rustc 1.7.0-dev $ ls -1 $HOME/build/demo-rust/qemu/arm/host/usr/lib/rustlib arm-buildroot-linux-gnueabihf components etc install.log manifest-rust-docs manifest-rust-std-arm-buildroot-linux-gnueabihf manifest-rust-std-x86_64-unknown-linux-gnu manifest-rustc rust-installer-version uninstall.sh x86_64-unknown-linux-gnu
As shown, the standard library is available both for ARM and x86_64 architectures.
Build Test Program
Now is the time to test the compiler. Create the Rust source file for the "Hello World" program:
mkdir -p $HOME/src/hello-rust cat <<EOF > $HOME/src/hello-rust/main.rs fn main() { println!("Hello World!"); } EOF
To build the hello-rust test program, execute:
Run Test Program from System
Rebuild the system image:
Now, you can start your system using QEMU:
qemu-system-arm \ -M vexpress-a9 \ -m 256 \ -kernel $HOME/build/demo-rust/qemu/arm/images/zImage \ -dtb $HOME/build/demo-rust/qemu/arm/images/vexpress-v2p-ca9.dtb \ -drive file=$HOME/build/demo-rust/qemu/arm/images/rootfs.ext2,if=sd,format=raw \ -append "console=ttyAMA0,115200 root=/dev/mmcblk0" \ -serial stdio \ -net nic,model=lan9118 \ -net user
Log as "root" (no password) and execute the test program:
Cool! Once again you've run a Rust program on an (emulated) embedded Linux system, but this time with everything built from scratch!