diff --git a/patches/pkgbuild/binutils.sh b/patches/pkgbuild/binutils.sh index 2e535c6..3c25017 100755 --- a/patches/pkgbuild/binutils.sh +++ b/patches/pkgbuild/binutils.sh @@ -1,16 +1,45 @@ #!/usr/bin/env bash -# binutils: drop the gold linker. +# binutils: install into /usr/lib, not /usr/lib64. # -# make[1]: *** [Makefile:6798: all-gold] Error 2 +# THIS IS THE ROOT OF A CHAIN, and the chain is worth writing down because +# nothing in it looks related to anything else in it: # -# gold is x86-centric, deprecated upstream since binutils 2.44, and it is not -# built for s390x by any distribution that ships this architecture. Nothing -# links with it here: ld.bfd is the linker on Z. +# 1. On s390x, GCC's and binutils' default MULTILIB_OSDIRNAME is lib64, so +# binutils installs libiberty.a into /usr/lib64 -- creating that directory +# as a REAL directory. On Arch, /usr/lib64 is a SYMLINK to lib. +# 2. meson chooses its default libdir by looking at the system: 64-bit plus a +# real /usr/lib64 means "lib64". A symlink does not count, which is why +# Arch never sees this and arch-meson passes no --libdir at all. +# 3. pkgconf is a meson package, so it installed into /usr/lib64 -- and +# COMPILED IN its search path as /usr/lib64/pkgconfig. +# 4. Every .pc file in the distribution is in /usr/lib/pkgconfig. So every +# pkg-config lookup inside the chroot failed. libxslt reported it as # -# Dropped rather than debugged, because fixing gold for s390x would be work -# spent on a component upstream is removing. +# configure: error: Package requirements (python-3.14) were not met: +# Package 'python-3.14' not found +# +# with python 3.14.7 installed and /usr/lib/pkgconfig/python-3.14.pc +# sitting right there. +# +# Step 4 reads as a missing dependency. Step 1 is a linker library nobody +# thinks about. Fixing only the visible end -- pkgconf's search path -- would +# leave the real /usr/lib64 there, and the next meson package would land in it. +# +# --libdir is set explicitly rather than left to the target default, which is +# what Arch's layout means: one library directory, /usr/lib, with lib64 as a +# compatibility symlink. set -euo pipefail -sed -i 's/--enable-gold[^ \\]*//' PKGBUILD -sed -i 's/^\(\s*\)--enable-gold.*$/\1--disable-gold \\/' PKGBUILD -grep -q -- '--enable-gold' PKGBUILD && { echo "binutils: gold still enabled" >&2; exit 1; } -echo "binutils: gold linker disabled (deprecated upstream, absent on s390x)" +python3 - <<'ZZPY' +import io, re +lines = io.open("PKGBUILD", encoding="utf-8").read().split("\n") +hit = [i for i, l in enumerate(lines) if re.search(r"/configure\b", l) and "#" not in l.split("/configure")[0]] +assert len(hit) == 1, "binutils: expected one configure call, got %d" % len(hit) +i = hit[0] +indent = re.match(r"^[ \t]*", lines[i]).group(0) +assert lines[i].rstrip().endswith("\\"), "binutils: configure does not continue" +lines.insert(i + 1, indent + " --libdir=/usr/lib \\") +io.open("PKGBUILD", "w", encoding="utf-8").write("\n".join(lines)) +ZZPY +grep -A1 -E "/configure( |\\\\)" PKGBUILD | grep -q -- '--libdir=/usr/lib' || { + echo "binutils: --libdir is not on the configure line" >&2; exit 1; } +echo "binutils: libdir pinned to /usr/lib (keeps /usr/lib64 a symlink)" diff --git a/scripts/build-stage2.sh b/scripts/build-stage2.sh index 6e581c0..80c2d3d 100755 --- a/scripts/build-stage2.sh +++ b/scripts/build-stage2.sh @@ -164,9 +164,37 @@ EOF # files directly, so --nodeps installs exactly these and not a resolved # closure -- correct here, since stage 1 already supplied the closure just # above. + # ONLY WHAT WAS ALREADY THERE. Restoring everything in repo2 fails: + # + # :: zlib-ng-compat-2.3.3-1 and zlib-1:1.3.2-3 are in conflict + # stage2: restoring stage-2 output failed + # + # Both are legitimate stage-2 output -- zlib-ng's PKGBUILD produces + # zlib-ng-compat, and zlib is its own package. A REPOSITORY holds both + # happily; an installed system cannot, because zlib-ng-compat replaces + # zlib. Stage 1 chose zlib for this chroot, and that choice is not stage + # 2's to revisit while it is rebuilding. + # + # So the filter is not a conflict list, which would need extending every + # time a package like this appeared. It is the intent stated exactly: put + # back the stage-2 build OF WHAT IS INSTALLED. A stage-2 package for + # something the chroot does not have was never part of this chroot, and + # installing it would quietly grow the build environment past what + # CHROOT_PKGS defines. shopt -s nullglob - local back=("$REPO2"/*.pkg.tar.*) + local cand=("$REPO2"/*.pkg.tar.*) shopt -u nullglob + local back=() inst f pn + if [ "${#cand[@]}" -gt 0 ]; then + inst=$(sudo pacman --root "$ROOT" --config "$CONF" -Qq 2>/dev/null) + for f in "${cand[@]}"; do + pn=$(bsdtar -xOf "$f" .PKGINFO 2>/dev/null | sed -n 's/^pkgname = //p') + [ -n "$pn" ] || continue + grep -qxF "$pn" <<< "$inst" && back+=("$f") + done + printf ' %s of %s stage-2 package(s) are installed here\n' \ + "${#back[@]}" "${#cand[@]}" + fi if [ "${#back[@]}" -gt 0 ]; then sudo pacman --root "$ROOT" --config "$CONF" --cachedir "$CACHE" \ --noconfirm --nodeps -U "${back[@]}" \ @@ -302,10 +330,28 @@ EOC sudo install -d -m0755 "$ROOT/usr/local/bin" sudo tee "$ROOT/usr/local/bin/arch-meson" > /dev/null <<'EOW' #!/usr/bin/env bash -# stage 2: this chroot has none of Arch's optional documentation tools, so a -# feature that cannot be built should be skipped rather than fatal. Appended, -# because meson honours the last occurrence. -exec /usr/bin/arch-meson "$@" --auto-features auto +# stage 2, two overrides, both appended because meson honours the last +# occurrence of an option. +# +# --auto-features auto This chroot has none of Arch's optional documentation +# tools, so a feature that cannot be built should be +# skipped rather than fatal. +# +# --libdir lib meson picks its default libdir by inspecting the +# system: 64-bit plus a REAL /usr/lib64 means "lib64". +# On Arch /usr/lib64 is a symlink, so it never triggers +# and arch-meson passes no --libdir at all. In here +# binutils had created a real /usr/lib64 (s390x's +# default MULTILIB_OSDIRNAME), so meson flipped, pkgconf +# installed there, and pkgconf COMPILED IN +# /usr/lib64/pkgconfig as its search path -- while every +# .pc file in the distribution is in /usr/lib/pkgconfig. +# Result: every pkg-config lookup in the chroot failed, +# reported by libxslt as a missing python-3.14 that was +# installed. binutils is pinned too; this is the +# backstop, because the heuristic will fire again the +# moment anything else creates that directory. +exec /usr/bin/arch-meson "$@" --auto-features auto --libdir lib EOW sudo chmod 755 "$ROOT/usr/local/bin/arch-meson" printf ' arch-meson: wrapped with --auto-features auto\n' @@ -468,7 +514,18 @@ NOTE # and Ubuntu ships 2.14.5. Ours is 2.15.3 and lives only in here, so this is # the only place libxslt can come from -- built by stage 2, not installed # into it. It has to precede shadow, which the list order does not do. -STAGE2_FIRST=(texinfo libxslt) +# +# binutils, then pkgconf -- in that order, and both before anything that uses +# pkg-config. Stage 1's repository is clean of /usr/lib64; the stage-2 +# binutils build created it, because s390x's default MULTILIB_OSDIRNAME is +# lib64. meson then chose lib64 as its default libdir (it inspects the system, +# and a REAL /usr/lib64 flips it -- Arch's is a symlink), pkgconf installed +# there, and pkgconf compiled in /usr/lib64/pkgconfig as its search path while +# every .pc in the distribution sits in /usr/lib/pkgconfig. Every pkg-config +# lookup in the chroot then failed. binutils first so the directory is gone +# before pkgconf builds; the arch-meson wrapper pins --libdir lib as the +# backstop. +STAGE2_FIRST=(texinfo binutils pkgconf libxslt) STAGE2_SKIP_HOOKS=(libgcrypt git meson libarchive) diff --git a/scripts/test-chroot.sh b/scripts/test-chroot.sh index b0d5745..b5647f5 100755 --- a/scripts/test-chroot.sh +++ b/scripts/test-chroot.sh @@ -96,13 +96,29 @@ else fi sudo rm -rf "$ROOT.probe" -note "2. ARTEFACT -- host contamination that raises no error" +note "2. ARTEFACT -- wrong library directories, which raise no error" +# TWO wrong libdirs, not one. +# +# This check was written for Debian's multiarch layout, lib/s390x-linux-gnu, +# because that was the contamination stage 1 kept producing. It reported "no +# Debian multiarch libdir anywhere" while binutils and pkgconf were shipping +# files in /usr/lib64 -- true, and useless, because it was answering a narrower +# question than the one it appeared to answer. +# +# usr/lib64 matters for a reason that is not symmetry. On Arch it is a SYMLINK +# to lib; a package that ships it as a real directory changes what meson picks +# as its default libdir, which changed where pkgconf installed, which changed +# pkgconf's compiled-in search path, which broke every pkg-config lookup in the +# chroot. One stray .a file at the bottom of that. n=0 for f in "$REPO"/*.pkg.tar.*; do - c=$(bsdtar -tf "$f" 2>/dev/null | grep -c 's390x-linux-gnu/') + _l=$(bsdtar -tf "$f" 2>/dev/null) + c=$(grep -c 's390x-linux-gnu/' <<< "$_l") [ "$c" -gt 0 ] && { bad "$(basename "$f"): $c multiarch paths"; n=$((n + 1)); } + c=$(grep -c '^usr/lib64/' <<< "$_l") + [ "$c" -gt 0 ] && { bad "$(basename "$f"): $c paths under usr/lib64"; n=$((n + 1)); } done -[ "$n" -eq 0 ] && good "no Debian multiarch libdir anywhere" +[ "$n" -eq 0 ] && good "every library is under usr/lib -- no multiarch, no lib64" note "3. SONAME -- what binaries ask for versus what the repository ships" # Two passes over the packages: collect the soname each shared library