[ADD] host deps: the tools six packages needed and nobody declared

--nodeps means every makedepend must be named here by hand. Seven builds
stopped on one, each naming a different tool: itstool, convert, fig2dev,
asciidoctor, libnsl, python -m build, libbpf, history.

The list was also a lie by omission. libreadline-dev, libncurses-dev,
zlib1g-dev, python3-dev, doxygen, xsltproc, docbook-xsl, elinks and
libcap2-bin were on this VM by hand and never declared. They made the
builds pass here and would fail on a fresh Ubuntu, for reasons that have
nothing to do with s390x.

history.pc is generated rather than copied: our own readline package
ships a correct one, but its libdir names /usr/lib, which on a multiarch
host holds no libhistory. The .pc has to describe THIS host.

Three hooks for what apt cannot buy. systemd asks for an EFI arch s390x
does not have -- and says "python3 is missing modules: elftools", which
sends you to apt for four lines before admitting the real reason.

--- FR ---

--nodeps veut dire que chaque makedepend doit être nommé ici à la main.
Sept compilations se sont arrêtées sur l'une d'elles, chacune désignant
un outil différent : itstool, convert, fig2dev, asciidoctor, libnsl,
python -m build, libbpf, history.

La liste mentait aussi par omission. libreadline-dev, libncurses-dev,
zlib1g-dev, python3-dev, doxygen, xsltproc, docbook-xsl, elinks et
libcap2-bin étaient posés à la main sur cette VM, jamais déclarés. Ils
faisaient passer les compilations ici et auraient échoué sur un Ubuntu
neuf, pour des raisons étrangères à s390x.

history.pc est généré et non copié : notre propre paquet readline en
livre un correct, mais son libdir nomme /usr/lib, qui sur un hôte
multiarch ne contient aucun libhistory. Le .pc doit décrire CET hôte-ci.

Trois crochets pour ce qu'apt n'achète pas. systemd réclame une
architecture EFI que s390x n'a pas — et annonce « python3 is missing
modules: elftools », ce qui envoie chez apt pour quatre lignes avant
d'avouer la vraie raison.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-17 01:33:41 -04:00
parent 04ee9be5cc
commit dcaa55b19a
4 changed files with 282 additions and 1 deletions

85
patches/pkgbuild/brotli.sh Executable file
View file

