[FIX] python packages: /usr/local, once, for all of them
Debian patches sysconfig to prefer the "posix_local" scheme, so every
wheel-installing PKGBUILD ships /usr/local -- a tree Arch reserves and whose
every path the filesystem package owns. pacman refuses the transaction:
error: failed to commit transaction (conflicting files)
/usr/local/share/man exists in both 'filesystem' and 'meson'
Four packages hit it. Two were dropped for other reasons. Rather than write a
third and fourth relocation hook, build_package now exports
DEB_PYTHON_INSTALL_LAYOUT=deb_system, which moves the default from
/usr/local/lib/python3.13/dist-packages to /usr/lib/python3/dist-packages and
scripts from /usr/local/bin to /usr/bin. One line, every python package,
including ones not built yet.
It does not finish the job: deb_system still says dist-packages and Arch reads
site-packages. That move stays per-package, because only modules actually
imported on the target need it. meson does, and its hook does it -- targeting
the version of the python PACKAGE in repo/s390x, not the interpreter running
the build. Installed under the host's 3.13, /usr/bin/meson runs in the chroot
and then says ModuleNotFoundError, because the chroot's python is our 3.14 and
never looks in 3.13.
--- FR ---
Debian modifie sysconfig pour préférer le schéma « posix_local » : tout
PKGBUILD installant une roue livre donc /usr/local, arbre qu'Arch réserve et
dont le paquet filesystem possède chaque chemin. pacman refuse la transaction :
error: failed to commit transaction (conflicting files)
/usr/local/share/man exists in both 'filesystem' et 'meson'
Quatre paquets l'ont rencontré. Deux ont été écartés pour d'autres raisons.
Plutôt que d'écrire un troisième et un quatrième crochet de relocalisation,
build_package exporte désormais DEB_PYTHON_INSTALL_LAYOUT=deb_system, qui
déplace le défaut de /usr/local/lib/python3.13/dist-packages vers
/usr/lib/python3/dist-packages, et les scripts de /usr/local/bin vers /usr/bin.
Une ligne, tous les paquets python, y compris ceux pas encore bâtis.
Cela ne termine pas le travail : deb_system dit encore dist-packages quand
Arch lit site-packages. Ce déplacement reste par paquet, car seuls les modules
réellement importés sur la cible en ont besoin. meson en fait partie, et son
crochet le fait — en visant la version du PAQUET python de repo/s390x, non
l'interpréteur qui exécute la compilation. Installé sous le 3.13 de l'hôte,
/usr/bin/meson démarre dans le chroot puis annonce ModuleNotFoundError, le
python du chroot étant notre 3.14, qui ne regarde jamais dans 3.13.
Assisted-by: Claude Opus 5
2026-08-19 20:19:37 -04:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# meson: `python -m installer` puts it under /usr/local.
|
|
|
|
|
#
|
|
|
|
|
# error: failed to commit transaction (conflicting files)
|
|
|
|
|
# /usr/local/share/man exists in both 'filesystem' and 'meson'
|
|
|
|
|
#
|
|
|
|
|
# Debian and Ubuntu patch sysconfig to prefer the "posix_local" scheme, so the
|
|
|
|
|
# host's python installs a wheel into /usr/local/lib/pythonX.Y/dist-packages
|
|
|
|
|
# with its scripts in /usr/local/bin. An Arch package may not ship /usr/local
|
|
|
|
|
# at all -- the filesystem package owns that tree, and ships
|
|
|
|
|
# usr/local/share/man as a SYMLINK, which is what the conflict is about: our
|
|
|
|
|
# meson shipped a directory where filesystem ships a link.
|
|
|
|
|
#
|
|
|
|
|
# THIRD INSTANCE of this scheme. python-brotli and python-libseccomp were
|
|
|
|
|
# simply dropped, because their wheels could not work on the target anyway.
|
|
|
|
|
# meson cannot be dropped -- stage 2 needs it to rebuild ten packages -- so it
|
|
|
|
|
# gets relocated instead.
|
|
|
|
|
#
|
|
|
|
|
# STAGE-1 ONLY, and listed in build-stage2.sh's STAGE2_SKIP_HOOKS. Inside the
|
|
|
|
|
# chroot the python doing the installing is OUR python, built from Arch's
|
|
|
|
|
# PKGBUILD, whose sysconfig answers /usr/lib/pythonX.Y/site-packages without
|
|
|
|
|
# being asked. The condition disappears, like git's missing zlib-ng headers and
|
|
|
|
|
# libgcrypt's too-old libgpg-error.
|
|
|
|
|
#
|
|
|
|
|
# AND THE VERSION IS THE TARGET'S, NOT THE HOST'S. This is the part that took
|
|
|
|
|
# a failed stage-2 build to see. meson is pure Python, so its ABI does not
|
|
|
|
|
# matter -- but the directory NAME carries a version, and the two pythons
|
|
|
|
|
# disagree:
|
|
|
|
|
#
|
|
|
|
|
# host python 3.13 -> /usr/lib/python3.13/site-packages
|
|
|
|
|
# our python pkg 3.14 -> /usr/lib/python3.14/site-packages
|
|
|
|
|
#
|
|
|
|
|
# Installed under the host's version, /usr/bin/meson runs in the chroot and
|
|
|
|
|
# then says
|
|
|
|
|
#
|
|
|
|
|
# ModuleNotFoundError: No module named 'mesonbuild'
|
|
|
|
|
#
|
|
|
|
|
# because the chroot's python -- ours -- never looks in 3.13. So the hook reads
|
|
|
|
|
# the version out of the python PACKAGE in repo/s390x, which is the python that
|
|
|
|
|
# will actually import this. Deriving it from the interpreter running the build
|
|
|
|
|
# is the intuitive thing and the wrong one.
|
|
|
|
|
set -euo pipefail
|
[FIX] a hook's scope belongs to its sections, not to its filename
git and meson kept failing in stage 2 on exactly what their own hooks fix, while
the log said
hook skipped (stage-1 only)
Both were named in STAGE2_SKIP_HOOKS, correctly, when their whole content was a
host workaround: git dropped ZLIB_NG because the host had no zlib-ng headers,
meson moved a wheel out of /usr/local. Then each grew a second section that BOTH
stages need -- git's asciidoc man pages, meson's hotdoc reference manual -- and
the list skips the whole FILE, so the new sections never ran. Two hooks that were
by then two thirds relevant, silently ignored.
A list of filenames cannot say why a hook is listed, and cannot notice when that
reason stops covering the file. The hooks now read EL_STAGE and decide per
section, with the reason written beside each guard; the list is gone. libgcrypt
and libarchive keep a whole-file guard, which now states its reason instead of
being an entry somewhere else.
Verified at both stages: git drops its man pages in each, and keeps ZLIB_NG in
the chroot where zlib-ng exists.
--- FR ---
git et meson échouaient à l'étage 2 sur précisément ce que leurs propres hooks
corrigent, pendant que le journal disait
hook skipped (stage-1 only)
Tous deux étaient nommés dans STAGE2_SKIP_HOOKS, à juste titre quand tout leur
contenu était un contournement de l'hôte : git abandonnait ZLIB_NG faute
d'en-têtes zlib-ng, meson déplaçait une roue hors de /usr/local. Puis chacun a
gagné une seconde section utile aux DEUX étages — les pages asciidoc de git, le
manuel hotdoc de meson — et la liste saute le FICHIER entier : ces sections n'ont
jamais tourné. Deux hooks devenus pertinents aux deux tiers, ignorés en silence.
Une liste de noms de fichiers ne peut pas dire pourquoi un hook y figure, ni
remarquer que cette raison a cessé de couvrir le fichier. Les hooks lisent
désormais EL_STAGE et tranchent par section, la raison écrite à côté de chaque
garde ; la liste disparaît. libgcrypt et libarchive gardent une garde de fichier
entier, qui énonce sa raison au lieu d'être une entrée ailleurs.
Vérifié aux deux étages : git abandonne ses pages de manuel dans les deux, et
conserve ZLIB_NG dans le chroot, où zlib-ng existe.
Assisted-by: Claude Opus 5
2026-08-23 17:23:30 -04:00
|
|
|
# STAGE 1 ONLY -- this section, not this file.
|
|
|
|
|
#
|
|
|
|
|
# It used to be expressed by naming this hook in STAGE2_SKIP_HOOKS, which skips
|
|
|
|
|
# the whole FILE. Then a section was appended that both stages need, and it
|
|
|
|
|
# silently never ran: the package kept failing in stage 2 on the very thing the
|
|
|
|
|
# new section fixes, and the log said "hook skipped (stage-1 only)" about a hook
|
|
|
|
|
# that was now two thirds relevant.
|
|
|
|
|
#
|
|
|
|
|
# The scope belongs next to the reason, so each section decides for itself.
|
|
|
|
|
if [ "${EL_STAGE:-1}" = 1 ]; then
|
|
|
|
|
|
[FIX] python packages: /usr/local, once, for all of them
Debian patches sysconfig to prefer the "posix_local" scheme, so every
wheel-installing PKGBUILD ships /usr/local -- a tree Arch reserves and whose
every path the filesystem package owns. pacman refuses the transaction:
error: failed to commit transaction (conflicting files)
/usr/local/share/man exists in both 'filesystem' and 'meson'
Four packages hit it. Two were dropped for other reasons. Rather than write a
third and fourth relocation hook, build_package now exports
DEB_PYTHON_INSTALL_LAYOUT=deb_system, which moves the default from
/usr/local/lib/python3.13/dist-packages to /usr/lib/python3/dist-packages and
scripts from /usr/local/bin to /usr/bin. One line, every python package,
including ones not built yet.
It does not finish the job: deb_system still says dist-packages and Arch reads
site-packages. That move stays per-package, because only modules actually
imported on the target need it. meson does, and its hook does it -- targeting
the version of the python PACKAGE in repo/s390x, not the interpreter running
the build. Installed under the host's 3.13, /usr/bin/meson runs in the chroot
and then says ModuleNotFoundError, because the chroot's python is our 3.14 and
never looks in 3.13.
--- FR ---
Debian modifie sysconfig pour préférer le schéma « posix_local » : tout
PKGBUILD installant une roue livre donc /usr/local, arbre qu'Arch réserve et
dont le paquet filesystem possède chaque chemin. pacman refuse la transaction :
error: failed to commit transaction (conflicting files)
/usr/local/share/man exists in both 'filesystem' et 'meson'
Quatre paquets l'ont rencontré. Deux ont été écartés pour d'autres raisons.
Plutôt que d'écrire un troisième et un quatrième crochet de relocalisation,
build_package exporte désormais DEB_PYTHON_INSTALL_LAYOUT=deb_system, qui
déplace le défaut de /usr/local/lib/python3.13/dist-packages vers
/usr/lib/python3/dist-packages, et les scripts de /usr/local/bin vers /usr/bin.
Une ligne, tous les paquets python, y compris ceux pas encore bâtis.
Cela ne termine pas le travail : deb_system dit encore dist-packages quand
Arch lit site-packages. Ce déplacement reste par paquet, car seuls les modules
réellement importés sur la cible en ont besoin. meson en fait partie, et son
crochet le fait — en visant la version du PAQUET python de repo/s390x, non
l'interpréteur qui exécute la compilation. Installé sous le 3.13 de l'hôte,
/usr/bin/meson démarre dans le chroot puis annonce ModuleNotFoundError, le
python du chroot étant notre 3.14, qui ne regarde jamais dans 3.13.
Assisted-by: Claude Opus 5
2026-08-19 20:19:37 -04:00
|
|
|
python3 - <<'ZZPY'
|
|
|
|
|
import io
|
|
|
|
|
s = io.open("PKGBUILD", encoding="utf-8").read()
|
|
|
|
|
old = ' python -m installer --destdir="$pkgdir" dist/*.whl\n'
|
|
|
|
|
assert s.count(old) == 1, "meson: installer invocation not in the expected form"
|
|
|
|
|
new = old + """ # Debian's posix_local scheme installs to /usr/local; Arch reserves that
|
|
|
|
|
# tree for the administrator and its filesystem package owns every path in
|
|
|
|
|
# it. Move what the wheel put there, then take the tree away.
|
|
|
|
|
local _repo="${REPO:-$HOME/work/arch-s390x/repo/s390x}"
|
|
|
|
|
# Two layouts to catch. build_package exports
|
|
|
|
|
# DEB_PYTHON_INSTALL_LAYOUT=deb_system, which keeps the wheel out of
|
|
|
|
|
# /usr/local -- but leaves it in dist-packages, which Arch's python does not
|
|
|
|
|
# read. The /usr/local branch stays for anyone running makepkg by hand
|
|
|
|
|
# without that variable.
|
|
|
|
|
# The TARGET python's version, read from our own package -- see the note at
|
|
|
|
|
# the top of this hook. Falls back to the host's only if the package is not
|
|
|
|
|
# built yet, which would be a stage-1 ordering error worth noticing.
|
|
|
|
|
local _pyver
|
|
|
|
|
_pyver=$(bsdtar -xOf "$_repo"/python-[0-9]*.pkg.tar.* .PKGINFO 2>/dev/null \
|
|
|
|
|
| sed -n 's/^pkgver = \\([0-9]*\\.[0-9]*\\).*/\\1/p' | head -1)
|
|
|
|
|
if [ -z "$_pyver" ]; then
|
|
|
|
|
_pyver=$(python -c 'import sys; print("%d.%d" % sys.version_info[:2])')
|
|
|
|
|
echo " warning: no python package in $_repo, using host python $_pyver" >&2
|
|
|
|
|
fi
|
|
|
|
|
local _site="$pkgdir/usr/lib/python$_pyver/site-packages"
|
|
|
|
|
if [ -d "$pkgdir/usr/local" ]; then
|
|
|
|
|
install -dm 755 "$pkgdir/usr/lib/python$_pyver" "$pkgdir/usr/bin"
|
|
|
|
|
[ -d "$pkgdir/usr/local/lib/python$_pyver/dist-packages" ] && \\
|
|
|
|
|
mv "$pkgdir/usr/local/lib/python$_pyver/dist-packages" "$_site"
|
|
|
|
|
[ -d "$pkgdir/usr/local/bin" ] && mv "$pkgdir/usr/local/bin/"* "$pkgdir/usr/bin/"
|
|
|
|
|
rm -rf "$pkgdir/usr/local"
|
|
|
|
|
fi
|
|
|
|
|
if [ -d "$pkgdir/usr/lib/python3/dist-packages" ]; then
|
|
|
|
|
install -dm 755 "$pkgdir/usr/lib/python$_pyver"
|
|
|
|
|
if [ -d "$_site" ]; then
|
|
|
|
|
cp -a "$pkgdir/usr/lib/python3/dist-packages/." "$_site/"
|
|
|
|
|
rm -rf "$pkgdir/usr/lib/python3/dist-packages"
|
|
|
|
|
else
|
|
|
|
|
mv "$pkgdir/usr/lib/python3/dist-packages" "$_site"
|
|
|
|
|
fi
|
|
|
|
|
rmdir --ignore-fail-on-non-empty "$pkgdir/usr/lib/python3" 2>/dev/null || true
|
|
|
|
|
fi
|
|
|
|
|
"""
|
|
|
|
|
s = s.replace(old, new, 1)
|
|
|
|
|
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
|
|
|
|
|
ZZPY
|
|
|
|
|
grep -q 'posix_local scheme installs' PKGBUILD || {
|
|
|
|
|
echo "meson: the relocation was not inserted" >&2; exit 1; }
|
|
|
|
|
echo "meson: wheel relocated out of /usr/local; STAGE-1 ONLY"
|
[FIX] a hook's scope belongs to its sections, not to its filename
git and meson kept failing in stage 2 on exactly what their own hooks fix, while
the log said
hook skipped (stage-1 only)
Both were named in STAGE2_SKIP_HOOKS, correctly, when their whole content was a
host workaround: git dropped ZLIB_NG because the host had no zlib-ng headers,
meson moved a wheel out of /usr/local. Then each grew a second section that BOTH
stages need -- git's asciidoc man pages, meson's hotdoc reference manual -- and
the list skips the whole FILE, so the new sections never ran. Two hooks that were
by then two thirds relevant, silently ignored.
A list of filenames cannot say why a hook is listed, and cannot notice when that
reason stops covering the file. The hooks now read EL_STAGE and decide per
section, with the reason written beside each guard; the list is gone. libgcrypt
and libarchive keep a whole-file guard, which now states its reason instead of
being an entry somewhere else.
Verified at both stages: git drops its man pages in each, and keeps ZLIB_NG in
the chroot where zlib-ng exists.
--- FR ---
git et meson échouaient à l'étage 2 sur précisément ce que leurs propres hooks
corrigent, pendant que le journal disait
hook skipped (stage-1 only)
Tous deux étaient nommés dans STAGE2_SKIP_HOOKS, à juste titre quand tout leur
contenu était un contournement de l'hôte : git abandonnait ZLIB_NG faute
d'en-têtes zlib-ng, meson déplaçait une roue hors de /usr/local. Puis chacun a
gagné une seconde section utile aux DEUX étages — les pages asciidoc de git, le
manuel hotdoc de meson — et la liste saute le FICHIER entier : ces sections n'ont
jamais tourné. Deux hooks devenus pertinents aux deux tiers, ignorés en silence.
Une liste de noms de fichiers ne peut pas dire pourquoi un hook y figure, ni
remarquer que cette raison a cessé de couvrir le fichier. Les hooks lisent
désormais EL_STAGE et tranchent par section, la raison écrite à côté de chaque
garde ; la liste disparaît. libgcrypt et libarchive gardent une garde de fichier
entier, qui énonce sa raison au lieu d'être une entrée ailleurs.
Vérifié aux deux étages : git abandonne ses pages de manuel dans les deux, et
conserve ZLIB_NG dans le chroot, où zlib-ng existe.
Assisted-by: Claude Opus 5
2026-08-23 17:23:30 -04:00
|
|
|
else
|
|
|
|
|
echo "meson: chroot python has no posix_local scheme; no relocation needed"
|
|
|
|
|
fi
|
[FIX] four more packages that build without their documentation
git, xz, meson and pam. Each uses the project's own switch where one exists.
git is the one worth reading: `man` appears as a make target twice and
`install-man` twice, once each for git proper and once for contrib/subtree.
Removing the build targets and leaving the install targets gives "No rule to
make target 'install-man'", which reads like a broken Makefile. Four places,
again.
pam is different in kind from the others: xmllint EXISTS here, from our own
libxml2. It fails because pam validates its DocBook sources against DTDs that
are not installed and --nonet forbids fetching them. Shipping the DocBook XML
data would be a package; disabling the documentation is a flag.
--- FR ---
git, xz, meson et pam. Chacun par l'interrupteur du projet lui-même, quand il
existe.
git mérite lecture : `man` figure deux fois comme cible make et `install-man`
deux fois, une paire pour git et une pour contrib/subtree. Retirer les cibles de
construction en laissant celles d'installation donne « No rule to make target
'install-man' », ce qui se lit comme un Makefile cassé. Quatre endroits, encore.
pam diffère en nature : xmllint EXISTE ici, il vient de notre libxml2. Il échoue
parce que pam valide ses sources DocBook contre des DTD non installées, et que
--nonet interdit de les chercher. Livrer les données DocBook serait un paquet ;
désactiver la documentation est un drapeau.
Assisted-by: Claude Opus 5
2026-08-22 00:07:14 -04:00
|
|
|
|
|
|
|
|
# --- no reference manual ------------------------------------------------------
|
|
|
|
|
#
|
|
|
|
|
# meson.build:6:4: ERROR: Problem encountered: Cannot build documentation
|
|
|
|
|
#
|
|
|
|
|
# meson builds its own reference manual with hotdoc, and its docs/meson.build
|
|
|
|
|
# refuses outright when the tool is absent rather than degrading. FOUR LINES:
|
|
|
|
|
# the setup, the compile, the install, and a reference_manual.json copied
|
|
|
|
|
# separately. Leaving any of the last three behind fails on a path in
|
|
|
|
|
# docs.build/ that was never created.
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
python3 - <<'ZZPY'
|
|
|
|
|
import io, re
|
|
|
|
|
lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n")
|
|
|
|
|
pats = [r"meson\.py setup docs docs\.build", r"meson\.py compile -C docs\.build",
|
|
|
|
|
r"meson\.py install -C docs\.build", r"docs\.build/reference_manual\.json"]
|
|
|
|
|
keep = []
|
|
|
|
|
found = 0
|
|
|
|
|
for l in lines:
|
|
|
|
|
if any(re.search(p, l) for p in pats):
|
|
|
|
|
found += 1
|
|
|
|
|
continue
|
|
|
|
|
keep.append(l)
|
|
|
|
|
assert found == 4, "meson: expected four docs lines, got %d" % found
|
|
|
|
|
io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(keep))
|
|
|
|
|
ZZPY
|
|
|
|
|
grep -q "docs.build" PKGBUILD && { echo "meson: a docs.build line survived" >&2; exit 1; }
|
|
|
|
|
echo "meson: reference manual dropped (no hotdoc)"
|