Thanks to the Debian 64-bit RISC-V port it's really easy to build a sysroot appropriate for cross-compiling Clang/LLVM and its separate test suite. Either use my rootless-deboostrap-wrapper script or the command I documented in LLVM's cross-compilation instructions, being sure to see the note on working around a Ninja dependency issue. For a bootable QEMU image, Debian-based recipes are similarly straightforward. But we don't have the luxury of a precompiled distribution for 32-bit RISC-V and so we'll lean on Yocto to produce the needed sysroot by building from source. I cover three cases: 1) building a sysroot for cross-compiling projects like LLVM, 2) doing the same but in a way that requires fewer build steps, 3) building an image approximating my debootstrap image recipes.
In this article I use release 5.3 ('Whinlatter'), which introduced the
bitbake-setup helper
tool.
For documentation, I found the Yocto quick build
guide, and
bitbake-setup
docs, and
image customisation
guide
helpful.
I'm not a Yocto developer, so if you reading this and think there are other approaches to consider or alternative ways of solving the problem that are better, please do drop me a note!
I'm running on Arch Linux which isn't one of the tested Yocto host distributions, but seemed to work just fine.
I found I needed to enable the en_US locale:
sudo sed /etc/locale.gen -i -e "s/^\#en_US.UTF-8 UTF-8.*/en_US.UTF-8 UTF-8/"
sudo locale-gen
And install the following additional packages:
sudo pacman -S inetutils chrpath cpio diffstat rpcsvc-proto flex bison zstd
Now we will check out bitbake into a work directory and set a directory to be used to hold downloaded files:
mkdir yocto-work && cd yocto-work
git clone https://git.openembedded.org/bitbake
./bitbake/bin/bitbake-setup settings set default dl-dir $HOME/.cache/yocto/dl
As is often the case, the workload I'm interested in here is LLVM. If you're looking to build a sysroot to cross-compile something else, you may need a slightly different package list.
In this first stanza, we use bitbake-setup to initialise our development
environment. Because there isn't a predefined machine target for riscv32 in
bitbake/default-registry/configurations/poky-whinlatter.conf.json, we
avoid selecting machine and will address it later. Importantly, we set a
SSTATE_DIR which will be used for the shared state cache, avoiding
rebuilding packages when not necessary (I'm not totaly sure when this isn't
exposed in bitbake-setup settings like dl-dir is).
./bitbake/bin/bitbake-setup init --non-interactive \
--skip-selection machine \
./bitbake/default-registry/configurations/poky-whinlatter.conf.json \
poky \
distro/poky
printf 'SSTATE_DIR = "%s"\n' "$HOME/.cache/yocto/sstate" >> bitbake-builds/site.conf
With that done, we can source the generated definitions to enter the build
environment (note we're using the default setup directory, you can override it
to something other than poky-whinlatter by using --setup-dir-name) and
use enable-fragment to set the qemuriscv32 machine:
. bitbake-builds/poky-whinlatter/build/init-build-env
bitbake-config-build enable-fragment machine/qemuriscv32
Now configure the build, indicating the additional libraries that need to be
present and run bitbake to actually produce it:
cat >> conf/local.conf <<'EOF'
IMAGE_INSTALL:append = " \
glibc-dev \
libgcc \
libgcc-dev \
libatomic \
libatomic-dev \
libstdc++ \
libstdc++-dev \
"
EOF
bitbake core-image-minimal
This results in 4482 build tasks and takes quite some time to complete if you
haven't run it before (i.e. aren't hitting in the sstate cache). The next
section of this article explores how to produce the needed output while
building much less, but let's finish the job and extract a rootfs from what
was built. I would like to now follow advice in the
documentation
and do runqemu-extract-sdk tmp/deploy/images/qemuriscv32/core-image-minimal-qemuriscv32.rootfs.tar.zst ~/rv32sysroot, except that fails because the runqemu-extract-sdk script
doesn't recognise .tar.zst (I've submitted a
patch). So
instead we manually extract the .tar from the .tar.zst and then run the
runqemu-extract-sdk script:
zstd -d -k -f tmp/deploy/images/qemuriscv32/core-image-minimal-qemuriscv32.rootfs.tar.zst
runqemu-extract-sdk tmp/deploy/images/qemuriscv32/core-image-minimal-qemuriscv32.rootfs.tar ~/rv32sysroot
At this point, you have a sysroot that's almost directly usable for
cross-compiling Clang/LLVM (with --target=riscv32-poky-linux) but there are
three finalisation steps we will perform:
$PATH after sourcing
build/init-build-env.mkdir -p "$HOME/rv32sysroot/usr/lib/gcc" && ln -s ../riscv32-poky-linux "$HOME/rv32sysroot/usr/lib/gcc/riscv32-poky-linux"
sysroot-relativelinks.py $HOME/rv32sysroot
ln -s usr/include $HOME/rv32sysroot/include
The core-image-minimal recipe above is straightforward, but does a lot more
work than strictly necessary. We can reduce this by instead adding a
dependency-only recipe that explicitly lists the needed build-time
dependencies and contains logic to produce the sysroot.
First, create a layer:
. bitbake-builds/poky-whinlatter/build/init-build-env
bitbake-layers create-layer --add-layer ../layers/meta-rv32-llvm-sysroot
Then add the recipe:
recipe_dir="../layers/meta-rv32-llvm-sysroot/recipes-devtools/rv32-llvm-deps-sysroot"
mkdir -p "$recipe_dir"
cat > "$recipe_dir/rv32-llvm-deps-sysroot.bb" <<'EOF'
SUMMARY = "Dependency-only recipe to export an RV32 sysroot"
LICENSE = "MIT-0"
INHIBIT_DEFAULT_DEPS = "1"
EXCLUDE_FROM_WORLD = "1"
PACKAGE_ARCH = "${MACHINE_ARCH}"
DEPENDS = "virtual/libc libgcc virtual/${MLPREFIX}compilerlibs zlib"
inherit deploy nopackages
do_configure[noexec] = "1"
do_compile[noexec] = "1"
do_install[noexec] = "1"
do_populate_sysroot[noexec] = "1"
do_deploy() {
export_dir="${DEPLOYDIR}/${PN}-${MACHINE}"
rm -rf "$export_dir"
mkdir -p "$export_dir"
cp -a "${RECIPE_SYSROOT}/." "$export_dir/"
sysroot-relativelinks.py "$export_dir"
mkdir -p "$export_dir/usr/lib/gcc"
ln -s ../riscv32-poky-linux "$export_dir/usr/lib/gcc/riscv32-poky-linux"
ln -s usr/include "$export_dir/include"
}
addtask deploy after do_prepare_recipe_sysroot before do_build
EOF
The do_deploy function implements the sysroot preparation logic that largely
mirrors the previous section. Otherwise, DEPENDS specifies the needed
dependencies (of these, virtual/${MLPREFIX}compilerlibs is a bit magic -
this resolves to the compiler runtime provider which pulls in things like
libstdc++).
Build the sysroot with:
bitbake rv32-llvm-deps-sysroot
This performs ~850 build tasks and will produce the sysroot at
tmp/deploy/images/qemuriscv32/rv32-llvm-deps-sysroot-qemuriscv32/.
The sysroot is slightly larger than the one in the section above because it
contains large unstripped static archives like usr/lib/libstdc++.a.
Watch this space!