@ -0,0 +1,85 @@
#!/usr/bin/env bash
# brotli: two Debian defaults, and both of them ship silently.
#
# Neither produces an error. makepkg exits 0 both times and the repository
# gains a package whose files are in places nothing on the target will look.
# This is the case "an exit code proves nothing, only the artefact proves"
# was written for -- the symptom arrives months later, far from the cause.
set -euo pipefail
# 1. The C library, and the one that actually matters.
#
# brotli's PKGBUILD calls plain `cmake`, not the arch-cmake stand-in, so it
# never sees the -DCMAKE_INSTALL_LIBDIR=lib that stand-in passes. CMake's
# GNUInstallDirs then picks its branch by sniffing the host: no
# /etc/arch-release, /etc/debian_version present, so
#
# CMAKE_INSTALL_LIBDIR = lib/s390x-linux-gnu
#
# Measured on a DESTDIR install of a real build: libbrotlicommon.so.1,
# libbrotlidec.so.1, libbrotlienc.so.1 and their .pc files all under
# /usr/lib/s390x-linux-gnu. brotli is in stage 1 only because the chroot said
# openssl and curl need libbrotli*.so -- and the filesystem package's
# /etc/ld.so.conf reads only ld.so.conf.d, so the target would never find
# them. A no-op in stage 2, where CMake takes the /etc/arch-release branch.
python3 - <<'PY'
import io
s = io.open("PKGBUILD", encoding="utf-8").read()
old = ' -DCMAKE_INSTALL_PREFIX=/usr \\\n'
assert s.count(old) == 1, "expected exactly one cmake configure, found %d" % s.count(old)
s = s.replace(old, old + ' -DCMAKE_INSTALL_LIBDIR=lib \\\n', 1)
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
PY
grep -q 'DCMAKE_INSTALL_LIBDIR=lib' PKGBUILD || {
echo "brotli: libdir not pinned" >&2; exit 1; }
echo "brotli: cmake libdir pinned to lib (host GNUInstallDirs says multiarch)"
# 2. The python wheel, and an honest account of what this buys.
#
# package_python-brotli() runs
#
# python -m installer --destdir="$pkgdir" dist/*.whl
#
# and `installer` asks sysconfig where a wheel belongs. Debian and Ubuntu
# patch sysconfig to prefer the "posix_local" scheme, so the answer here is
# /usr/local/lib/python3.13/dist-packages. Verified by running the real wheel
# through the real installer into a scratch pkgdir.
#
# BE CLEAR ABOUT WHAT THIS FIXES. It does NOT make the module importable on
# Arch. Arch ships python 3.14 and this wheel is cp313 with an ABI-tagged
# _brotli.cpython-313-*.so, so no destination makes a stage-1 wheel work on
# the target; stage 2 is what produces a usable one. What the move buys is
# that an Arch package must not ship files under /usr/local, which Arch
# reserves for the administrator. That is the whole justification, and
# writing a bigger one here would mislead the next reader.
#
# `installer --prefix=/usr` does NOT help: posix_local is literally
# "{base}/local/lib/pythonX.Y/dist-packages", so overriding base with /usr
# still lands in /usr/local. The SCHEME would have to change, and the only
# supported way to change it is to be a different interpreter.
#
# Guarded on the directory, not the host name: under an Arch python the
# directory is absent and the block does nothing. Only purelib moves -- the
# wheel declares py_modules=["brotli"] plus the extension, no scripts and no
# data files, so nothing else of it ever leaves /usr/local.
python3 - <<'PY'
import io
s = io.open("PKGBUILD", encoding="utf-8").read()
old = ' python -m installer --destdir="$pkgdir" dist/*.whl\n'
assert old in s, "installer invocation not found"
new = old + """ # Ubuntu's python installs to /usr/local/lib/pythonX.Y/dist-packages
# (sysconfig's posix_local scheme). An Arch package may not ship /usr/local.
local _pyver=$(python -c 'import sys; print("%d.%d" % sys.version_info[:2])')
if [ -d "$pkgdir/usr/local/lib/python$_pyver/dist-packages" ]; then
install -dm 755 "$pkgdir/usr/lib/python$_pyver"
mv "$pkgdir/usr/local/lib/python$_pyver/dist-packages" \\
"$pkgdir/usr/lib/python$_pyver/site-packages"
rm -rf "$pkgdir/usr/local"
fi
"""
s = s.replace(old, new, 1)
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
PY
grep -q "posix_local scheme" PKGBUILD || {
echo "brotli: wheel relocation not inserted" >&2; exit 1; }
echo "brotli: python wheel moved out of /usr/local"

44
patches/pkgbuild/pam.sh Executable file
View file

