diff --git a/patches/pkgbuild/dbus.sh b/patches/pkgbuild/dbus.sh new file mode 100755 index 0000000..69f690e --- /dev/null +++ b/patches/pkgbuild/dbus.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# dbus: three documentation generators, made mandatory by arch-meson. +# +# The build stopped three times in a row, on a different tool each time: +# +# ERROR: Program 'ducktype' not found or not executable +# ERROR: Program 'yelp-build' not found or not executable +# ERROR: Program 'qhelpgenerator qhelpgenerator-qt5' not found +# +# Each looked like its own missing host package, and the first two were duly +# installed. THE TRAP is that chasing them one at a time is the wrong shape: +# the third needs Qt, so the chain does not terminate anywhere reasonable, and +# none of the three was ever really required. +# +# meson.build asks for them like this: +# +# doxygen = find_program('doxygen', required: get_option('doxygen_docs')) +# ducktype = find_program('ducktype', required: get_option('ducktype_docs')) +# yelpbuild = find_program('yelp-build', required: get_option('ducktype_docs')) +# qhelpgen = find_program('qhelpgenerator', ..., required: get_option('qt_help')) +# +# and all three options are features declared with no value -- 'auto'. +# arch-meson passes --auto-features enabled, which promotes every one of them +# to a hard requirement. On Arch that is harmless: the doc tools are in the +# build chroot. Here it turned optional documentation into three consecutive +# build failures. Same mechanism as util-linux's translated man pages and +# expat's docbook converter; dbus is just the clearest instance, because it +# repeated three times in one afternoon. +# +# xml_docs is deliberately LEFT ALONE: it generates the man pages through +# xsltproc, which is installed, and man pages are worth having. +# +# The dbus-docs sub-package goes with the options. Its `_pick docs +# usr/share/doc` is a mv, so with nothing generated it would fail the way +# every other _pick on an absent path in this port has failed -- after a +# successful compile, at the last step, naming a directory rather than a +# reason. Deferred, not architectural: TODO.md records the way back. +set -euo pipefail +python3 - <<'PY' +import io, re +s = io.open("PKGBUILD", encoding="utf-8").read() + +# (a) pin the three features off, beside the options already being pinned +anchor = " -D apparmor=disabled\n" +assert s.count(anchor) == 1, "dbus: meson_options array not in the expected form" +s = s.replace(anchor, + anchor + + " -D doxygen_docs=disabled\n" + " -D ducktype_docs=disabled\n" + " -D qt_help=disabled\n", 1) + +# (b) the sub-package that would have nothing to package +m = re.search(r"pkgname=\((.*?)\)\n", s, re.S) +assert m, "dbus: pkgname array not found" +names = [n for n in re.findall(r"[A-Za-z0-9._+-]+", m.group(1))] +assert "dbus-docs" in names, "dbus: dbus-docs not in pkgname" +kept = [n for n in names if n != "dbus-docs"] +s = s[:m.start()] + "pkgname=(\n" + "".join(" %s\n" % n for n in kept) + ")\n" + s[m.end():] + +# (c) and the _pick that feeds it +old = ' _pick docs "$pkgdir"/usr/share/doc\n' +assert s.count(old) == 1, "dbus: docs _pick not in the expected form" +s = s.replace(old, "", 1) + +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +PY +for o in doxygen_docs ducktype_docs qt_help; do + grep -qF -- "-D $o=disabled" PKGBUILD || { + echo "dbus: $o not pinned off" >&2; exit 1; } +done +grep -qF '_pick docs' PKGBUILD && { + echo "dbus: docs _pick survived and would fail on an absent path" >&2; exit 1; } +python3 - <<'PY' +import io, re, sys +s = io.open("PKGBUILD", encoding="utf-8").read() +m = re.search(r"pkgname=\((.*?)\)\n", s, re.S) +if not m: + sys.exit("dbus: pkgname array unreadable after patching") +names = re.findall(r"[A-Za-z0-9._+-]+", m.group(1)) +if "dbus-docs" in names: + sys.exit("dbus: dbus-docs still declared in pkgname") +print("dbus: pkgname -> %s" % " ".join(names)) +PY +echo "dbus: doc generators pinned off (--auto-features made three of them fatal)" diff --git a/patches/pkgbuild/gettext.sh b/patches/pkgbuild/gettext.sh new file mode 100755 index 0000000..fbb91ca --- /dev/null +++ b/patches/pkgbuild/gettext.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# gettext: --without-selinux. The sixth package to pick up the host's +# libselinux, and the first one nothing warned about. +# +# $ readelf -d usr/lib/libgettextlib-1.0.so | grep NEEDED +# (NEEDED) Shared library: [libselinux.so.1] +# +# Nothing failed. gettext built, passed, and went into the repository with a +# library linked against something Arch has no package for -- the artefact +# audit is the only thing that found it, exactly as it found coreutils, +# findutils, sed and tar before it. +# +# WHY IT CAME BACK. Those four were fixed with per-package hooks, and the +# reasoning at the time was that the same gnulib probe "catches findutils, sed +# and tar" -- a list of the packages then in the build. gettext was not in the +# port yet. It arrived with the dependency closure, uses gnulib too, probes +# selinux/selinux.h unconditionally, and found Ubuntu's libselinux1-dev +# sitting where it always was. +# +# So this is the fifth copy of one three-line fix, and that is the real +# finding: the per-package hook does not scale to a probe that any gnulib +# package can trip. The alternative -- exporting +# ac_cv_header_selinux_selinux_h=no once in build_package -- was considered +# and rejected when coreutils was fixed, on the grounds that a global cache +# override is invisible at the point where it acts. That trade has now been +# paid for five times. TODO.md records it as a decision to revisit rather than +# leaving the next person to rediscover the arithmetic. +# +# Arch's gettext links no libselinux, so this converges with Arch. +set -euo pipefail +grep -q '^ --without-included-libunistring$' PKGBUILD || { + echo "gettext: configure block not in the expected form" >&2; exit 1; } +sed -i 's|^\( --without-included-libunistring\)$|\1 \\\n --without-selinux|' PKGBUILD +[ "$(grep -c -- '--without-selinux' PKGBUILD)" = 1 ] || { + echo "gettext: --without-selinux not inserted exactly once" >&2; exit 1; } +echo "gettext: --without-selinux (gnulib found the host's libselinux)" diff --git a/patches/pkgbuild/krb5.sh b/patches/pkgbuild/krb5.sh new file mode 100755 index 0000000..4668913 --- /dev/null +++ b/patches/pkgbuild/krb5.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# krb5: --with-system-ss, and Ubuntu ships no ss headers. +# +# checking whether system ss package works... configure: error: cannot run +# test program +# +# THE TRAP is that message. "cannot run test program" reads like a broken +# cross-compile or a missing loader -- the kind of thing that sends you into +# config.log looking for a link failure on this architecture. It is neither. +# The subsystem library (ss, from e2fsprogs) has no development package on +# Ubuntu at all: libss2 is runtime-only, libss-dev does not exist, and +# e2fslibs-dev has no candidate. There is no ss.h to compile against, so the +# test program cannot be built, let alone run. +# +# Note what IS present, so the two are not confused: --with-system-et works, +# because comerr-dev ships /usr/include/et/com_err.h. Only ss is missing. +# +# krb5 carries its own copy of the subsystem library and uses it when not told +# otherwise, so dropping the flag costs nothing but a slightly larger binary. +# Building e2fsprogs to supply one header would be the other answer; it is not +# worth a package in the closure, and TODO.md records the choice. +set -euo pipefail +grep -qE '^\s+--with-system-ss \\$' PKGBUILD || { + echo "krb5: --with-system-ss line not in the expected form" >&2; exit 1; } +[ "$(grep -c -- '--with-system-ss' PKGBUILD)" = 1 ] || { + echo "krb5: expected exactly one --with-system-ss" >&2; exit 1; } +sed -i '/^\s\+--with-system-ss \\$/d' PKGBUILD +grep -q -- '--with-system-ss' PKGBUILD && { + echo "krb5: --with-system-ss survived" >&2; exit 1; } +echo "krb5: uses its bundled ss (Ubuntu has no ss development package)" diff --git a/patches/pkgbuild/pinentry.sh b/patches/pkgbuild/pinentry.sh new file mode 100755 index 0000000..82e826e --- /dev/null +++ b/patches/pkgbuild/pinentry.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# pinentry: three graphical front ends, on a machine with no display stack. +# +# configure: error: +# *** +# *** Qt6 (Qt6Core, Qt6Gui, Qt6Widgets) is required. +# *** +# +# Arch builds every front end pinentry has -- tty, curses, emacs, gnome3 and +# qt -- and declares qt6-base, gcr, kguiaddons and kwindowsystem as +# makedepends. None of that exists on this build host, and none of it belongs +# in a bootstrap: pacman needs a passphrase prompt on a terminal, nothing more. +# +# THE HONEST FRAMING. This is not architectural -- Qt runs on s390x perfectly +# well. It is deferred, and TODO.md says so: a desktop on Z would want these +# back, and the way back is to build qt6-base and the KDE pieces first. What +# is not defensible is dragging a GUI toolkit into the closure of a package +# manager's passphrase prompt. +# +# THE SECOND PLACE, and then a THIRD. libsecret and glib2 are in the top-level +# depends= and exist only to serve the front ends being switched off. --nodeps +# means makepkg never checks them, so leaving them would ship a pinentry that +# cannot install -- the same trap as gcc-libs/libhwasan, make/guile and +# gnutls/leancrypto. +# +# The third place is package(), which APPENDS more: +# +# depends+=( libglib-2.0.so libncursesw.so libsecret-1.so ) +# +# The first version of this hook fixed the array at the top of the file, ran +# its guard against that array, passed, and shipped the append untouched. The +# guard checked the place I was thinking about rather than every place the +# name occurs -- which is exactly the mistake the note above warns against. +# Only libncursesw.so survives; the other two name libraries this build no +# longer links. +# +# tty, curses and fallback-curses stay, and so does emacs: it is a protocol +# over a pipe, not a toolkit, and it links nothing extra. +set -euo pipefail +python3 - <<'PY' +import io +s = io.open("PKGBUILD", encoding="utf-8").read() + +for flag in ("--enable-pinentry-gnome3", "--enable-pinentry-qt", "--enable-libsecret"): + off = flag.replace("--enable-", "--disable-", 1) + assert s.count(flag) == 1, "pinentry: expected exactly one %s" % flag + s = s.replace(flag, off, 1) + +old = " 'glibc' 'ncurses' 'libassuan' 'libsecret' 'glib2'\n" +assert s.count(old) == 1, "pinentry: depends block not in the expected form" +s = s.replace(old, " 'glibc' 'ncurses' 'libassuan'\n", 1) + +# package() appends three sonames; two of them are gone with the front ends. +old = " depends+=(\n libglib-2.0.so\n libncursesw.so\n libsecret-1.so\n )\n" +assert s.count(old) == 1, "pinentry: package() depends+= not in the expected form" +s = s.replace(old, " depends+=(\n libncursesw.so\n )\n", 1) + +# package() renames the GTK binary, which is no longer built. +old = ' # The -gtk backend has been built to be used with GTK3.\n mv "${pkgdir}/usr/bin/pinentry-gtk"{-2,}\n' +assert s.count(old) == 1, "pinentry: gtk rename not in the expected form" +s = s.replace(old, "", 1) + +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +PY +for f in gnome3 qt libsecret; do + grep -q -- "--disable-pinentry-$f\|--disable-$f" PKGBUILD || { + echo "pinentry: $f front end still enabled" >&2; exit 1; } +done +grep -qE "^ 'glibc' 'ncurses' 'libassuan'$" PKGBUILD || { + echo "pinentry: depends not reduced" >&2; exit 1; } +# EVERY place the names occur -- but as they are actually WRITTEN, not as bare +# substrings. The first version of this loop grepped for `libsecret` and +# matched the `--disable-libsecret` it had just inserted, so a correct patch +# reported failure. Third guard in this port to check something adjacent to +# what it meant; the lesson is that a guard needs the same care as the edit. +for n in "'libsecret'" "'glib2'" "libglib-2.0.so" "libsecret-1.so" \ + "--enable-libsecret" "--enable-pinentry-qt" "--enable-pinentry-gnome3" \ + 'pinentry-gtk"{-2,}'; do + grep -qF -- "$n" PKGBUILD && { + echo "pinentry: $n still present" >&2; exit 1; } +done +grep -q -- "--enable-pinentry-curses" PKGBUILD || { + echo "pinentry: curses front end lost -- nothing would prompt" >&2; exit 1; } +echo "pinentry: tty/curses/emacs only (no Qt, GNOME or libsecret on this host)" diff --git a/patches/pkgbuild/sqlite.sh b/patches/pkgbuild/sqlite.sh new file mode 100755 index 0000000..6ee9cc8 --- /dev/null +++ b/patches/pkgbuild/sqlite.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# sqlite: the tcl extension installs where Ubuntu's tclConfig.sh says, not +# where Arch's does. +# +# mv: cannot stat '/usr/lib/tcl8.6/sqlite*': No such file or directory +# +# THE TRAP is that tcl was found. The log says so, plainly: +# +# Checking for tclsh8.6.../usr/bin/tclsh8.6 +# Using tclConfig.sh: /usr/lib/s390x-linux-gnu/tclConfig.sh +# +# so "install tcl-dev" looks like it worked, and the failure looks like a +# separate problem. It is the same problem one layer down: tclConfig.sh +# carries TCL_PACKAGE_PATH, and Ubuntu's says share/tcltk while Arch's says +# lib. Measured in the pkgdir: +# +# usr/share/tcltk/tcl8.6/sqlite3.53.4/libsqlite3.53.4.so +# +# and usr/lib/tcl8.6 does not exist at all. This is the multiarch libdir theme +# again -- a path the BUILD HOST decides -- but one layer out from the library +# directory itself, which is why --libdir does not reach it. +# +# The tree is relocated to Arch's path before package_sqlite() splits it out, +# so the upstream mv keeps working unchanged. Same shape as the lib64 merge in +# gcc.sh: move the tree, do not rewrite every path that names it. +set -euo pipefail +python3 - <<'PY' +import io +s = io.open("PKGBUILD", encoding="utf-8").read() +anchor = " # split out tcl extension\n" +assert s.count(anchor) == 1, "sqlite: expected one tcl split block" +fix = """ # Ubuntu's tclConfig.sh puts the tcl package tree under share/tcltk; + # Arch's puts it under lib. Move it before the split below reads it. + if [ -d "$pkgdir"/usr/share/tcltk/tcl8.6 ]; then + mkdir -p "$pkgdir"/usr/lib + mv "$pkgdir"/usr/share/tcltk/tcl8.6 "$pkgdir"/usr/lib/tcl8.6 + rmdir --ignore-fail-on-non-empty "$pkgdir"/usr/share/tcltk + fi +""" +s = s.replace(anchor, fix + anchor, 1) +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +PY +grep -q "tclConfig.sh puts the tcl package tree" PKGBUILD || { + echo "sqlite: tcl relocation not inserted" >&2; exit 1; } +echo "sqlite: tcl extension relocated from share/tcltk to lib (host TCL_PACKAGE_PATH)"