[REM] sub-packages: four that cost more closure than they carry
pacman's resolver named 70 unresolved dependencies. Excluding python-brotli alone took that to 47 and removed `python` outright, with libffi, mpdecimal and gdbm behind it. python-libseccomp would have re-admitted the same tree, and nothing across 135 packages depends on either. Both wheels are unusable on the target anyway: built by the host's python 3.13, ABI-tagged cpython-313, while Arch ships 3.14. Stage 2 produces real ones. systemd-ukify is different -- it is architectural. ukify assembles a PE binary and its EFI_ARCH_MAP has no s390x entry, so guess_efi_arch() raises ValueError on this machine. Shipping it would put a program in the repository that cannot start. systemd-tests followed it out: a test suite behind --nocheck, and the only reason five more python packages were wanted. The dropped artefacts were still indexed in core.db afterwards. repo-add only adds, so a sub-package removed from pkgname leaves its last build installable forever. Removed by hand; TODO.md records the gap. --- FR --- Le résolveur de pacman nommait 70 dépendances non résolues. Écarter le seul python-brotli a ramené le compte à 47 et retiré `python` d'un coup, avec libffi, mpdecimal et gdbm derrière. python-libseccomp aurait réadmis le même arbre, et rien parmi 135 paquets ne dépend de l'un ou de l'autre. Les deux roues sont de toute façon inutilisables sur la cible : bâties par le python 3.13 de l'hôte, marquées cpython-313, quand Arch livre la 3.14. L'étage 2 en produira de vraies. systemd-ukify est d'une autre nature — architectural. ukify assemble un binaire PE et son EFI_ARCH_MAP n'a pas d'entrée s390x : guess_efi_arch() lève donc ValueError sur cette machine. Le livrer mettrait au dépôt un programme incapable de démarrer. systemd-tests l'a suivi : une suite de tests derrière --nocheck, et la seule raison de réclamer cinq paquets python de plus. Les artefacts écartés restaient indexés dans core.db. repo-add n'ajoute que : un sous-paquet retiré de pkgname laisse sa dernière compilation installable à jamais. Retirés à la main ; TODO.md consigne le manque. Assisted-by: Claude Opus 5
This commit is contained in:
parent
549b5650f5
commit
2a1997e06d
3 changed files with 191 additions and 52 deletions
|
|
@ -1,10 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
# brotli: two Debian defaults, and both of them ship silently.
|
||||
# brotli: a Debian default that ships silently, and a sub-package that costs
|
||||
# more than it is worth.
|
||||
#
|
||||
# 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.
|
||||
# The first produces no error. makepkg exits 0 and the repository gains a
|
||||
# package whose library sits where nothing on the target will look -- the case
|
||||
# "an exit code proves nothing, only the artefact proves" was written for,
|
||||
# where the symptom arrives months later, far from the cause.
|
||||
#
|
||||
# The second is a closure decision, not a defect, and it is measured rather
|
||||
# than argued.
|
||||
set -euo pipefail
|
||||
|
||||
# 1. The C library, and the one that actually matters.
|
||||
|
|
@ -34,52 +38,40 @@ 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.
|
||||
# 2. python-brotli: dropped, because it costs the entire python closure.
|
||||
#
|
||||
# package_python-brotli() runs
|
||||
# THE MEASUREMENT THAT DECIDED IT. pacman resolver named 70 missing
|
||||
# dependencies. Excluding this one sub-package from the ledger took that to
|
||||
# 47 and removed `python` outright -- with libffi, mpdecimal and gdbm behind
|
||||
# it. Three packages of closure, for one module.
|
||||
#
|
||||
# python -m installer --destdir="$pkgdir" dist/*.whl
|
||||
# And the module could not work anyway. The wheel is built by the host python
|
||||
# 3.13 and is ABI-tagged cpython-313; Arch ships 3.14. No destination makes it
|
||||
# importable on the target, so stage 2 is what has to produce a real one.
|
||||
# TODO.md carries that entry already.
|
||||
#
|
||||
# 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.
|
||||
# Note what is NOT the reason. The wheel also lands in
|
||||
# /usr/local/lib/python3.13/dist-packages, because Debian patches sysconfig to
|
||||
# prefer its posix_local scheme, and an Arch package may not ship /usr/local.
|
||||
# That was worth a relocation while the sub-package existed; with the
|
||||
# sub-package gone there is nothing to relocate, and keeping the relocation
|
||||
# would be editing a package() function makepkg never calls.
|
||||
#
|
||||
# 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.
|
||||
# build() still builds the wheel -- it is one function for all three
|
||||
# sub-packages -- and the result is simply not packaged. Cheap, and it keeps
|
||||
# this hook to one line of real change.
|
||||
#
|
||||
# `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'
|
||||
# brotli-testdata stays: nothing depends on it, it costs no closure, and
|
||||
# removing it would be churn.
|
||||
python3 - <<'PYB'
|
||||
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)
|
||||
old = "pkgname=('brotli' 'python-brotli' 'brotli-testdata')"
|
||||
assert s.count(old) == 1, "brotli: expected exactly one pkgname line"
|
||||
s = s.replace(old, "pkgname=('brotli' 'brotli-testdata')", 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"
|
||||
PYB
|
||||
grep -q "^pkgname=('brotli' 'brotli-testdata')$" PKGBUILD || {
|
||||
echo "brotli: python-brotli not removed from pkgname" >&2; exit 1; }
|
||||
grep -q "python-brotli" PKGBUILD && echo "brotli: python-brotli remains only in its own package function (never called)"
|
||||
echo "brotli: python-brotli dropped (pulled the whole python closure)"
|
||||
|
|
|
|||
36
patches/pkgbuild/libseccomp.sh
Executable file
36
patches/pkgbuild/libseccomp.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env bash
|
||||
# libseccomp: the python sub-package ships into /usr/local.
|
||||
#
|
||||
# usr/local/lib/python3.13/dist-packages/...
|
||||
#
|
||||
# Debian patches sysconfig to prefer its posix_local scheme, so
|
||||
# `python -m installer` puts the wheel under /usr/local -- a tree an Arch
|
||||
# package may not ship at all. Nothing fails: makepkg exits 0 and the audit
|
||||
# is what notices.
|
||||
#
|
||||
# THE SAME DECISION AS python-brotli, for the same measured reason. Nothing in
|
||||
# the repository depends on python-libseccomp -- checked across all 135
|
||||
# packages -- and keeping it would pull `python` back into the closure, with
|
||||
# libffi, mpdecimal and gdbm behind it. That closure was deliberately removed
|
||||
# when python-brotli was dropped; re-admitting it for a binding no package
|
||||
# asks for would undo the measurement.
|
||||
#
|
||||
# And the module would not work anyway: the wheel is built by the host's
|
||||
# python 3.13 and ABI-tagged cpython-313, while Arch ships 3.14. Stage 2 is
|
||||
# what produces a usable one.
|
||||
#
|
||||
# build() still builds the wheel -- it is one function for both sub-packages
|
||||
# -- and the result is simply not packaged. package_python-libseccomp() stays
|
||||
# in the file, uncalled, like package_libquadmath() in gcc.
|
||||
set -euo pipefail
|
||||
python3 - <<'PY'
|
||||
import io
|
||||
s = io.open("PKGBUILD", encoding="utf-8").read()
|
||||
old = "pkgname=(libseccomp python-libseccomp)"
|
||||
assert s.count(old) == 1, "libseccomp: pkgname line not in the expected form"
|
||||
s = s.replace(old, "pkgname=(libseccomp)", 1)
|
||||
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
|
||||
PY
|
||||
grep -q '^pkgname=(libseccomp)$' PKGBUILD || {
|
||||
echo "libseccomp: python sub-package not dropped" >&2; exit 1; }
|
||||
echo "libseccomp: python-libseccomp dropped (would re-admit the python closure)"
|
||||
|
|
@ -1,11 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
# systemd: two build options Arch can hold and s390x cannot.
|
||||
# systemd: four defaults 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.
|
||||
# None of them is a missing host package. Two are hard errors raised by meson
|
||||
# because the PKGBUILD ASKS for something this architecture does not have; two
|
||||
# are defaults computed from the BUILD MACHINE rather than the target. No
|
||||
# amount of apt-get fixes any of them. The libraries systemd wants (libbpf,
|
||||
# clang, libfdisk, libkmod, ...) ARE host packages and belong in
|
||||
# install_host_deps, not here.
|
||||
#
|
||||
# Three of the four fail with a message that names something else entirely.
|
||||
# Each section says which, because that is the part worth reading twice.
|
||||
set -euo pipefail
|
||||
|
||||
# 1. -Dbootloader=enabled: systemd-boot is EFI, and s390x has no EFI.
|
||||
|
|
@ -126,3 +130,110 @@ sed -i 's/^\( *\)-Dcompat-sysv-interfaces=false/\1-Dsplit-bin=false\n\1-Dcompat-
|
|||
grep -q -- '-Dsplit-bin=false' PKGBUILD || {
|
||||
echo "systemd: split-bin still auto (would install into /usr/sbin)" >&2; exit 1; }
|
||||
echo "systemd: split-bin=false (Ubuntu's /usr/sbin is not Arch's)"
|
||||
|
||||
# 4. -Dukify=auto: the tool cannot run on this architecture at all.
|
||||
#
|
||||
# ukify assembles a Unified Kernel Image, and a UKI is a PE binary -- ukify.py
|
||||
# imports pefile at module level and drives it directly (pefile.PE,
|
||||
# SectionStructure, IMAGE_SCN_*). It is EFI tooling, so the same reasoning as
|
||||
# section 1 applies. But it is stronger than "pointless on Z", and the source
|
||||
# says so out loud:
|
||||
#
|
||||
# EFI_ARCH_MAP = {
|
||||
# 'x86_64': ['x64','ia32'], 'i[3456]86': ['ia32'], 'aarch64': ['aa64'],
|
||||
# 'armv[45678]*l': ['arm'], 'loongarch32': ..., 'riscv64': ...
|
||||
# }
|
||||
#
|
||||
# def guess_efi_arch() -> str:
|
||||
# ...
|
||||
# else:
|
||||
# raise ValueError(f'Unsupported architecture {arch}')
|
||||
#
|
||||
# s390x is not in that table, so ukify RAISES on this machine. Shipping it
|
||||
# would put a program in the repository that cannot start.
|
||||
#
|
||||
# THE TRAP is that ukify does not announce itself as EFI-only anywhere the
|
||||
# build stops. meson_options.txt:560 declares it a plain feature with no
|
||||
# value -- 'auto' -- and meson.build:1659 is get_option('ukify').allowed(),
|
||||
# which is true for 'auto'. So it is on by default, and what it broke was
|
||||
# nowhere near ukify:
|
||||
#
|
||||
# FAILED: src/boot/test-hwids-section.c
|
||||
# ModuleNotFoundError: No module named 'pefile'
|
||||
#
|
||||
# That looked like a missing host package, and `apt install python3-pefile`
|
||||
# made it build. It was the wrong fix: the target at src/boot/meson.build:31
|
||||
# is gated on ENABLE_UKIFY, not on ENABLE_BOOTLOADER, which is why disabling
|
||||
# systemd-boot did not reach it. Disabling ukify does, and python3-pefile is
|
||||
# then unnecessary -- nothing else in the tree needs it, since the only other
|
||||
# importer, tools/check-efi-alignment.py, is reached from
|
||||
# src/boot/meson.build:495, far below the ENABLE_BOOTLOADER subdir_done()
|
||||
# guard.
|
||||
#
|
||||
# systemd-tests goes with it, for a different reason. It is a test suite --
|
||||
# stage 1 runs --nocheck -- and it is the sole reason five python packages
|
||||
# (colorama, packaging, pexpect, psutil, pytest) would enter the closure.
|
||||
# Deferred, not architectural: TODO.md records it.
|
||||
#
|
||||
# The two subpackages leave in three places, and the pkgname array is the
|
||||
# awkward one: its LAST entry carries the closing paren, so deleting a line
|
||||
# would delete the ')' with it. The array is rebuilt rather than edited.
|
||||
python3 - <<'PY'
|
||||
import io, re
|
||||
s = io.open("PKGBUILD", encoding="utf-8").read()
|
||||
|
||||
# (a) the option, next to the other EFI one so they read together
|
||||
anchor = " -Dbootloader=disabled\n"
|
||||
assert s.count(anchor) == 1, "expected section 1 to have set bootloader=disabled"
|
||||
s = s.replace(anchor, anchor + " -Dukify=disabled\n", 1)
|
||||
|
||||
# (b) the pkgname array, rebuilt to keep its syntax intact
|
||||
m = re.search(r"pkgname=\((.*?)\)\n", s, re.S)
|
||||
assert m, "pkgname array not found"
|
||||
names = re.findall(r"'([^']+)'", m.group(1))
|
||||
drop = {"systemd-ukify", "systemd-tests"}
|
||||
assert drop <= set(names), "expected both subpackages in pkgname, found %s" % names
|
||||
kept = [n for n in names if n not in drop]
|
||||
s = s[:m.start()] + "pkgname=(" + ("\n" + " " * 9).join("'%s'" % n for n in kept) + ")\n" + s[m.end():]
|
||||
|
||||
# (c) package_systemd() moves four ukify paths out. With ukify disabled none
|
||||
# of them exists, and mv fails -- after a successful compile, which is the
|
||||
# expensive way to find out.
|
||||
blk = re.search(
|
||||
r"\n # ukify shipped in separate package\n"
|
||||
r"(?:.*\n)*?"
|
||||
r" mv \"\$pkgdir\"/usr/lib/kernel/install\.d/60-ukify\.install systemd-ukify/install\.d\n",
|
||||
s)
|
||||
assert blk, "ukify mv block not found"
|
||||
s = s[:blk.start()] + "\n" + s[blk.end():]
|
||||
|
||||
# (d) an optdepends on a package we no longer produce
|
||||
s = re.sub(r"^.*'systemd-ukify: .*\n", "", s, flags=re.M)
|
||||
|
||||
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
|
||||
PY
|
||||
# The guard checks what MATTERS, which is not "no mention of ukify remains".
|
||||
# package_systemd-ukify() still sits in the file and still names four paths --
|
||||
# harmless, because makepkg never calls a function whose name is absent from
|
||||
# pkgname, exactly as with package_libquadmath() in gcc.sh. Counting mentions
|
||||
# instead of checking the two real conditions made this hook exit 1 on a
|
||||
# correctly patched PKGBUILD, and build_package reads that as a failed
|
||||
# package: systemd would have been skipped entirely, with every edit applied.
|
||||
grep -q -- '-Dukify=disabled' PKGBUILD || {
|
||||
echo "systemd: ukify still enabled" >&2; exit 1; }
|
||||
grep -q '# ukify shipped in separate package' PKGBUILD && {
|
||||
echo "systemd: package_systemd() still moves ukify paths that cannot exist" >&2
|
||||
exit 1; }
|
||||
python3 - <<'PYGUARD'
|
||||
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("systemd: pkgname array unreadable after patching")
|
||||
names = re.findall(r"'([^']+)'", m.group(1))
|
||||
left = sorted({"systemd-ukify", "systemd-tests"} & set(names))
|
||||
if left:
|
||||
sys.exit("systemd: still declared in pkgname: %s" % left)
|
||||
print("systemd: pkgname -> %s" % " ".join(names))
|
||||
PYGUARD
|
||||
echo "systemd: ukify disabled (guess_efi_arch raises on s390x), tests dropped"
|
||||
|
|
|
|||
Loading…
Reference in a new issue