@ -0,0 +1,44 @@
#!/usr/bin/env bash
# pam: the PDF removal assumes fop, which the Ubuntu host does not have.
#
# rm: cannot remove
# '.../pkg/pam/usr/share/doc/Linux-PAM/*.pdf': No such file or directory
#
# package() ends on
#
# # remove unreproducible pdf files
# rm "${pkgdir}"/usr/share/doc/Linux-PAM/*.pdf
#
# and that line only works because Arch lists fop in makedepends. fop is a
# Java renderer; putting a JVM on the build host to produce three PDFs that
# the very next line deletes buys nothing, so the host stays without it.
#
# Without fop meson drops every PDF target in silence -- doc/adg, doc/mwg and
# doc/sag each guard theirs with `if prog_fop.found()`, and the configured
# build.ninja then holds zero .pdf rules. The DIRECTORY still exists, because
# the HTML and text manuals install beside them, so the glob is never
# expanded and rm is handed the literal string `*.pdf`. Same shape as the
# util-linux glob that started the libdir hunt: the message names a path that
# was never going to exist, and it aborts package() AFTER a successful
# compile -- so the build looks like it failed at the very last step for no
# reason at all.
#
# -f is the whole fix. With fop the PDFs are still removed; without it the
# empty glob stops being an error.
#
# Not a hook, but the other half of pam and worth finding here: libnsl and
# libtirpc are NOT optional on this host even though upstream marks NIS
# `auto`, because arch-meson passes --auto-features enabled, which promotes
# every auto feature to a hard requirement. Same mechanism turns pam's `docs`
# feature into five fatal xmlcatalog probes. All of it is host packages, and
# they are declared in install_host_deps.
set -euo pipefail
# Exactly one removal, and it must be the pdf one. Never a blanket rewrite:
# package() also runs `install` and `chmod +s` on paths under the same tree,
# and meson install itself writes there.
n=$(grep -cF 'rm "${pkgdir}"/usr/share/doc/Linux-PAM/' PKGBUILD || true)
[ "$n" = "1" ] || { echo "pam: expected 1 pdf removal, found $n" >&2; exit 1; }
sed -i 's|rm "${pkgdir}"/usr/share/doc/Linux-PAM/|rm -f "${pkgdir}"/usr/share/doc/Linux-PAM/|' PKGBUILD
grep -qF 'rm -f "${pkgdir}"/usr/share/doc/Linux-PAM/*.pdf' PKGBUILD || {
echo "pam: pdf removal not made tolerant" >&2; exit 1; }
echo "pam: pdf removal tolerates the empty glob (host has no fop)"

83
patches/pkgbuild/systemd.sh Executable file
View file

@ -0,0 +1,83 @@
#!/usr/bin/env bash
# systemd: two build options Arch can hold and s390x cannot.
#
# Neither is a missing host package -- both are hard errors raised by meson
# because the PKGBUILD ASKS for something that does not exist on this
# architecture, so no amount of apt-get makes them go away. The libraries
# systemd wants (libbpf, clang, libfdisk, libkmod, ...) ARE host packages and
# belong in install_host_deps, not here.
set -euo pipefail
# 1. -Dbootloader=enabled: systemd-boot is EFI, and s390x has no EFI.
#
# The message you get is NOT about EFI:
#
# systemd/meson.build:1638:19: ERROR: python3 is missing modules: elftools
#
# because meson.build:1638 asks for pyelftools with
# `required : get_option('bootloader')`, and the PKGBUILD set that to enabled.
#
# THE TRAP is that this reads like a missing host package, and
# `apt install python3-pyelftools` looks like the fix. It is not. It buys four
# lines, and then meson.build:1642 says what is really wrong:
#
# ERROR: Feature bootloader cannot be enabled: unsupported EFI arch or
# EFI support is disabled
#
# (verified by planting a stub elftools module on PYTHONPATH; without it the
# EFI message is unreachable, which is why nobody ever sees it first.)
#
# meson.build:1627 maps a cpu family to an EFI machine type name -- aa64, arm,
# loongarch32/64, riscv32/64, x64, ia32 -- and s390x is not in that table, so
# efi_arch is ''. -Defi is still true; the architecture simply has no EFI.
# IBM Z boots from an IPL record, there is no ESP for systemd-boot to write
# into, and no configuration invents one.
#
# The cost is the systemd-boot artefacts. bootctl itself is still built and
# installed -- checked in the install plan -- package_systemd() names neither,
# and the arch.conf/loader.conf/splash-arch.bmp it ships are `install`ed from
# $srcdir rather than built. Packaging is untouched. Disabling also drops the
# pyelftools requirement in the same line.
sed -i 's/-Dbootloader=enabled/-Dbootloader=disabled/' PKGBUILD
grep -q -- '-Dbootloader=disabled' PKGBUILD || {
echo "systemd: bootloader option not rewritten" >&2; exit 1; }
echo "systemd: bootloader disabled (s390x has no EFI machine type)"
# 2. -Dvmlinux-h=provided: the file Arch points at is an Arch file.
#
# src/bpf/meson.build: error('Path to provided vmlinux.h does not exist.')
#
# Arch's linux-headers ships /usr/src/linux/vmlinux.h, the path hard-coded in
# the PKGBUILD. Ubuntu's linux-headers-* ship no vmlinux.h anywhere. This
# failure only appears AFTER libbpf and clang are installed, because the whole
# block is skipped while BPF_FRAMEWORK is off -- fixing libbpf uncovers it.
#
# systemd's own fallback dumps the header out of the running kernel's BTF,
# which is what 'generated' selects:
#
# bpftool btf dump file /sys/kernel/btf/vmlinux format c
#
# Checked on this host: exit 0, 122471 lines, and enum lsm_integrity_type is
# there at line 15931, so restrict-fsaccess.bpf.c builds rather than being
# quietly dropped.
#
# THE TRAP is that 'auto' would NOT have done this for us. The auto branch
# generates only when `host_machine.cpu_family() in ['x86_64', 'aarch64']`; on
# s390x it falls through to "neither provided nor generated" and silently
# drops the BPF programs that need the header. s390x has to say it out loud.
#
# Note the asymmetry this buys: on the 'provided' branch systemd probes the
# header with cc.compiles() before deciding what to build. On 'generated' it
# sets have_lsm_integrity_type = true outright. So the build now depends on
# the BTF of the kernel RUNNING when it starts -- fine on 6.17.0-41, and on an
# older kernel it turns into a compile error with no fallback.
#
# -Dvmlinux-h-path goes with it: 'generated' ignores the value, and leaving a
# path that resolves to nothing invites the next reader to "repair" it.
sed -i 's/-Dvmlinux-h=provided/-Dvmlinux-h=generated/' PKGBUILD
sed -i '/-Dvmlinux-h-path=/d' PKGBUILD
grep -q -- '-Dvmlinux-h=generated' PKGBUILD || {
echo "systemd: vmlinux.h still read from a provided path" >&2; exit 1; }
grep -q -- '-Dvmlinux-h-path' PKGBUILD && {
echo "systemd: stale vmlinux-h-path left in place" >&2; exit 1; }
echo "systemd: vmlinux.h generated from /sys/kernel/btf/vmlinux"

