[FIX] stage 2: x86_64 compiler flags, and a .pc naming nothing

Two failures three packages apart, both from the same place: our pacman
package ships Arch's configuration verbatim, and Arch's is for x86_64.

  libxml2/meson.build:1:0: ERROR: Unable to detect linker for compiler
  `cc ... -march=x86-64 -mtune=generic ...`

configure_chroot fixed CARCH, CHOST, MAKEFLAGS and OPTIONS and left CFLAGS
alone. They are emptied rather than translated, because that is what stage 1
used -- the host's makepkg.conf has no CFLAGS line at all -- and 179 working
packages are the evidence. s390x tuning is a deliberate later choice.

Emptied by APPENDING, not commenting. The first attempt put a # in front of
each assignment, and CFLAGS spans several lines: commenting the first left the
continuations active and the quote unbalanced, so makepkg would not start.

Then readline. Its .pc says `Requires.private: termcap` and this repository
ships tinfo.pc; nothing provides termcap.pc. Nothing failed at build time --
a .pc is data, and pkg-config only follows Requires.private when a consumer
asks. The first consumer to ask was libxml2, in the chroot, one stage and
three packages from the cause.

--- FR ---

Deux échecs à trois paquets d'écart, de la même origine : notre paquet pacman
livre la configuration d'Arch telle quelle, et celle d'Arch vise x86_64.

  libxml2/meson.build:1:0: ERROR: Unable to detect linker for compiler
  `cc ... -march=x86-64 -mtune=generic ...`

configure_chroot corrigeait CARCH, CHOST, MAKEFLAGS et OPTIONS, et laissait
CFLAGS. Ils sont vidés plutôt que traduits, car c'est ce qu'a utilisé l'étage
1 — le makepkg.conf de l'hôte n'a aucune ligne CFLAGS — et 179 paquets
fonctionnels en sont la preuve. Le réglage pour s390x est un choix ultérieur
délibéré.

Vidés par AJOUT, non par commentaire. La première tentative mettait un # devant
chaque affectation, et CFLAGS s'étend sur plusieurs lignes : commenter la
première laissait les continuations actives et le guillemet déséquilibré, si
bien que makepkg ne démarrait plus.

Puis readline. Son .pc dit « Requires.private: termcap » alors que ce dépôt
livre tinfo.pc ; personne ne fournit termcap.pc. Rien n'échouait à la
compilation — un .pc est une donnée, et pkg-config ne suit Requires.private
que si un consommateur le demande. Le premier à demander fut libxml2, dans le
chroot, à une étape et trois paquets de la cause.

Assisted-by: Claude Opus 5
This commit is contained in:
Mathieu Benoit 2026-08-19 20:19:37 -04:00
parent adc508d8f6
commit 4d8856b82d
3 changed files with 127 additions and 1 deletions

View file

