I am trying to build for ARM targets in Github Actions. Since the toolchain does not support cross-compilation, the workaround is a foreign chroot environment and QEMU.
Steps were mostly taken from https://www.hellion.org.uk/blog/posts/foreign-chroots-with-schroot-and-qemu/, except that I am skipping the schroot/sbuild part. Instead, I intend to run the build by executing all related commands one after another in a foreign bash. To allow this to happen as regular GHA steps, I have created a script, bash-wrapper
, which I run as a custom shell and which forwards all commands to a bash process for the target architecture running inside the chroot.
Here is the excerpt with which I set up the chroot and QEMU, and do a brief smoke test:
- name: Set up cross-compilation environment
shell: bash
run: |
outputs() { for var in "$@" ; do echo steps.vars.outputs.${var}="${!var}"; echo ::set-output name=${var}::${!var}; done; }
CROSS_ROOT="/opt/chroot/armhf"
CROSS_MIRROR="http://deb.debian.org/debian/"
outputs CROSS_ROOT CROSS_MIRROR
sudo apt-get -y install debootstrap qemu-user-static binfmt-support
sudo mkdir -p $CROSS_ROOT
sudo debootstrap --variant=buildd --include=fakeroot,build-essential,sudo --arch=${{ matrix.job.arch }} --foreign ${{ matrix.job.release }} $CROSS_ROOT $CROSS_MIRROR
sudo mkdir -p $CROSS_ROOT/usr/bin
# TODO this is not fully portable across all architectures
sudo ln /usr/bin/qemu-arm-static $CROSS_ROOT/usr/bin/
sudo chroot $CROSS_ROOT ./debootstrap/debootstrap --second-stage
# we need to replicate users, groups and home dirs if we’re not running as root
sudo ln -f /etc/passwd $CROSS_ROOT/etc/passwd
sudo ln -f /etc/group $CROSS_ROOT/etc/group
sudo mkdir -p $CROSS_ROOT/$HOME
sudo rm -Rf $CROSS_ROOT/$HOME
pushd /
sudo mv $HOME $CROSS_ROOT/$HOME
sudo ln -s $CROSS_ROOT/$HOME $HOME
sudo chown `whoami`:`whoami` $HOME
popd
sudo mkdir -p $CROSS_ROOT/etc/sudoers.d
echo 'Defaults lecture = never' | sudo tee $CROSS_ROOT/etc/sudoers.d/lecture
sudo chmod 440 $CROSS_ROOT/etc/sudoers.d/lecture
sudo ln -f /etc/sudoers $CROSS_ROOT/etc/sudoers
# TODO disable password prompt
# prepare bash wrapper
sudo cp .github/bash-wrapper /bin/
sudo chown root:root /bin/bash-wrapper
sudo chmod a+x /bin/bash-wrapper
# TODO remove after debug
set
- name: Test bash-wrapper
shell: bash-wrapper {0}
run: |
echo Running as $(whoami) in $(pwd)
whoami
echo Target platform is $(uname -a)
echo -n 'Path to sudo: '
which sudo || echo not found
set
bash-wrapper
is a shell script with the following content:
#!/bin/bash
CROSS_ROOT=/opt/chroot/armhf
# FIXME for some reasons, GHA passes scripts but does not set the executable flag,
# so we need to work around this.
test -f $1 && sudo chmod a+x $1
sudo chroot --userspec=`whoami`:`whoami` $CROSS_ROOT qemu-arm-static /bin/bash -c "cd $GITHUB_WORKSPACE; $*"
Comparing set
output on both host and guest systems, I notice a few differences:
GITHUB_*
variables are missing in the guestHOME
and USER
are set for root
, even though bash is running as a different user, runner
.How can I pass the host environment to the guest, with only a few exceptions (BASH_*
, MACHTYPE
, OSTYPE
)? Or is there an easier approach to set up a VM for a foreign architecture and execute GHA steps in it?
For a handful of known variables, export
them when calling bash
from bash-wrapper
:
sudo chroot --userspec=`whoami`:`whoami` $CROSS_ROOT qemu-arm-static /bin/bash -c "export HOME=$HOME; cd $GITHUB_WORKSPACE; $*"
Limitations: you need to know which variables you need, and for a large number of variables, this method may become impractical.