View file

@ -37,6 +37,30 @@ install_host_deps() {
# systemtap-sdt-dev and gmp/mpfr/mpc for glibc and gcc, autopoint and # systemtap-sdt-dev and gmp/mpfr/mpc for glibc and gcc, autopoint and
# gperf for coreutils, asciidoc for pacman, po4a for xz, gnat for gcc's # gperf for coreutils, asciidoc for pacman, po4a for xz, gnat for gcc's
# Ada front end, debuginfod and jansson for binutils. # Ada front end, debuginfod and jansson for binutils.
#
# THE LIST WAS ALSO A LIE BY OMISSION. An audit of the six packages that
# follow found libreadline-dev, libncurses-dev, zlib1g-dev, python3-dev,
# doxygen, xsltproc, docbook-xsl, elinks and libcap2-bin already present
# on the build VM but installed BY HAND, never declared here. On this
# host they made the builds pass; on a fresh Ubuntu they would have
# failed at readline, at libxml2 and inside shadow's `make install`, for
# reasons that have nothing to do with s390x. They are named below now.
# The rest is per-package, and each group says which package needs it.
#
# gnupg imagemagick + fig2dev render doc/*.svg and doc/*.fig, which
# a git checkout must generate; librsvg2-bin guarantees the
# SVG coder even under --no-install-recommends.
# shadow itstool builds the translated man pages; libcap2-bin gives
# the bare `setcap` its install rule calls.
# util-linux asciidoctor -- Ruby, and NOT the `asciidoc` above, which
# is a different program added for pacman. Easy to mistake
# for coverage.
# pam libnsl/libtirpc for NIS, the DocBook 5 catalog, and elinks,
# which meson uses to dump the HTML manuals to text.
# brotli cmake, and the PEP 517 chain its python bindings build with.
# libxml2 ICU, readline, and the doc toolchain.
# systemd the widest surface of anything in stage 1; every one of
# these was checked against a real meson configure.
sudo apt-get -o DPkg::Lock::Timeout=600 install -y -qq \ sudo apt-get -o DPkg::Lock::Timeout=600 install -y -qq \
meson ninja-build pkg-config gettext \ meson ninja-build pkg-config gettext \
libarchive-dev libcurl4-openssl-dev libgpgme-dev libssl-dev \ libarchive-dev libcurl4-openssl-dev libgpgme-dev libssl-dev \
@ -46,7 +70,19 @@ install_host_deps() {
systemtap-sdt-dev asciidoc autopoint gperf help2man rsync \ systemtap-sdt-dev asciidoc autopoint gperf help2man rsync \
libgmp-dev libmpfr-dev libmpc-dev python3-docutils \ libgmp-dev libmpfr-dev libmpc-dev python3-docutils \
libseccomp-dev libpcre2-dev \ libseccomp-dev libpcre2-dev \
po4a gnat libdebuginfod-dev libjansson-dev po4a gnat libdebuginfod-dev libjansson-dev \
libreadline-dev libncurses-dev zlib1g-dev python3-dev \
doxygen xsltproc docbook-xsl libcap2-bin \
imagemagick fig2dev librsvg2-bin \
itstool asciidoctor \
libtirpc-dev libnsl-dev docbook5-xml docbook-xsl-ns elinks \
cmake python3-build python3-installer python3-pkgconfig \
python3-setuptools python3-wheel \
libicu-dev \
libbpf-dev clang libapparmor-dev libfdisk-dev libkmod-dev libdw-dev \
libpwquality-dev libxkbcommon-dev libdbus-1-dev libqrencode-dev \
libfido2-dev libtss2-dev libmicrohttpd-dev libaudit-dev \
libcryptsetup-dev libgcrypt20-dev libgnutls28-dev libpam0g-dev
install_host_shims install_host_shims
} }
@ -62,6 +98,39 @@ install_host_shims() {
log "Host shims" log "Host shims"
local d="$HERE_DIR/devtools" local d="$HERE_DIR/devtools"
sudo install -m755 "$d/arch-meson" "$d/arch-cmake" /usr/local/bin/ sudo install -m755 "$d/arch-meson" "$d/arch-cmake" /usr/local/bin/
# history.pc, which Ubuntu drops and Arch ships.
#
# GNU readline generates and installs BOTH readline.pc and history.pc;
# Debian and Ubuntu keep the first and delete the second, while the
# library, libhistory.so, is right there in libreadline-dev. libxml2 asks
# pkg-config for `history` and stops:
#
# libxml2/meson.build:353:18: ERROR: Dependency "history" not found
#
# THE TRAP: our own readline package in repo/s390x DOES ship a correct
# history.pc, so copying that one looks like the tidy answer. It is not.
# Its libdir is ${exec_prefix}/lib -- right for the target rootfs, wrong
# for a multiarch host where /usr/lib holds no libhistory. The .pc has to
# describe THIS host, so it is generated from the host's own readline.pc.
local multiarch version
multiarch="$(dpkg-architecture -qDEB_HOST_MULTIARCH)"
version="$(pkg-config --modversion readline)"
sudo mkdir -p /usr/local/lib/pkgconfig
printf '%s\n' \
"prefix=/usr" \
"exec_prefix=\${prefix}" \
"libdir=/usr/lib/$multiarch" \
"includedir=\${prefix}/include" \
"" \
"Name: History" \
"Description: GNU History library" \
"Version: $version" \
"Requires.private: tinfo" \
"Libs: -L\${libdir} -lhistory" \
"Cflags: -I\${includedir}" \
| sudo tee /usr/local/lib/pkgconfig/history.pc > /dev/null
pkg-config --exists history || {
echo "history.pc still not visible to pkg-config" >&2; return 1; }
} }
build_pacman() { build_pacman() {