[FIX] four hooks the closure's second round needed

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
This commit is contained in:
Mathieu Benoit 2026-08-19 06:24:11 -04:00
parent 1ef18efeec
commit cb13d98b2b
4 changed files with 197 additions and 0 deletions

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

@ -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)"

45
patches/pkgbuild/e2fsprogs.sh Executable file
View file

@ -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 '<pkgdir>/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)"

44
patches/pkgbuild/libcap-ng.sh Executable file
View file

@ -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)"

64
patches/pkgbuild/libsasl.sh Executable file
View file

@ -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)"