From cb13d98b2b174195a52bcac16dfe4f42b18c4d87 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 19 Aug 2026 06:24:11 -0400 Subject: [PATCH] [FIX] four hooks the closure's second round needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit e2fsprogs builds fuse2fs, deletes it, and repackages it. Without fuse3 on the host it was never compiled, so the delete aborted package() after a clean build -- and its sub-package declares fuse3, which this port does not ship, so even a success would have been uninstallable. Three reasons, one direction. libsasl said `libpq-fe.h: No such file or directory` about a file that is there, in /usr/include/postgresql. mysql.h is in /usr/include/mariadb, the same shape one step behind it. Two headers that exist on paths configure does not try is a queue, and this port already wrote down what a queue means: a single missing library is a host dependency, a queue of them is a feature to disable. Arch declares depends=(glibc) for libsasl and says why -- the SQL plugins are dlopened, never linked. python-audit and python-capng go the way python-brotli went, but the reason is stated differently on purpose. Cost was the argument then; measured now, python costs three packages rather than a subtree, so that argument no longer holds. The ABI does: both are cp313 wheels and Arch ships 3.14. --- FR --- e2fsprogs bâtit fuse2fs, le supprime, puis le rempaquette. Sans fuse3 sur l'hôte il n'a jamais été compilé : la suppression interrompait donc package() après une compilation propre — et son sous-paquet déclare fuse3, que ce portage ne livre pas, si bien qu'une réussite aurait été ininstallable. Trois raisons, une seule direction. libsasl annonçait « libpq-fe.h: No such file or directory » à propos d'un fichier présent, sous /usr/include/postgresql. mysql.h est sous /usr/include/mariadb, même forme un cran derrière. Deux en-têtes qui existent sur des chemins que configure n'essaie pas font une file d'attente, et ce dépôt a déjà consigné ce qu'une file signifie : une bibliothèque manquante est une dépendance hôte, une file en est une fonctionnalité à désactiver. Arch déclare depends=(glibc) pour libsasl et explique pourquoi — les greffons SQL sont chargés dynamiquement, jamais liés. python-audit et python-capng suivent python-brotli, mais le motif est énoncé autrement à dessein. C'était le coût alors ; mesuré maintenant, python coûte trois paquets et non un sous-arbre, cet argument ne tient donc plus. L'ABI, si : les deux sont des roues cp313 quand Arch livre la 3.14. Assisted-by: Claude Opus 5 --- patches/pkgbuild/audit.sh | 44 ++++++++++++++++++++++++ patches/pkgbuild/e2fsprogs.sh | 45 ++++++++++++++++++++++++ patches/pkgbuild/libcap-ng.sh | 44 ++++++++++++++++++++++++ patches/pkgbuild/libsasl.sh | 64 +++++++++++++++++++++++++++++++++++ 4 files changed, 197 insertions(+) create mode 100755 patches/pkgbuild/audit.sh create mode 100755 patches/pkgbuild/e2fsprogs.sh create mode 100755 patches/pkgbuild/libcap-ng.sh create mode 100755 patches/pkgbuild/libsasl.sh diff --git a/patches/pkgbuild/audit.sh b/patches/pkgbuild/audit.sh new file mode 100755 index 0000000..a7831ee --- /dev/null +++ b/patches/pkgbuild/audit.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# audit: the python-audit sub-package cannot work, whatever it costs. +# +# The wheel is built by the HOST's python 3.13 and carries a cpython-313 ABI +# tag; Arch ships python 3.14. No destination makes it importable on the +# target -- the same reason python-brotli and python-libseccomp were dropped. +# Stage 2 builds it against the port's own python, where the tag matches. +# +# THE COST ARGUMENT IS NOT THE REASON, and that distinction is worth keeping. +# python-brotli was dropped when the repository held forty packages and +# `python` dragged libffi, mpdecimal and gdbm in behind it. Measured again at +# 131 packages, python costs three: the closure had filled in around it. So +# the cheap-versus-expensive reasoning that was right then is wrong now, and +# reusing it here would have been reasoning from a stale measurement. +# +# What has not changed is the ABI. That is why this one goes. +# +# build() still builds the wheel -- one function for every sub-package -- and +# the result is simply not packaged. package_python-audit() stays in the file, +# uncalled, like package_libquadmath() in gcc. +set -euo pipefail +python3 - <<'PY' +import io, re, sys +s = io.open("PKGBUILD", encoding="utf-8").read() +m = re.search(r"pkgname=\((.*?)\)\n", s, re.S) +assert m, "audit: pkgname array not found" +names = re.findall(r"[A-Za-z0-9._+-]+", m.group(1)) +assert "python-audit" in names, "audit: python-audit not in pkgname, found %s" % names +kept = [n for n in names if n != "python-audit"] +s = s[:m.start()] + "pkgname=(\n" + "".join(" %s\n" % n for n in kept) + ")\n" + s[m.end():] +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +PY +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("audit: pkgname array unreadable after patching") +names = re.findall(r"[A-Za-z0-9._+-]+", m.group(1)) +if "python-audit" in names: + sys.exit("audit: python-audit still declared in pkgname") +print("audit: pkgname -> %s" % " ".join(names)) +PY +echo "audit: python-audit dropped (cp313 wheel, Arch ships python 3.14)" diff --git a/patches/pkgbuild/e2fsprogs.sh b/patches/pkgbuild/e2fsprogs.sh new file mode 100755 index 0000000..50035d8 --- /dev/null +++ b/patches/pkgbuild/e2fsprogs.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# e2fsprogs: fuse2fs is built, deleted, and repackaged -- and none of that +# happens without fuse3 on the host. +# +# rm: cannot remove '/usr/bin/fuse2fs': No such file or directory +# +# package_e2fsprogs() removes fuse2fs so that package_fuse2fs() can ship it +# separately. The host has no fuse3, so misc/fuse2fs was never compiled, the +# path never existed, and the rm aborted package() AFTER a clean build -- the +# same shape as pam's PDF glob and util-linux's static libraries. +# +# THE PART WORTH CHECKING RATHER THAN PATCHING BLINDLY: making the rm tolerant +# is not enough on its own. fuse2fs is a SUB-PACKAGE, declared in pkgname, and +# package_fuse2fs() installs from misc/fuse2fs -- so it would fail next, on +# the same absent file. And its depends=('fuse3' 'e2fsprogs') names fuse3, +# which this port does not build, so even a successful package would be +# uninstallable. Three reasons pointing the same way. +# +# e2fsprogs is in stage 1 for libcom_err.so and libss.so, which krb5 needs +# after --with-system-ss was dropped. A FUSE mount helper is not part of that. +# +# Deferred, not architectural: build fuse3, add it to the closure, and both +# the sub-package and the rm come back on their own. TODO.md records it. +set -euo pipefail +python3 - <<'PY' +import io +s = io.open("PKGBUILD", encoding="utf-8").read() + +old = "pkgname=('e2fsprogs' 'fuse2fs')" +assert s.count(old) == 1, "e2fsprogs: pkgname line not in the expected form" +s = s.replace(old, "pkgname=('e2fsprogs')", 1) + +# The rm keeps its -f rather than being deleted: with fuse3 present the files +# WOULD exist and must still be removed from the main package. +old = ' rm "${pkgdir}"/usr/{bin/fuse2fs,share/man/man1/fuse2fs.1}\n' +assert s.count(old) == 1, "e2fsprogs: fuse2fs removal not in the expected form" +s = s.replace(old, ' rm -f "${pkgdir}"/usr/{bin/fuse2fs,share/man/man1/fuse2fs.1}\n', 1) + +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +PY +grep -q "^pkgname=('e2fsprogs')$" PKGBUILD || { + echo "e2fsprogs: fuse2fs not dropped from pkgname" >&2; exit 1; } +grep -qF 'rm -f "${pkgdir}"/usr/{bin/fuse2fs' PKGBUILD || { + echo "e2fsprogs: fuse2fs removal not made tolerant" >&2; exit 1; } +echo "e2fsprogs: fuse2fs dropped (no fuse3 on the host, none in the closure)" diff --git a/patches/pkgbuild/libcap-ng.sh b/patches/pkgbuild/libcap-ng.sh new file mode 100755 index 0000000..7f70522 --- /dev/null +++ b/patches/pkgbuild/libcap-ng.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# libcap-ng: the python-capng sub-package cannot work, whatever it costs. +# +# The wheel is built by the HOST's python 3.13 and carries a cpython-313 ABI +# tag; Arch ships python 3.14. No destination makes it importable on the +# target -- the same reason python-brotli and python-libseccomp were dropped. +# Stage 2 builds it against the port's own python, where the tag matches. +# +# THE COST ARGUMENT IS NOT THE REASON, and that distinction is worth keeping. +# python-brotli was dropped when the repository held forty packages and +# `python` dragged libffi, mpdecimal and gdbm in behind it. Measured again at +# 131 packages, python costs three: the closure had filled in around it. So +# the cheap-versus-expensive reasoning that was right then is wrong now, and +# reusing it here would have been reasoning from a stale measurement. +# +# What has not changed is the ABI. That is why this one goes. +# +# build() still builds the wheel -- one function for every sub-package -- and +# the result is simply not packaged. package_python-capng() stays in the file, +# uncalled, like package_libquadmath() in gcc. +set -euo pipefail +python3 - <<'PY' +import io, re, sys +s = io.open("PKGBUILD", encoding="utf-8").read() +m = re.search(r"pkgname=\((.*?)\)\n", s, re.S) +assert m, "libcap-ng: pkgname array not found" +names = re.findall(r"[A-Za-z0-9._+-]+", m.group(1)) +assert "python-capng" in names, "libcap-ng: python-capng not in pkgname, found %s" % names +kept = [n for n in names if n != "python-capng"] +s = s[:m.start()] + "pkgname=(\n" + "".join(" %s\n" % n for n in kept) + ")\n" + s[m.end():] +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +PY +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("libcap-ng: pkgname array unreadable after patching") +names = re.findall(r"[A-Za-z0-9._+-]+", m.group(1)) +if "python-capng" in names: + sys.exit("libcap-ng: python-capng still declared in pkgname") +print("libcap-ng: pkgname -> %s" % " ".join(names)) +PY +echo "libcap-ng: python-capng dropped (cp313 wheel, Arch ships python 3.14)" diff --git a/patches/pkgbuild/libsasl.sh b/patches/pkgbuild/libsasl.sh new file mode 100755 index 0000000..319d8d4 --- /dev/null +++ b/patches/pkgbuild/libsasl.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# libsasl: --enable-sql, and the message names a file that exists. +# +# sql.c:192:10: fatal error: libpq-fe.h: No such file or directory +# +# THE TRAP: the header is there. +# +# /usr/include/postgresql/libpq-fe.h +# +# Debian and Ubuntu put the PostgreSQL client headers in a subdirectory, and +# the PKGBUILD passes --with-pgsql=/usr/lib, which sends configure looking +# somewhere else. "No such file or directory" is true of the path it tried and +# false of the file, so the obvious move -- install libpq-dev -- changes +# nothing, because it is already installed. +# +# AND IT IS A QUEUE, WHICH IS THE REAL POINT. The same PKGBUILD passes +# --with-mysql=/usr, and mysql.h is at /usr/include/mariadb/mysql.h -- another +# subdirectory, another header that exists and cannot be found. Fixing the +# first uncovers the second. This port already wrote the rule down, in +# install_host_deps, after dbus asked for three documentation tools in a row: +# a single missing library is a host dependency; a queue of them is a feature +# that should be disabled. +# +# So the SQL auxprop plugin goes, along with the three --with-*sql flags that +# feed it. What it costs is worth stating precisely, because it is nothing: +# Arch itself declares depends=(glibc) for this package and says why in a +# comment at the top of its own PKGBUILD -- "else this would require mariadb +# and postgresql in [core]". The plugins are dlopened, never linked, so +# dropping them removes no dependency Arch was tracking and no capability +# anything in this port uses. libsasl is here because libldap wants SASL +# authentication; that is the DIGEST/GSSAPI/PLAIN side, all still enabled. +# +# Deferred, not architectural: a server that wants SQL auxprop rebuilds this +# with the include paths supplied. TODO.md records it. +set -euo pipefail +python3 - <<'PY' +import io, re +s = io.open("PKGBUILD", encoding="utf-8").read() + +old = " --enable-sql\n" +assert s.count(old) == 1, "libsasl: expected exactly one --enable-sql" +s = s.replace(old, " --disable-sql\n", 1) + +# The three database locations exist only to serve the plugin just disabled. +n = 0 +for flag in ("--with-mysql=", "--with-pgsql=", "--with-sqlite3="): + pat = re.compile(r"^\s*" + re.escape(flag) + r".*\n", re.M) + found = pat.findall(s) + assert len(found) == 1, "libsasl: expected one %s line, found %d" % (flag, len(found)) + s = pat.sub("", s, count=1) + n += 1 +assert n == 3 +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +PY +grep -q -- '--disable-sql' PKGBUILD || { + echo "libsasl: sql plugin still enabled" >&2; exit 1; } +for f in -- '--with-mysql' '--with-pgsql' '--with-sqlite3'; do + [ "$f" = "--" ] && continue + grep -qF -- "$f" PKGBUILD && { + echo "libsasl: $f survived" >&2; exit 1; } +done +grep -q -- '--enable-gssapi' PKGBUILD || { + echo "libsasl: gssapi lost -- libldap needs SASL auth" >&2; exit 1; } +echo "libsasl: SQL auxprop disabled (two headers that exist, on paths it does not try)"