diff --git a/scripts/build-xlibre.sh b/scripts/build-xlibre.sh new file mode 100755 index 0000000..1072280 --- /dev/null +++ b/scripts/build-xlibre.sh @@ -0,0 +1,325 @@ +#!/usr/bin/env bash +set -euo pipefail + +repoUrl="https://github.com/X11Libre/pkgbuilds-arch-based.git" +rootfsUrl="http://os.archlinuxarm.org/os/ArchLinuxARM-aarch64-latest.tar.gz" + +aarch64Packages=( + xlibre-xserver + xlibre-input-libinput + xlibre-video-fbdev + xlibre-video-amdgpu + xlibre-video-ati + xlibre-video-nouveau +) + +outputDir=$(pwd -P) +workDir="" +chrootDir="" +binfmtMounted=false +binfmtRegistered=false + +log() { + printf '%s %s\n' "$*" "$(date '+%Y-%m-%d %H:%M:%S')" +} + +die() { + printf 'Error: %s\n' "$*" >&2 + exit 1 +} + +cleanup() { + local exitStatus=$? + local cleanupFailed=false + local mountTarget + local chrootMounts=() + + trap - EXIT INT TERM + + if [[ -n "$workDir" && -d "$workDir" ]]; then + log "Removing temporary chroot" + if [[ -n "$chrootDir" ]]; then + mapfile -t chrootMounts < <( + findmnt -rn -o TARGET | + awk -v root="$chrootDir" '$0 == root || index($0, root "/") == 1' | + sort -r + ) + fi + for mountTarget in "${chrootMounts[@]}"; do + if ! mountpoint -q "$mountTarget"; then + continue + fi + if ! umount --recursive "$mountTarget"; then + printf 'Warning: normal chroot unmount failed; detaching it lazily\n' >&2 + umount --recursive --lazy "$mountTarget" || cleanupFailed=true + fi + done + rm -rf --one-file-system "$workDir" || cleanupFailed=true + if [[ -e "$workDir" ]]; then + printf 'Error: failed to remove temporary chroot: %s\n' "$workDir" >&2 + cleanupFailed=true + fi + fi + + if [[ "$binfmtRegistered" == true && -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]]; then + printf '%s' -1 > /proc/sys/fs/binfmt_misc/qemu-aarch64 || cleanupFailed=true + fi + + if [[ "$binfmtMounted" == true ]]; then + umount /proc/sys/fs/binfmt_misc || cleanupFailed=true + fi + + if [[ "$cleanupFailed" == true && "$exitStatus" -eq 0 ]]; then + exitStatus=1 + fi + + exit "$exitStatus" +} + +require_commands() { + local commandName + local missingCommands=() + + for commandName in arch-chroot awk bsdtar chown cp curl date findmnt grep install mkdir mktemp mount mountpoint rm sort tr umount; do + if ! command -v "$commandName" >/dev/null 2>&1; then + missingCommands+=("$commandName") + fi + done + + if ((${#missingCommands[@]})); then + die "Missing required commands: ${missingCommands[*]}" + fi + + [[ -x /usr/bin/qemu-aarch64-static ]] || die "/usr/bin/qemu-aarch64-static is required" + [[ -r /usr/lib/binfmt.d/qemu-aarch64-static.conf ]] || die "The qemu-user-static-binfmt package is required" +} + +configure_binfmt() { + if ! mountpoint -q /proc/sys/fs/binfmt_misc; then + mount -t binfmt_misc binfmt_misc /proc/sys/fs/binfmt_misc + binfmtMounted=true + fi + + if [[ ! -e /proc/sys/fs/binfmt_misc/qemu-aarch64 ]]; then + tr -d '\n' < /usr/lib/binfmt.d/qemu-aarch64-static.conf > /proc/sys/fs/binfmt_misc/register + binfmtRegistered=true + fi + + grep -qx 'enabled' /proc/sys/fs/binfmt_misc/qemu-aarch64 || die "aarch64 binfmt registration is not enabled" +} + +create_build_script() { + local buildScript=$1 + + install -m 0755 /dev/stdin "$buildScript" <<'CHROOT_SCRIPT' +#!/usr/bin/env bash +set -euo pipefail + +repoUrl=$1 +shift +packages=("$@") +repoDir=/build/pkgbuilds + +log() { + printf '%s %s\n' "$*" "$(date '+%Y-%m-%d %H:%M:%S')" +} + +install_dependencies() { + local packageDir=$1 + local dependency + local outputPackage + local srcInfo + local dependencies=() + local filteredDependencies=() + local missingDependencies=() + local outputPackages=() + local -A internalPackages=() + + # $1 is expanded by the nested builder shell, not this root shell. + # shellcheck disable=SC2016 + srcInfo=$(runuser -u builder -- bash -c \ + 'cd "$1" && makepkg --printsrcinfo' _ "$packageDir") + + mapfile -t outputPackages < <( + printf '%s\n' "$srcInfo" | + awk -F ' = ' '/^[[:space:]]*pkgname = / { print $2 }' + ) + for outputPackage in "${outputPackages[@]}"; do + internalPackages["$outputPackage"]=1 + done + + mapfile -t dependencies < <( + printf '%s\n' "$srcInfo" | + awk -F ' = ' ' + /^[[:space:]]*(depends|makedepends|checkdepends)(_aarch64)? = / { + dependency=$2 + sub(/[<>=].*/, "", dependency) + print dependency + } + ' | + sort -u + ) + for dependency in "${dependencies[@]}"; do + if [[ -z ${internalPackages[$dependency]+present} ]]; then + filteredDependencies+=("$dependency") + fi + done + ((${#filteredDependencies[@]})) || return 0 + + mapfile -t missingDependencies < <(pacman -T "${filteredDependencies[@]}" || true) + ((${#missingDependencies[@]})) || return 0 + + log "Installing dependencies for ${packageDir##*/}" + pacman -S --asdeps --needed --noconfirm "${missingDependencies[@]}" +} + +remove_installed_packages() { + local packageName + local installedPackages=() + + for packageName in "$@"; do + if pacman -Q "$packageName" >/dev/null 2>&1; then + installedPackages+=("$packageName") + fi + done + ((${#installedPackages[@]})) || return 0 + + log "Removing conflicting packages: ${installedPackages[*]}" + pacman -Rdd --noconfirm "${installedPackages[@]}" +} + +build_package() { + local packageName=$1 + local installAfterBuild=${2:-false} + local packageDir="$repoDir/$packageName" + local builtPackages=() + + [[ -f "$packageDir/PKGBUILD" ]] || { + printf 'Missing PKGBUILD for %s\n' "$packageName" >&2 + return 1 + } + + log "Building $packageName" + install_dependencies "$packageDir" + # $1 is expanded by the nested builder shell, not this root shell. + # shellcheck disable=SC2016 + runuser -u builder -- bash -c \ + 'cd "$1" && makepkg --noconfirm --clean --cleanbuild' _ "$packageDir" + + mapfile -d '' builtPackages < <( + find "$packageDir" -maxdepth 1 -type f -name '*.pkg.tar.*' ! -name '*.sig' -print0 + ) + ((${#builtPackages[@]})) || { + printf 'No package artifacts were produced for %s\n' "$packageName" >&2 + return 1 + } + + if [[ "$installAfterBuild" == true ]]; then + case "$packageName" in + xlibre-input-libinput) + remove_installed_packages xf86-input-libinput + ;; + xlibre-xserver) + remove_installed_packages \ + xorg-server \ + xorg-server-common \ + xorg-server-devel \ + xorg-server-xephyr \ + xorg-server-xnest \ + xorg-server-xvfb \ + glamor-egl \ + xf86-video-modesetting + ;; + esac + log "Installing $packageName for subsequent builds" + pacman -U --noconfirm "${builtPackages[@]}" + fi +} + +log "Initializing Arch Linux ARM" +# Pacman's Landlock sandbox is unavailable through qemu-user emulation. +sed -i \ + -e '/^[[:space:]]*DisableSandbox[[:space:]]*$/d' \ + -e '/^\[options\][[:space:]]*$/a DisableSandbox' \ + /etc/pacman.conf +pacman-key --init +pacman-key --populate archlinuxarm +pacman -Syu --noconfirm +pacman -S --needed --noconfirm base-devel git + +useradd --create-home --shell /bin/bash builder +install -d -o builder -g builder /build + +log "Cloning XLibre PKGBUILDs" +runuser -u builder -- git clone --depth 1 "$repoUrl" "$repoDir" + +# The input driver must first be built against the stock xorg-server-devel. +build_package xlibre-input-libinput true + +# Installing all server split packages replaces the stock Xorg server/devel +# packages and provides the ABI dependencies needed by the video drivers. +build_package xlibre-xserver true + +for packageName in "${packages[@]}"; do + case "$packageName" in + xlibre-input-libinput|xlibre-xserver) + continue + ;; + esac + build_package "$packageName" +done + +install -d /output +find "$repoDir" -type f -name '*.pkg.tar.*' ! -name '*.sig' -exec cp -t /output -- {} + +log "All requested aarch64 packages built" +CHROOT_SCRIPT +} + +main() { + local rootfsArchive + local packageFile + local copiedPackageCount=0 + + ((EUID == 0)) || die "Run this script as root: sudo $0" + require_commands + trap cleanup EXIT + trap 'exit 130' INT + trap 'exit 143' TERM + configure_binfmt + + workDir=$(mktemp -d /tmp/xlibre-aarch64.XXXXXX) + chrootDir="$workDir/root" + rootfsArchive="$workDir/ArchLinuxARM-aarch64-latest.tar.gz" + mkdir -p "$chrootDir" + + log "Downloading a fresh Arch Linux ARM aarch64 root filesystem" + curl --fail --location --retry 3 --output "$rootfsArchive" "$rootfsUrl" + + log "Creating aarch64 chroot" + bsdtar -xpf "$rootfsArchive" -C "$chrootDir" + install -m 0755 /usr/bin/qemu-aarch64-static "$chrootDir/usr/bin/qemu-aarch64-static" + cp --remove-destination /etc/resolv.conf "$chrootDir/etc/resolv.conf" + create_build_script "$chrootDir/root/build-xlibre" + + # arch-chroot expects the chroot root to be a mountpoint. + mount --bind "$chrootDir" "$chrootDir" + + log "Starting aarch64 package build" + arch-chroot "$chrootDir" /root/build-xlibre "$repoUrl" "${aarch64Packages[@]}" + + shopt -s nullglob + for packageFile in "$chrootDir"/output/*.pkg.tar.*; do + [[ ${packageFile##*/} == *-aarch64.pkg.tar.* ]] || die "Unexpected non-aarch64 artifact: ${packageFile##*/}" + install -m 0644 "$packageFile" "$outputDir/" + ((copiedPackageCount += 1)) + if [[ -n ${SUDO_UID:-} && -n ${SUDO_GID:-} ]]; then + chown "$SUDO_UID:$SUDO_GID" "$outputDir/${packageFile##*/}" + fi + done + shopt -u nullglob + + ((copiedPackageCount > 0)) || die "No package artifacts were copied to $outputDir" + log "Copied $copiedPackageCount completed packages to $outputDir" +} + +main "$@" diff --git a/scripts/upgrade-xlibre.sh b/scripts/upgrade-xlibre.sh deleted file mode 100755 index 819d6f9..0000000 --- a/scripts/upgrade-xlibre.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -startDir="$(pwd)" -buildDir="${startDir}/xlibre-build" - -packageList=( - xlibre-input-libinput - xlibre-xserver - xlibre-video-amdgpu - xlibre-video-ati - xlibre-video-fbdev - xlibre-video-intel - xlibre-video-nouveau - xlibre-video-vesa - xlibre-video-dummy-with-vt -) - -mkdir -p "${buildDir}" - -for i in "${packageList[@]}" ; do - yay -Ga "$i" - pushd "$i" - makepkg -Acrsf - cp -v ./*.pkg.tar.* "${buildDir}/" - popd -done