From cb3a32101a12ab1e5f8e6d141c1129aaace3008b Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Sun, 23 Aug 2026 17:23:30 -0400 Subject: [PATCH] [FIX] a hook's scope belongs to its sections, not to its filename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- patches/pkgbuild/git.sh | 14 ++++++++++++++ patches/pkgbuild/libarchive.sh | 9 +++++++++ patches/pkgbuild/libgcrypt.sh | 9 +++++++++ patches/pkgbuild/meson.sh | 14 ++++++++++++++ scripts/bootstrap-pacman.sh | 6 +++++- scripts/build-stage2.sh | 27 +++++++++++++-------------- 6 files changed, 64 insertions(+), 15 deletions(-) diff --git a/patches/pkgbuild/git.sh b/patches/pkgbuild/git.sh index b6ad819..185588a 100755 --- a/patches/pkgbuild/git.sh +++ b/patches/pkgbuild/git.sh @@ -24,6 +24,17 @@ # choice will have to be made then: build Rust, or drop the flag. Recorded now # rather than discovered twice. set -euo pipefail +# 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 + python3 - <<'PY' import io s = io.open("PKGBUILD", encoding="utf-8").read() @@ -45,6 +56,9 @@ grep -q "'zlib-ng'" PKGBUILD && { grep -qF "'grep' 'shadow' 'zlib')" PKGBUILD || { echo "git: zlib not substituted in depends" >&2; exit 1; } echo "git: plain zlib (no zlib-ng headers on the host); STAGE-1 ONLY" +else + echo "git: zlib-ng available in the chroot; keeping Arch's choice" +fi # --- no man pages ------------------------------------------------------------- # diff --git a/patches/pkgbuild/libarchive.sh b/patches/pkgbuild/libarchive.sh index ecb0529..8290974 100755 --- a/patches/pkgbuild/libarchive.sh +++ b/patches/pkgbuild/libarchive.sh @@ -37,6 +37,15 @@ # leaves the package uninstallable for a reason the diff appears to fix -- the # fifth place, and the one that has cost the most time in this port. set -euo pipefail + +# STAGE 1 ONLY, the whole file. Expressed here rather than in a list kept by the +# driver: a list of file names cannot say WHY, and when a hook later grows a +# section that both stages need, the list silently skips that too. That happened +# to git and meson. +if [ "${EL_STAGE:-1}" != 1 ]; then + echo "libarchive: nothing to do at stage 2 -- our libxml2 and icu agree in the chroot" + exit 0 +fi python3 - <<'ZZPY' import io, re s = io.open("PKGBUILD", encoding="utf-8").read() diff --git a/patches/pkgbuild/libgcrypt.sh b/patches/pkgbuild/libgcrypt.sh index 6e94427..7af1ab6 100755 --- a/patches/pkgbuild/libgcrypt.sh +++ b/patches/pkgbuild/libgcrypt.sh @@ -53,6 +53,15 @@ # MAKEFLAGS bound inside build() rather than at hook level. set -euo pipefail +# STAGE 1 ONLY, the whole file. Expressed here rather than in a list kept by the +# driver: a list of file names cannot say WHY, and when a hook later grows a +# section that both stages need, the list silently skips that too. That happened +# to git and meson. +if [ "${EL_STAGE:-1}" != 1 ]; then + echo "libgcrypt: nothing to do at stage 2 -- our libgpg-error is installed in the chroot" + exit 0 +fi + WORK="${WORK:-$HOME/work/arch-s390x}" PREFIX="$WORK/stage1-prefix" diff --git a/patches/pkgbuild/meson.sh b/patches/pkgbuild/meson.sh index 49a759d..01c6511 100755 --- a/patches/pkgbuild/meson.sh +++ b/patches/pkgbuild/meson.sh @@ -40,6 +40,17 @@ # will actually import this. Deriving it from the interpreter running the build # is the intuitive thing and the wrong one. set -euo pipefail +# 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 + python3 - <<'ZZPY' import io s = io.open("PKGBUILD", encoding="utf-8").read() @@ -89,6 +100,9 @@ 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" +else + echo "meson: chroot python has no posix_local scheme; no relocation needed" +fi # --- no reference manual ------------------------------------------------------ # diff --git a/scripts/bootstrap-pacman.sh b/scripts/bootstrap-pacman.sh index 3d369b3..3f35e0e 100755 --- a/scripts/bootstrap-pacman.sh +++ b/scripts/bootstrap-pacman.sh @@ -403,7 +403,11 @@ build_package() { local hook="$PATCH_DIR/$name.sh" if [ -f "$hook" ]; then git checkout -- PKGBUILD 2>/dev/null || true - bash "$hook" || return 1 + # EL_STAGE tells the hook which stage is asking. Some hooks -- git, + # meson, libgcrypt, libarchive -- exist partly or wholly to work around + # something about the HOST, and that part must not run in the chroot + # where the problem does not exist. + EL_STAGE=1 bash "$hook" || return 1 fi rm -f ./*.pkg.tar.* # Explicit `|| return 1`. Relying on `set -e` here does NOT work: callers diff --git a/scripts/build-stage2.sh b/scripts/build-stage2.sh index 8b45040..21ba418 100755 --- a/scripts/build-stage2.sh +++ b/scripts/build-stage2.sh @@ -687,7 +687,7 @@ NOTE # backstop. # # libxml2 -- because the xar cycle reformed INSIDE stage 2. libarchive is in -# STAGE2_SKIP_HOOKS, so stage 2 rebuilt it as Arch does, with xar, linking the +# a hook that runs at stage 1 only, so stage 2 rebuilt it as Arch does, with # libxml2 that happened to be installed at that moment: the stage-1 one, which # wants the host's libicuuc.so.76. bsdtar then could not start, and bsdtar is # what makepkg uses to extract sources and write packages -- so 39 of 39 @@ -781,8 +781,6 @@ CHROOT_STAGE2_PKGS=( python-setuptools-scm ) -STAGE2_SKIP_HOOKS=(libgcrypt git meson libarchive) - # Packages whose sources must be extracted on the HOST, because the chroot # cannot extract anything until they are rebuilt. # @@ -825,12 +823,6 @@ host_extract() { return 1 } -skip_hook() { - local n="$1" h - for h in "${STAGE2_SKIP_HOOKS[@]}"; do [ "$n" = "$h" ] && return 0; done - return 1 -} - # stage2_build -- rebuild one package inside the chroot and install it. # # The hook is applied on the HOST, not in the chroot: hooks are seds over the @@ -843,10 +835,18 @@ stage2_build() { ( cd "$dir" && git checkout -- PKGBUILD 2>/dev/null ) || true if [ -f "$PATCH_DIR/$name.sh" ]; then - if skip_hook "$name"; then - echo " hook skipped (stage-1 only)" - else - ( cd "$dir" && bash "$PATCH_DIR/$name.sh" ) || { + # EL_STAGE=2, and no skip list any more. + # + # There WAS a list -- naming hooks to ignore here. It skipped the + # whole FILE, and that broke silently the moment a listed hook grew a + # section both stages need: git and meson each kept failing in stage + # 2 on exactly what their new sections fix, while the log said + # "hook skipped (stage-1 only)" about a hook that was by then two + # thirds relevant. + # + # A list of file names cannot say why. The hooks read EL_STAGE and + # decide per section, with the reason written beside the guard. + ( cd "$dir" && EL_STAGE=2 bash "$PATCH_DIR/$name.sh" ) || { echo " hook failed" >&2; return 1; } # A hook can leave a PKGBUILD that no longer parses, and makepkg # reports that far from its cause: @@ -863,7 +863,6 @@ stage2_build() { # calls a valid file broken without it. ( cd "$dir" && bash -O extglob -n PKGBUILD ) || { echo " hook left an unparseable PKGBUILD" >&2; return 1; } - fi fi ( cd "$dir" && rm -f ./*.pkg.tar.* ) || true