326 lines
9.1 KiB
Bash
Executable File
326 lines
9.1 KiB
Bash
Executable File
#!/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 "$@"
|