@ -43,3 +43,42 @@ sed -n '/^makedepends=(/,/)/p' PKGBUILD | grep -q 'qt6-base' && {
grep -q -- '--sphinx-man' PKGBUILD || {
echo "cmake: man pages lost with the GUI" >&2; exit 1; }
echo "cmake: --qt-gui dropped (no Qt, no display)"
# emacs, for one lisp file, at the very last line of package().
#
# PKGBUILD: line 64: emacs: command not found
#
# The build compiled everything, generated the sphinx documentation, installed
# the whole tree, and then died on
#
# emacs -batch -f batch-byte-compile .../site-lisp/cmake-mode.el
#
# make install already put cmake-mode.el in place; this only produces the
# byte-compiled .elc beside it. Emacs loads the .el perfectly well without it,
# a little slower on first load. Installing emacs -- forty megabytes and its
# own dependency tree -- to pre-compile one editor mode for a bootstrap that
# has no editor is not a trade worth making.
#
# THE SHAPE IS WORTH NOTING because it is the third time: pam's PDF removal,
# e2fsprogs' fuse2fs removal, and now this. A failure on the LAST line of
# package(), after a completely successful build, always looks like something
# broke badly. It never is -- it is a tool the host does not have, doing
# something optional.
set -euo pipefail
python3 - <<'ZZPY'
import io
s = io.open("PKGBUILD", encoding="utf-8").read()
old = ' emacs -batch -f batch-byte-compile "${pkgdir}"/usr/share/emacs/site-lisp/cmake-mode.el\n'
assert s.count(old) == 1, "cmake: the emacs byte-compile line is not in the expected form"
s = s.replace(old, "", 1)
old = "makedepends=(emacs\n"
assert s.count(old) == 1, "cmake: makedepends does not start with emacs"
s = s.replace(old, "makedepends=(\n", 1)
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
ZZPY
grep -q 'emacs -batch' PKGBUILD && { echo "cmake: the emacs step survived" >&2; exit 1; }
sed -n '/^makedepends=(/,/)/p' PKGBUILD | grep -qw emacs && {
echo "cmake: emacs still in makedepends" >&2; exit 1; }
grep -q 'cmake-mode' PKGBUILD && {
echo "cmake: a cmake-mode reference remains -- check it is harmless" >&2; }
echo "cmake: emacs byte-compilation dropped (the .el is installed regardless)"

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

@ -0,0 +1,45 @@
#!/usr/bin/env bash
# readline: its .pc file requires a module nothing in the repository provides.
#
# Package 'termcap', required by 'readline', not found
#
# readline's configure writes `Requires.private: termcap` into readline.pc,
# because that is the historical name of the terminal library it links. Our
# ncurses package ships tinfo.pc and ncurses.pc -- there is no termcap.pc, and
# nothing else in the repository supplies one.
#
# Nothing fails at build time. The .pc is data, and pkg-config only follows
# Requires.private when a CONSUMER asks. The first consumer to ask was libxml2,
# inside the stage-2 chroot, and it stopped at meson configure -- three
# packages and one stage away from the cause.
#
# tinfo is the right name: it is what ncurses actually installs the terminfo
# routines into (`--with-termlib`), and readline links -lncurses which pulls it.
# This is the same substitution the host needed for history.pc, made for the
# same reason, in the other direction: there the file was missing and had to be
# generated, here it exists and names the wrong dependency.
#
# The package's .pc is patched in package(), after make install writes it, so
# nothing in the build sees a different file than what ships.
set -euo pipefail
python3 - <<'ZZPY'
import io, re
s = io.open("PKGBUILD", encoding="utf-8").read()
# AFTER make install, not at the top of package(). The first version anchored
# on `package() {` and ran the sed before the file existed:
# sed: can't read .../usr/lib/pkgconfig/readline.pc: No such file or directory
m = re.search(r'^ make -C \$pkgname-\$_basever DESTDIR="\$pkgdir" install\n', s, re.M)
assert m, "readline: the install line in package() is not in the expected form"
fix = """ # readline.pc says `Requires.private: termcap`; this repository ships
# tinfo.pc, not termcap.pc, so any consumer asking pkg-config about readline
# fails on a module nobody provides.
sed -i 's/^Requires.private: termcap$/Requires.private: tinfo/' \\
"$pkgdir"/usr/lib/pkgconfig/readline.pc
grep -q '^Requires.private: tinfo$' "$pkgdir"/usr/lib/pkgconfig/readline.pc
"""
s = s[:m.end()] + fix + s[m.end():]
io.open("PKGBUILD", "w", encoding="utf-8").write(s)
ZZPY
grep -q 'Requires.private: tinfo' PKGBUILD || {
echo "readline: the .pc substitution was not inserted" >&2; exit 1; }
echo "readline: readline.pc now requires tinfo (termcap.pc does not exist here)"

View file

@ -123,7 +123,47 @@ configure_chroot() {
# stages, and comparing them is the whole point.
sudo sed -i 's|^OPTIONS=.*|OPTIONS=(strip docs !libtool !staticlibs emptydirs zipman purge !debug !lto)|' \
"$ROOT/etc/makepkg.conf"
# CFLAGS and friends, which arrive from the same place and are just as
# wrong. Our pacman package ships Arch's makepkg.conf verbatim, so the
# chroot inherits
#
# CFLAGS="-march=x86-64 -mtune=generic -O2 ... -fcf-protection ..."
#
# On s390x cc rejects that, and the failure surfaces nowhere near the
# cause: meson simply cannot start.
#
# libxml2/meson.build:1:0: ERROR: Unable to detect linker for compiler
# `cc -Wl,--version ... -march=x86-64 -mtune=generic ...`
#
# They are emptied rather than translated, because "no flags" is what stage
# 1 used -- the host's makepkg.conf carries no CFLAGS line at all -- and
# 179 working packages are the evidence that it builds. Choosing s390x
# tuning (-march=z13, and only the hardening flags that exist on Z) is a
# deliberate later step, not something to guess at inside a bootstrap.
# TODO.md records it.
#
# APPENDED, not commented. The first attempt here put a # in front of each
# assignment, and CFLAGS is a MULTI-LINE assignment: commenting its first
# line left the continuations active and the quote unbalanced, so makepkg
# would not start at all --
#
# /etc/makepkg.conf: line 109: unexpected EOF while looking for matching `"'
#
# An override at the end of the file needs no parsing of what came before:
# the last assignment is the one that counts.
sudo tee -a "$ROOT/etc/makepkg.conf" > /dev/null <<'EOC'
# --- stage 2: the shipped values are Arch's x86_64 ones ---
CFLAGS=""
CXXFLAGS=""
LDFLAGS=""
LTOFLAGS=""
RUSTFLAGS=""
DEBUG_CFLAGS=""
DEBUG_CXXFLAGS=""
EOC
sudo grep -E '^(CARCH|CHOST|MAKEFLAGS|OPTIONS)=' "$ROOT/etc/makepkg.conf" | sed 's/^/ /'
printf ' compiler flags: commented out (stage 1 used none)\n'
# makepkg refuses to run as root, so the chroot needs the SAME uid as the
# user who owns the bind-mounted sources. A bind mount carries the host's
@ -251,7 +291,9 @@ NOTE
# git.sh joins it for the same reason: it drops ZLIB_NG=1 because Ubuntu has no
# zlib-ng headers, and our own zlib-ng package ships them, so inside the chroot
# the flag is correct and the hook would be a downgrade.
STAGE2_SKIP_HOOKS=(libgcrypt git)
# meson.sh joins them: it moves a wheel out of /usr/local, which only the
# host's Debian-patched python puts there.
STAGE2_SKIP_HOOKS=(libgcrypt git meson)
# Packages whose sources must be extracted on the HOST, because the chroot
# cannot extract anything until they are rebuilt.