Bisecting Mesa on Ubuntu isn’t as straight-forward as my previous post might suggest. Most modern workloads use 64-bit binaries, but Steam still requires 32-bit libraries and game managers like Uplay frequently carry similar requirements. Deploying different builds of Mesa for 64- and 32-bit often leads to workload instability.

Cross-compilng Mesa on Ubuntu is a bit fraught, however. Until recently, one needed to install the 32-bit valgrind package to build 32-bit Mesa, which forced the 64-bit valgrind package to be uninstalled. As of this writing, Mesa requires LLVM 15, and installing the 32bit version of LLVM 15 on Ubuntu 22.04 will remove key packages like ubuntu-desktop and xorg. If in ignorance one uses apt’s -y option, sadness ensues.

These considerations render Mesa’s standard cross-compilation method ill-suited to Ubuntu. There is, however, an alternative approach that utilizes schroot, which enables us to provision build environments inside chroots, much like a Python venv. The following script illustrates the technique.

#!/bin/sh

set -e # terminate on errors
set -x # echo commands

BUILD_OPTS="-Dglvnd=true"
SRC_DIR=$HOME/src/mesa

CODENAME=$(lsb_release --codename --short)
# make sure source is available
if [ ! -d "$SRC_DIR" ]; then
    mkdir -p $SRC_DIR
fi

set +e # allow errors temporarily
git -C $SRC_DIR rev-parse 2>/dev/null
exit_code=$(echo $?)
set -e
if [ "$exit_code" -ne 0 ] ; then
    echo "Cloning source..."
    # checkout source
    git clone https://gitlab.freedesktop.org/mesa/mesa.git $SRC_DIR
else
    echo "Source already cloned."
fi

# configure execution-wide state
BUILD_ID=`git -C $SRC_DIR describe --always --tags`
INSTALL_DIR=/usr/local-$BUILD_ID

build_mesa() {
        # $1: The schroot architecure
        # $2: The name of the schroot environment
        # $3: The schroot personality
        # ref: https://unix.stackexchange.com/questions/12956/how-do-i-run-32-bit-programs-on-a-64-bit-debian-ubuntu
        SCHROOT_PATH="/build/$CODENAME/$1"
        
        sudo apt -y install schroot debootstrap
        sudo mkdir -p $SCHROOT_PATH

        echo "Bootstrapping environment..."
        set +e # debootstrap will return non-zero if the environment has been previously provisioned
        sudo debootstrap --arch $1 $CODENAME $SCHROOT_PATH http://archive.ubuntu.com/ubuntu
        set -e

        echo "Configuring apt..."
        # create minimum viable apt sources
        # ref: https://stackoverflow.com/questions/17487872/shell-writing-many-lines-in-a-file-as-sudo
        sudo sh -c "cat > $SCHROOT_PATH/etc/apt/sources.list" << EOF
deb http://archive.ubuntu.com/ubuntu $CODENAME universe restricted main multiverse
deb http://archive.ubuntu.com/ubuntu ${CODENAME}-updates universe restricted main multiverse
deb http://archive.ubuntu.com/ubuntu ${CODENAME}-backports universe restricted main multiverse
deb http://archive.ubuntu.com/ubuntu ${CODENAME}-security universe restricted main multiverse
deb-src http://us.archive.ubuntu.com/ubuntu/ $CODENAME universe restricted main multiverse
deb-src http://us.archive.ubuntu.com/ubuntu/ ${CODENAME}-updates universe restricted main multiverse
deb-src http://us.archive.ubuntu.com/ubuntu/ ${CODENAME}-backports universe restricted main multiverse
deb-src http://us.archive.ubuntu.com/ubuntu/ ${CODENAME}-security universe restricted main multiverse
EOF

        echo "Configuring chroot..."
        sudo sh -c "cat > /etc/schroot/chroot.d/$2" << EOF
[$2]
description=64b Mesa Build Env
directory=$SCHROOT_PATH
type=directory
personality=$3
groups=users,admin,sudo
EOF

        sudo schroot -c $2 apt update
        # "-- sh -c" required to pass arguments to chroot correctly
        # ref: https://stackoverflow.com/a/3074544
        sudo schroot -c $2 -- sh -c "apt -y --fix-broken install" # sometimes required for initial setup
        sudo schroot -c $2 -- sh -c "apt -y upgrade"
        sudo schroot -c $2 -- sh -c "apt -y build-dep mesa"
        sudo schroot -c $2 -- sh -c "apt -y install git llvm llvm-15"

        # Contemporary Mesa requires LLVM 15. Make sure it's available
        sudo schroot -c $2 -- sh -c "update-alternatives --install /usr/bin/llvm-config llvm-config /usr/lib/llvm-15/bin/llvm-config 200"

        # do the build
        cd $SRC_DIR
        BUILD_DIR=build-$BUILD_ID/$1
        mkdir -p $BUILD_DIR
        sudo schroot -c $2 -- sh -c "meson setup $BUILD_DIR $BUILD_OPTS --prefix=$INSTALL_DIR"
        sudo schroot -c $2 -- sh -c "ninja -C $BUILD_DIR"
        sudo schroot -c $2 -- sh -c "ninja -C $BUILD_DIR install"

        # deploy
        sudo cp -Tvr "${SCHROOT_PATH}${INSTALL_DIR}" "$INSTALL_DIR"
}

build_mesa "amd64" "${CODENAME}64" "linux"
build_mesa "i386" "${CODENAME}32" "linux32"

As before, environment overrides are required for system components to use the new build. Both 64-bit and 32-bit overrides are necessary.

# Force use of the local Mesa build for GL workloads
LD_LIBRARY_PATH=/usr/local/lib/x86_64-linux-gnu/:/usr/local/lib/i386-linux-gnu/:$LD_LIBRARY_PATH
LIBGL_DRIVERS_PATH=/usr/local/lib/x86_64-linux-gnu/dri:/usr/local/lib/i386-linux-gnu/dri:$LIBGL_DRIVERS_PATH

# Here I force the use of local the local build for Intel GPUs--revise as needed
# Newer versions of the Vulkan Loader should make this simpler;
# see https://github.com/KhronosGroup/Vulkan-Loader/pull/1274
VK_ICD_FILENAMES=/usr/local/share/vulkan/icd.d/intel_icd.x86_64.json:/usr/local/share/vulkan/icd.d/intel_icd.i686.json

Also as before, the new build must be deployed to /usr/local (in a shell that sources the new environment):

sudo service gdm3 stop
sudo ln -sfn /usr/local-`git describe --always --tags` /usr/local
sudo ldconfig # update linker cache
sudo service gdm3 start

I expect this script to evolve over time, so I’ve committed it to GitHub.