[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# Stage 2: rebuild the repository inside the repository.
|
|
|
|
|
#
|
|
|
|
|
# WHAT STAGE 2 IS FOR
|
|
|
|
|
#
|
|
|
|
|
# Every package stage 1 produced was compiled against UBUNTU's libraries. That
|
|
|
|
|
# is not a defect -- Arch's glibc needs an Arch gcc which needs an Arch glibc,
|
|
|
|
|
# so the first pass has nowhere else to start -- but it leaves host artefacts
|
|
|
|
|
# baked in. scripts/test-chroot.sh names nine of them precisely: binaries that
|
|
|
|
|
# ask for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more, at the
|
|
|
|
|
# host's soname versions, while the repository ships Arch's. Stage 2 dissolves
|
|
|
|
|
# all nine by rebuilding each package against what the repository actually has.
|
|
|
|
|
#
|
|
|
|
|
# THE CONSTRAINT THAT SHAPES THIS SCRIPT
|
|
|
|
|
#
|
|
|
|
|
# pacman cannot run inside the stage-1 rootfs. libalpm was linked against the
|
|
|
|
|
# host's gpgme, so the binary is there and does not start:
|
|
|
|
|
#
|
|
|
|
|
# pacman: error while loading shared libraries: libgpgme.so.11
|
|
|
|
|
#
|
|
|
|
|
# So the rootfs is populated from OUTSIDE, with the host's pacman and --root,
|
|
|
|
|
# the way scripts/test-chroot.sh does. The chroot is used only to BUILD. That
|
|
|
|
|
# is not a workaround, it is the order the problem has: stage 2's own output
|
|
|
|
|
# is the first pacman that will run on the target.
|
|
|
|
|
#
|
|
|
|
|
# makepkg, by contrast, is a shell script, and it works.
|
|
|
|
|
set -uo pipefail
|
|
|
|
|
|
|
|
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
# Stage 2 rebuilds the same packages in the same order; packages.sh explains
|
|
|
|
|
# why that order is not re-derived here.
|
|
|
|
|
source "$HERE/packages.sh"
|
[FIX] stage 2: the build tools the host was providing silently
glibc rebuilt inside the chroot; binutils died on `make info-recursive`, and
makeinfo said why: its XS module was compiled against the host's perl 5.40 and
our perl is 5.42. Stage-1 texinfo cannot run in there. Everything that builds
.info documentation needs it, and it sat near the end of the list because that
is where its own dependencies are, so stage 2 hoists it.
linux-api-headers stopped earlier on `rsync: command not found` -- the kernel's
headers_install runs it. apt had put it there and a build that finds its tool
says nothing, so stage 1 never mentioned it. Its man pages come from Markdown
the git tag does not pre-render, hence --disable-md2man.
An inventory of the 110 apt packages against the chroot found 84 more such
binaries. Most are documentation; the rest wait until something needs them.
--- FR ---
glibc a été rebâti dans le chroot ; binutils est mort sur `make info-recursive`,
et makeinfo en donne la raison : son module XS a été compilé contre le perl 5.40
de l'hôte, or le nôtre est en 5.42. Le texinfo de l'étage 1 ne peut pas tourner
là-dedans. Tout ce qui bâtit de la documentation .info en dépend, et il figurait
en fin de liste, là où sont ses propres dépendances : l'étage 2 le remonte.
linux-api-headers s'était arrêté avant sur `rsync: command not found` — le
headers_install du noyau l'appelle. apt l'avait posé, et une construction qui
trouve son outil n'en dit rien : l'étage 1 ne l'a jamais mentionné. Ses pages de
manuel viennent d'un Markdown que l'étiquette git ne rend pas, d'où
--disable-md2man.
Un inventaire des 110 paquets apt face au chroot a trouvé 84 autres binaires du
même genre. La plupart sont de la documentation ; le reste attendra qu'un paquet
les réclame.
Assisted-by: Claude Opus 5
2026-08-20 01:34:41 -04:00
|
|
|
# watched(): bootstrap-pacman.sh cannot be sourced here -- see lib-watch.sh.
|
|
|
|
|
source "$HERE/lib-watch.sh"
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
WORK="${WORK:-$HOME/work/arch-s390x}"
|
|
|
|
|
REPO1="${REPO1:-$WORK/repo/s390x}"
|
|
|
|
|
REPO2="${REPO2:-$WORK/repo2/s390x}"
|
|
|
|
|
ROOT="${ROOT:-$WORK/rootfs-stage2}"
|
|
|
|
|
CONF="$WORK/pacman-stage2.conf"
|
|
|
|
|
CACHE="$WORK/pacman-stage2.cache"
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
CONF2="$WORK/pacman-stage2-both.conf"
|
|
|
|
|
STATE2="$WORK/stage2.state"
|
|
|
|
|
PATCH_DIR="${PATCH_DIR:-$HERE/../patches/pkgbuild}"
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
BUILDER="${BUILDER:-$(id -un)}"
|
|
|
|
|
BUILD_UID="$(id -u)"
|
|
|
|
|
BUILD_GID="$(id -g)"
|
|
|
|
|
|
|
|
|
|
# The chroot's contents. Two groups, and the second is the one the dependency
|
|
|
|
|
# resolver could never have told us about: nothing in the repository DEPENDS on
|
|
|
|
|
# bison or fakeroot, they are simply what a build needs to happen.
|
|
|
|
|
CHROOT_PKGS=(
|
|
|
|
|
# A system that reaches a shell and can read a package.
|
|
|
|
|
filesystem glibc bash coreutils sed grep gawk findutils file which
|
|
|
|
|
tar gzip xz bzip2 zstd libarchive diffutils patch
|
|
|
|
|
# The toolchain.
|
|
|
|
|
gcc binutils make m4 autoconf automake libtool pkgconf
|
|
|
|
|
bison flex texinfo groff gettext
|
|
|
|
|
# makepkg itself, and the one thing it cannot do without.
|
|
|
|
|
pacman fakeroot
|
[FIX] stage 2: the build tools the host was providing silently
glibc rebuilt inside the chroot; binutils died on `make info-recursive`, and
makeinfo said why: its XS module was compiled against the host's perl 5.40 and
our perl is 5.42. Stage-1 texinfo cannot run in there. Everything that builds
.info documentation needs it, and it sat near the end of the list because that
is where its own dependencies are, so stage 2 hoists it.
linux-api-headers stopped earlier on `rsync: command not found` -- the kernel's
headers_install runs it. apt had put it there and a build that finds its tool
says nothing, so stage 1 never mentioned it. Its man pages come from Markdown
the git tag does not pre-render, hence --disable-md2man.
An inventory of the 110 apt packages against the chroot found 84 more such
binaries. Most are documentation; the rest wait until something needs them.
--- FR ---
glibc a été rebâti dans le chroot ; binutils est mort sur `make info-recursive`,
et makeinfo en donne la raison : son module XS a été compilé contre le perl 5.40
de l'hôte, or le nôtre est en 5.42. Le texinfo de l'étage 1 ne peut pas tourner
là-dedans. Tout ce qui bâtit de la documentation .info en dépend, et il figurait
en fin de liste, là où sont ses propres dépendances : l'étage 2 le remonte.
linux-api-headers s'était arrêté avant sur `rsync: command not found` — le
headers_install du noyau l'appelle. apt l'avait posé, et une construction qui
trouve son outil n'en dit rien : l'étage 1 ne l'a jamais mentionné. Ses pages de
manuel viennent d'un Markdown que l'étiquette git ne rend pas, d'où
--disable-md2man.
Un inventaire des 110 paquets apt face au chroot a trouvé 84 autres binaires du
même genre. La plupart sont de la documentation ; le reste attendra qu'un paquet
les réclame.
Assisted-by: Claude Opus 5
2026-08-20 01:34:41 -04:00
|
|
|
# rsync, which is not a runtime dependency of anything here. The kernel's
|
|
|
|
|
# headers_install target runs it, so linux-api-headers -- the first package
|
|
|
|
|
# stage 2 tries -- stopped on `rsync: command not found`. The host had it
|
|
|
|
|
# from apt, which is exactly why stage 1 never mentioned it.
|
|
|
|
|
rsync
|
[FIX] s390x: say which machine this port targets
zlib stopped on '__builtin_s390_vec_unpackl' requires '-mvx', after its own
configure had detected vector support and defined -DHAVE_S390X_VX. Both halves
were right, so it was worth measuring rather than patching:
host gcc (Ubuntu) --with-arch=z13 --with-tune=z16 default -march=arch11
our gcc nothing default -march=arch5
arch5 is z900, from 2000. Arch's PKGBUILD names no s390x baseline because Arch
has no s390x, so ours fell back to the oldest machine imaginable while zlib went
on detecting a CPU the compiler had been told to forget. Every distribution
picks one of these; this port had never said which, and silence chose 2000.
z13 is what Ubuntu s390x already requires, so nothing that runs today stops.
Set in two places: makepkg.conf, how this distribution is compiled, and gcc's
--with-arch, what the compiler we ship assumes with no flags at all.
--- FR ---
zlib s'est arrêté sur '__builtin_s390_vec_unpackl' requires '-mvx', après que
son propre configure avait détecté le support vectoriel et défini
-DHAVE_S390X_VX. Les deux moitiés avaient raison : il fallait mesurer, pas
rustiner.
gcc de l'hôte --with-arch=z13 --with-tune=z16 défaut -march=arch11
notre gcc rien défaut -march=arch5
arch5, c'est z900, de l'an 2000. Le PKGBUILD d'Arch ne nomme aucune base s390x
puisque Arch n'a pas de s390x : le nôtre retombait sur la machine la plus
ancienne imaginable pendant que zlib détectait un processeur qu'on avait dit au
compilateur d'oublier. Toute distribution en choisit une ; ce portage ne l'avait
jamais dit, et le silence a choisi 2000.
z13 est ce qu'Ubuntu s390x exige déjà : rien qui tourne aujourd'hui ne cesse de
tourner. Posé aux deux endroits : makepkg.conf, comment cette distribution est
compilée, et le --with-arch de gcc, ce que suppose le compilateur qu'on livre.
Assisted-by: Claude Opus 5
2026-08-21 00:23:26 -04:00
|
|
|
# The same class, named by the failures of the first full pass rather than
|
|
|
|
|
# guessed at. Each one is here because a package said so:
|
|
|
|
|
#
|
|
|
|
|
# gperf coreutils, diffutils, systemd, libseccomp
|
|
|
|
|
# wget sed, grep, findutils
|
|
|
|
|
# patchelf curl
|
|
|
|
|
# inetutils gnupg, for hostname
|
|
|
|
|
# libxslt shadow, for xsltproc
|
|
|
|
|
# swig audit
|
|
|
|
|
# help2man flex
|
[ADD] the seven tools stage 2 asked for by name
Six built on the host. libxslt will not: its configure demands libxml2 2.15.1
and Ubuntu ships 2.14.5. Ours is 2.15.3 and exists only inside the stage-2
chroot, so that chroot is the only place libxslt can come from -- built BY
stage 2 rather than installed into it, and hoisted so it precedes shadow, which
died on `xsltproc is missing`.
That is a shape worth naming: a package the host cannot build because the host
is behind. Not contamination and not a closure gap -- the wrong machine.
Three more tools were asked for and refused: po4a for fakeroot, a2x for
ca-certificates, asciidoctor for cryptsetup. Perl, Python and Ruby respectively,
each to render a man page. Hooks instead.
--- FR ---
Six se bâtissent sur l'hôte. Pas libxslt : son configure exige libxml2 2.15.1 et
Ubuntu livre 2.14.5. Le nôtre est en 2.15.3 et n'existe que dans le chroot de
l'étage 2 : ce chroot est donc le seul endroit d'où libxslt puisse venir — bâti
PAR l'étage 2 plutôt qu'installé dedans, et hissé pour précéder shadow, mort sur
`xsltproc is missing`.
La forme mérite un nom : un paquet que l'hôte ne peut pas bâtir parce que l'hôte
est en retard. Ni contamination ni trou de fermeture — la mauvaise machine.
Trois autres outils réclamés et refusés : po4a pour fakeroot, a2x pour
ca-certificates, asciidoctor pour cryptsetup. Perl, Python et Ruby
respectivement, chacun pour rendre une page de manuel. Des hooks à la place.
Assisted-by: Claude Opus 5
2026-08-21 00:29:40 -04:00
|
|
|
#
|
|
|
|
|
# libxslt is NOT here, and cannot be: it will not build on the host at all.
|
|
|
|
|
#
|
|
|
|
|
# configure: error: Version 2.14.5 found. You need at least libxml2
|
|
|
|
|
# 2.15.1 for this version of libxslt
|
|
|
|
|
#
|
|
|
|
|
# 2.14.5 is Ubuntu's libxml2. Ours is 2.15.3 -- and it exists only inside
|
|
|
|
|
# this chroot, which is the one place libxslt can be built. So it is built
|
|
|
|
|
# BY stage 2 rather than installed into it, which is why it sits in
|
|
|
|
|
# STAGE2_FIRST instead. Listing it here would fail make_rootfs with "target
|
|
|
|
|
# not found" against a repository that will never contain it.
|
|
|
|
|
gperf wget patchelf inetutils swig help2man
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
# libxcrypt-compat, for a reason no declaration expresses. perl declares
|
|
|
|
|
# `libxcrypt` and `libcrypt.so`, both satisfied by libxcrypt, which ships
|
|
|
|
|
# libcrypt.so.2. But perl's BINARY was linked on the host against Ubuntu's
|
|
|
|
|
# libcrypt.so.1, so it does not start:
|
|
|
|
|
#
|
|
|
|
|
# /usr/bin/perl: error while loading shared libraries: libcrypt.so.1
|
|
|
|
|
#
|
|
|
|
|
# The repository does ship that soname -- in libxcrypt-compat, a separate
|
|
|
|
|
# sub-package -- so this is neither a missing package nor a soname the
|
|
|
|
|
# audit should have flagged. It is a third thing: the dependency
|
|
|
|
|
# declarations cannot pull it in, because they name the unversioned soname
|
|
|
|
|
# that the newer library also provides. Listed explicitly, because nothing
|
|
|
|
|
# will deduce it.
|
|
|
|
|
libxcrypt-compat
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
# git: 51 PKGBUILDs use git sources, and makepkg validates the clone even
|
|
|
|
|
# under --noextract.
|
|
|
|
|
git
|
[ADD] the build systems stage 2 rebuilds with
Ten of the 159 PKGBUILDs call arch-meson and four call cmake, so a chroot
without them could rebuild most of the repository and then stop. Neither is a
dependency of anything in the repository, which is why no closure round ever
named them -- the same shape as fakeroot and bison, one layer up.
python returns with meson, and that is not the earlier decision being
reversed. Dropping python at stage 1 was about what the REPOSITORY must
supply: it was wanted only by two wheels Arch's own python could not load.
This is about what the BUILD ENVIRONMENT must contain, and meson is written in
Python. Different question, different answer.
python needed two fixes of its own. Its xvfb loop is in build() AND in
check(); stage 1 never ran the second because of --nocheck, but stage 2 drops
--nocheck on purpose, so it would have spun there too. And Python 3.13 removed
the vendored libmpdec, so --with-system-libmpdec is the only way to build and
needs the host headers -- reported nine hundred lines in as a missing make
rule for a file that used to be vendored.
cmake wanted rhash, then jsoncpp, then cppdap, one at a time. That is a queue,
and the rule in install_host_deps says a queue is a feature to disable. Not
applied here, deliberately: each exists as an Ubuntu package, so the queue
ends. The rule is for queues that do not, like dbus reaching a documentation
tool that needed Qt. Its Qt GUI is dropped -- a dialog box on a headless
mainframe.
--- FR ---
Dix des 159 PKGBUILD appellent arch-meson et quatre appellent cmake : un
chroot sans eux pourrait reconstruire l'essentiel du dépôt puis s'arrêter.
Aucun des deux n'est une dépendance de quoi que ce soit dans le dépôt, ce qui
explique qu'aucun tour de fermeture ne les ait nommés — même forme que
fakeroot et bison, une couche plus haut.
python revient avec meson, et ce n'est pas un revirement. L'écarter à l'étage
1 portait sur ce que le DÉPÔT doit fournir : il n'était voulu que par deux
roues que le python d'Arch ne pouvait pas charger. Ici il s'agit de ce que
l'ENVIRONNEMENT DE BUILD doit contenir, et meson est écrit en Python. Autre
question, autre réponse.
python a demandé deux correctifs propres. Sa boucle xvfb est dans build() ET
dans check() ; l'étage 1 n'a jamais exécuté la seconde grâce à --nocheck, mais
l'étage 2 l'abandonne exprès — elle y aurait tourné aussi. Et Python 3.13 a
retiré le libmpdec embarqué : --with-system-libmpdec est la seule voie et
réclame les en-têtes de l'hôte, signalé neuf cents lignes plus loin comme une
règle make manquante pour un fichier autrefois embarqué.
cmake a réclamé rhash, puis jsoncpp, puis cppdap, un par un. C'est une file, et
la règle d'install_host_deps dit qu'une file est une fonctionnalité à
désactiver. Non appliquée ici, délibérément : chacun existe en paquet Ubuntu,
donc la file se termine. La règle vise celles qui ne terminent pas, comme dbus
atteignant un outil de documentation qui exigeait Qt. Son interface Qt est
retirée — une boîte de dialogue sur un mainframe sans écran.
Assisted-by: Claude Opus 5
2026-08-19 19:53:46 -04:00
|
|
|
# meson and cmake, with the python they are written in. Ten PKGBUILDs
|
|
|
|
|
# call arch-meson and four call cmake, so without these stage 2 could
|
|
|
|
|
# rebuild most of the repository and then stop.
|
|
|
|
|
python meson ninja cmake
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
log() { printf '\n== %s ==\n' "$*"; }
|
|
|
|
|
die() { printf 'stage2: %s\n' "$*" >&2; exit 1; }
|
|
|
|
|
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
# A PREDICATE and a fatal check, kept apart on purpose.
|
|
|
|
|
#
|
|
|
|
|
# require_space calls die, which exits. Using it inside the rebuild loop as
|
|
|
|
|
# `require_space || break` looks like a clean early stop and is not one: the
|
|
|
|
|
# break is unreachable, the run ends mid-loop, and the summary naming which
|
|
|
|
|
# packages were rebuilt and which failed is never printed. At the start of the
|
|
|
|
|
# run, exiting IS the right answer -- there is nothing to summarise yet.
|
|
|
|
|
space_ok() {
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
local free_mb
|
|
|
|
|
free_mb=$(df -Pm "$WORK" | awk 'NR==2 {print $4}')
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
if [ "${free_mb:-0}" -lt 8192 ]; then
|
|
|
|
|
printf ' only %s MiB free under %s; need 8192\n' "${free_mb:-0}" "$WORK" >&2
|
|
|
|
|
return 1
|
|
|
|
|
fi
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
}
|
|
|
|
|
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
require_space() { space_ok || die "not enough disk space to start"; }
|
|
|
|
|
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
make_rootfs() {
|
|
|
|
|
log "Populating the stage-2 rootfs from stage 1"
|
|
|
|
|
cat > "$CONF" <<EOF
|
|
|
|
|
[options]
|
|
|
|
|
Architecture = s390x
|
|
|
|
|
SigLevel = Never
|
|
|
|
|
[core]
|
|
|
|
|
Server = file://$REPO1
|
|
|
|
|
EOF
|
|
|
|
|
sudo rm -rf "$ROOT" "$CACHE"
|
|
|
|
|
sudo mkdir -p "$ROOT/var/lib/pacman" "$CACHE"
|
|
|
|
|
sudo pacman --root "$ROOT" --config "$CONF" --cachedir "$CACHE" \
|
|
|
|
|
--noconfirm -Sy "${CHROOT_PKGS[@]}" > "$WORK/stage2-install.txt" 2>&1 \
|
|
|
|
|
|| { tail -20 "$WORK/stage2-install.txt" >&2; die "populate failed"; }
|
|
|
|
|
printf ' %s packages installed\n' "$(sudo ls "$ROOT/var/lib/pacman/local" | wc -l)"
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
|
|
|
|
|
# Put stage 2's own output back on top of it.
|
|
|
|
|
#
|
|
|
|
|
# make_rootfs wipes and repopulates from stage 1 on EVERY run, which is
|
|
|
|
|
# what makes the chroot reproducible -- and what would make stage 2
|
|
|
|
|
# unresumable, because stage2.state survives while the packages it names do
|
|
|
|
|
# not. The second invocation would say "already rebuilt, skipping" about
|
|
|
|
|
# packages that had just been thrown away, and the next build would link
|
|
|
|
|
# against stage-1 libraries while the record claimed otherwise. Silent, and
|
|
|
|
|
# the kind of thing found weeks later in an artefact.
|
|
|
|
|
#
|
|
|
|
|
# Stage 2 is a hundred and thirty-three packages. It will not finish in one
|
|
|
|
|
# invocation, so resuming has to be correct rather than approximately
|
|
|
|
|
# correct.
|
|
|
|
|
#
|
|
|
|
|
# --nodeps for the same reason the per-package install uses it: a chroot
|
|
|
|
|
# halfway through stage 2 is a mixed population, some packages declaring
|
|
|
|
|
# versioned soname dependencies and some declaring names. -U is fed the
|
|
|
|
|
# files directly, so --nodeps installs exactly these and not a resolved
|
|
|
|
|
# closure -- correct here, since stage 1 already supplied the closure just
|
|
|
|
|
# above.
|
|
|
|
|
shopt -s nullglob
|
|
|
|
|
local back=("$REPO2"/*.pkg.tar.*)
|
|
|
|
|
shopt -u nullglob
|
|
|
|
|
if [ "${#back[@]}" -gt 0 ]; then
|
|
|
|
|
sudo pacman --root "$ROOT" --config "$CONF" --cachedir "$CACHE" \
|
|
|
|
|
--noconfirm --nodeps -U "${back[@]}" \
|
|
|
|
|
> "$WORK/stage2-restore.txt" 2>&1 \
|
|
|
|
|
|| { tail -20 "$WORK/stage2-restore.txt" >&2; die "restoring stage-2 output failed"; }
|
|
|
|
|
printf ' %s stage-2 package(s) restored\n' "${#back[@]}"
|
|
|
|
|
fi
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
configure_chroot() {
|
|
|
|
|
log "Configuring the chroot"
|
|
|
|
|
# CARCH and CHOST, for the same reason they had to be set on the host --
|
|
|
|
|
# except here the wrong value arrives from OUR OWN pacman package, which
|
|
|
|
|
# ships Arch's /etc/makepkg.conf verbatim:
|
|
|
|
|
#
|
|
|
|
|
# CARCH="x86_64"
|
|
|
|
|
# CHOST="x86_64-pc-linux-gnu"
|
|
|
|
|
#
|
|
|
|
|
# A stage-2 build with those would configure every source for x86_64 on an
|
|
|
|
|
# s390x machine. CHOST must be the canonical triplet, not the Debian one:
|
|
|
|
|
# config.sub turns s390x-linux-gnu into s390x-ibm-linux-gnu and GCC builds
|
|
|
|
|
# its tree under the canonical name, which is what broke gcc's own
|
|
|
|
|
# packaging on the host.
|
|
|
|
|
sudo sed -i 's|^CARCH=.*|CARCH="s390x"|; s|^CHOST=.*|CHOST="s390x-ibm-linux-gnu"|' \
|
|
|
|
|
"$ROOT/etc/makepkg.conf"
|
|
|
|
|
sudo sed -i "s|^#\?MAKEFLAGS=.*|MAKEFLAGS=\"-j$(nproc)\"|" "$ROOT/etc/makepkg.conf"
|
|
|
|
|
# !debug and !lto, matching what stage 1 used. Arch's defaults enable both;
|
|
|
|
|
# turning them on here would change what is being compared between the two
|
|
|
|
|
# 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"
|
[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
2026-08-19 20:19:37 -04:00
|
|
|
# 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 ---
|
[FIX] s390x: say which machine this port targets
zlib stopped on '__builtin_s390_vec_unpackl' requires '-mvx', after its own
configure had detected vector support and defined -DHAVE_S390X_VX. Both halves
were right, so it was worth measuring rather than patching:
host gcc (Ubuntu) --with-arch=z13 --with-tune=z16 default -march=arch11
our gcc nothing default -march=arch5
arch5 is z900, from 2000. Arch's PKGBUILD names no s390x baseline because Arch
has no s390x, so ours fell back to the oldest machine imaginable while zlib went
on detecting a CPU the compiler had been told to forget. Every distribution
picks one of these; this port had never said which, and silence chose 2000.
z13 is what Ubuntu s390x already requires, so nothing that runs today stops.
Set in two places: makepkg.conf, how this distribution is compiled, and gcc's
--with-arch, what the compiler we ship assumes with no flags at all.
--- FR ---
zlib s'est arrêté sur '__builtin_s390_vec_unpackl' requires '-mvx', après que
son propre configure avait détecté le support vectoriel et défini
-DHAVE_S390X_VX. Les deux moitiés avaient raison : il fallait mesurer, pas
rustiner.
gcc de l'hôte --with-arch=z13 --with-tune=z16 défaut -march=arch11
notre gcc rien défaut -march=arch5
arch5, c'est z900, de l'an 2000. Le PKGBUILD d'Arch ne nomme aucune base s390x
puisque Arch n'a pas de s390x : le nôtre retombait sur la machine la plus
ancienne imaginable pendant que zlib détectait un processeur qu'on avait dit au
compilateur d'oublier. Toute distribution en choisit une ; ce portage ne l'avait
jamais dit, et le silence a choisi 2000.
z13 est ce qu'Ubuntu s390x exige déjà : rien qui tourne aujourd'hui ne cesse de
tourner. Posé aux deux endroits : makepkg.conf, comment cette distribution est
compilée, et le --with-arch de gcc, ce que suppose le compilateur qu'on livre.
Assisted-by: Claude Opus 5
2026-08-21 00:23:26 -04:00
|
|
|
#
|
|
|
|
|
# THE BASELINE CPU OF THIS PORT, which was being decided by accident.
|
|
|
|
|
#
|
|
|
|
|
# zlib stopped on
|
|
|
|
|
#
|
|
|
|
|
# contrib/crc32vx/crc32_vx.c:205:10: error:
|
|
|
|
|
# '__builtin_s390_vec_unpackl' requires '-mvx'
|
|
|
|
|
#
|
|
|
|
|
# after its own configure had detected vector support and defined
|
|
|
|
|
# -DHAVE_S390X_VX. Both halves were right, which is what made it worth
|
|
|
|
|
# measuring instead of patching:
|
|
|
|
|
#
|
|
|
|
|
# host gcc (Ubuntu) --with-arch=z13 --with-tune=z16 default -march=arch11
|
|
|
|
|
# our gcc (nothing) default -march=arch5
|
|
|
|
|
#
|
|
|
|
|
# arch5 is z900, from 2000. arch11 is z13, from 2015. Arch's PKGBUILD names no
|
|
|
|
|
# s390x baseline because Arch has no s390x, so our gcc fell back to the oldest
|
|
|
|
|
# machine the port could possibly run on -- and every package using a vector
|
|
|
|
|
# intrinsic failed, while zlib's configure went on detecting a CPU the compiler
|
|
|
|
|
# had been told to forget.
|
|
|
|
|
#
|
|
|
|
|
# z13 is not a guess: it is what Ubuntu s390x, the host distribution, already
|
|
|
|
|
# requires, so nothing that runs here today stops running. Every distribution
|
|
|
|
|
# picks one of these; this port had simply never said which, and silence chose
|
|
|
|
|
# the year 2000.
|
|
|
|
|
#
|
|
|
|
|
# TWO PLACES, because they answer different questions. Here is how this
|
|
|
|
|
# distribution is COMPILED. patches/pkgbuild/gcc.sh passes --with-arch to the
|
|
|
|
|
# compiler we SHIP, so a build done later on the target assumes the same
|
|
|
|
|
# machine. Setting only this one leaves a gcc that quietly reverts to arch5.
|
|
|
|
|
CFLAGS="-march=z13 -mtune=z16 -O2 -pipe -fno-plt -fexceptions"
|
|
|
|
|
CXXFLAGS="$CFLAGS -Wp,-D_GLIBCXX_ASSERTIONS"
|
|
|
|
|
LDFLAGS="-Wl,-O1 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now"
|
[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
2026-08-19 20:19:37 -04:00
|
|
|
LTOFLAGS=""
|
|
|
|
|
RUSTFLAGS=""
|
|
|
|
|
DEBUG_CFLAGS=""
|
|
|
|
|
DEBUG_CXXFLAGS=""
|
|
|
|
|
EOC
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
sudo grep -E '^(CARCH|CHOST|MAKEFLAGS|OPTIONS)=' "$ROOT/etc/makepkg.conf" | sed 's/^/ /'
|
[FIX] s390x: say which machine this port targets
zlib stopped on '__builtin_s390_vec_unpackl' requires '-mvx', after its own
configure had detected vector support and defined -DHAVE_S390X_VX. Both halves
were right, so it was worth measuring rather than patching:
host gcc (Ubuntu) --with-arch=z13 --with-tune=z16 default -march=arch11
our gcc nothing default -march=arch5
arch5 is z900, from 2000. Arch's PKGBUILD names no s390x baseline because Arch
has no s390x, so ours fell back to the oldest machine imaginable while zlib went
on detecting a CPU the compiler had been told to forget. Every distribution
picks one of these; this port had never said which, and silence chose 2000.
z13 is what Ubuntu s390x already requires, so nothing that runs today stops.
Set in two places: makepkg.conf, how this distribution is compiled, and gcc's
--with-arch, what the compiler we ship assumes with no flags at all.
--- FR ---
zlib s'est arrêté sur '__builtin_s390_vec_unpackl' requires '-mvx', après que
son propre configure avait détecté le support vectoriel et défini
-DHAVE_S390X_VX. Les deux moitiés avaient raison : il fallait mesurer, pas
rustiner.
gcc de l'hôte --with-arch=z13 --with-tune=z16 défaut -march=arch11
notre gcc rien défaut -march=arch5
arch5, c'est z900, de l'an 2000. Le PKGBUILD d'Arch ne nomme aucune base s390x
puisque Arch n'a pas de s390x : le nôtre retombait sur la machine la plus
ancienne imaginable pendant que zlib détectait un processeur qu'on avait dit au
compilateur d'oublier. Toute distribution en choisit une ; ce portage ne l'avait
jamais dit, et le silence a choisi 2000.
z13 est ce qu'Ubuntu s390x exige déjà : rien qui tourne aujourd'hui ne cesse de
tourner. Posé aux deux endroits : makepkg.conf, comment cette distribution est
compilée, et le --with-arch de gcc, ce que suppose le compilateur qu'on livre.
Assisted-by: Claude Opus 5
2026-08-21 00:23:26 -04:00
|
|
|
printf ' baseline: -march=z13 -mtune=z16 (host Ubuntu uses the same)\n'
|
[FIX] stage 2: produce its first package
Stage 2 could build and could not deliver. Four obstacles, all in the tail of
package(), after a compile that had already succeeded.
Our meson ships arch-meson and it passes --auto-features enabled, so each of
the nine documentation tools this chroot lacks was a hard error, not a skipped
feature. A wrapper appends --auto-features auto; meson honours the last one.
Building those tools was the alternative and it is not close -- doxygen alone
wants clang, fmt, spdlog, llvm-libs.
Then bsdtar would not start: stage-1 libxml2 asks for the host's
libicuuc.so.76, our icu ships 78, and bsdtar is what writes the package. The
package that would fix it was the one being built. libarchive only links
libxml2 for xar, which nothing here reads, so stage 1 drops it.
Verified: libxml2 rebuilt against libicuuc.so.78, provides libxml2.so=16-64,
zero multiarch paths.
--- FR ---
L'étage 2 savait bâtir et ne savait pas livrer. Quatre obstacles, tous dans la
queue de package(), après une compilation déjà réussie.
Notre meson livre arch-meson, qui passe --auto-features enabled : chacun des
neuf outils de documentation absents de ce chroot devenait une erreur franche
au lieu d'une option écartée. Une enveloppe ajoute --auto-features auto, meson
retenant la dernière occurrence. Bâtir ces outils était l'autre voie et l'écart
est net -- doxygen seul réclame clang, fmt, spdlog, llvm-libs.
Puis bsdtar ne démarrait plus : le libxml2 de l'étage 1 réclame le
libicuuc.so.76 de l'hôte, notre icu livre le 78, et bsdtar est ce qui écrit le
paquet. Le paquet qui corrigeait cela était celui qu'on bâtissait. libarchive
ne lie libxml2 que pour xar, que rien ici ne lit : l'étage 1 l'abandonne.
Vérifié : libxml2 rebâti sur libicuuc.so.78, fournit libxml2.so=16-64, aucun
chemin multiarch.
Assisted-by: Claude Opus 5
2026-08-20 01:12:14 -04:00
|
|
|
|
|
|
|
|
# --auto-features auto, for this chroot only.
|
|
|
|
|
#
|
|
|
|
|
# Our own meson package ships /usr/bin/arch-meson, and it passes
|
|
|
|
|
# --auto-features enabled -- correct on Arch, whose build chroot has every
|
|
|
|
|
# optional tool. Ours has none of them:
|
|
|
|
|
#
|
|
|
|
|
# doxygen xsltproc asciidoctor itstool convert fig2dev elinks
|
|
|
|
|
# ducktype yelp-build -- all ABSENT
|
|
|
|
|
#
|
|
|
|
|
# With `enabled`, each one is a hard error. libxml2 stops at
|
|
|
|
|
#
|
|
|
|
|
# libxml2/doc/meson.build:3:10: ERROR: Program 'doxygen' not found
|
|
|
|
|
#
|
|
|
|
|
# MEASURED BEFORE CHOOSING, because building them was the other option and
|
|
|
|
|
# it is not close: doxygen alone wants clang, fmt, spdlog and llvm-libs --
|
|
|
|
|
# an entire compiler infrastructure for a documentation generator. Behind
|
|
|
|
|
# the other eight stand Ruby, ImageMagick and a GNOME stack. That is
|
|
|
|
|
# several times the size of everything built so far, for man pages.
|
|
|
|
|
#
|
|
|
|
|
# `auto` is meson's own default and means "build what you can". It is the
|
|
|
|
|
# honest setting for an environment with fewer tools, not a workaround --
|
|
|
|
|
# and what it drops is visible in the artefact, which is where this port
|
|
|
|
|
# checks everything anyway.
|
|
|
|
|
#
|
|
|
|
|
# A WRAPPER, not a patched package: /usr/bin/arch-meson belongs to meson and
|
|
|
|
|
# stage 2 must not ship a modified copy of it. meson takes the LAST
|
|
|
|
|
# occurrence of an option, so appending wins while leaving every other
|
|
|
|
|
# choice arch-meson makes intact. in_chroot puts /usr/local/bin first on
|
|
|
|
|
# PATH so this is found.
|
|
|
|
|
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
|
|
|
|
|
EOW
|
|
|
|
|
sudo chmod 755 "$ROOT/usr/local/bin/arch-meson"
|
|
|
|
|
printf ' arch-meson: wrapped with --auto-features auto\n'
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
# numeric owner across, so a different uid inside would see them as
|
|
|
|
|
# somebody else's and fail to write $srcdir.
|
|
|
|
|
sudo tee -a "$ROOT/etc/passwd" > /dev/null <<EOF
|
|
|
|
|
$BUILDER:x:$BUILD_UID:$BUILD_GID::/build:/usr/bin/bash
|
|
|
|
|
EOF
|
|
|
|
|
sudo tee -a "$ROOT/etc/group" > /dev/null <<EOF
|
|
|
|
|
$BUILDER:x:$BUILD_GID:
|
|
|
|
|
EOF
|
|
|
|
|
sudo mkdir -p "$ROOT/build" "$ROOT/repo2"
|
|
|
|
|
sudo chown "$BUILD_UID:$BUILD_GID" "$ROOT/build" "$ROOT/repo2"
|
|
|
|
|
# The stage-1 repository, so makepkg's --nodeps builds can still read the
|
|
|
|
|
# packages if anything wants to, and so repo-add has somewhere to write.
|
|
|
|
|
mkdir -p "$REPO2"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mount_chroot() {
|
|
|
|
|
log "Mounting"
|
|
|
|
|
# /dev/pts is not optional: without it any build step that opens a pty --
|
|
|
|
|
# and gcc's testsuite driver does -- fails in a way that names the pty and
|
|
|
|
|
# not the missing mount.
|
|
|
|
|
for m in proc sys dev dev/pts; do
|
|
|
|
|
sudo mkdir -p "$ROOT/$m"
|
|
|
|
|
done
|
|
|
|
|
mountpoint -q "$ROOT/proc" || sudo mount -t proc proc "$ROOT/proc"
|
|
|
|
|
mountpoint -q "$ROOT/sys" || sudo mount -t sysfs sys "$ROOT/sys"
|
|
|
|
|
mountpoint -q "$ROOT/dev" || sudo mount --bind /dev "$ROOT/dev"
|
|
|
|
|
mountpoint -q "$ROOT/dev/pts" || sudo mount -t devpts devpts "$ROOT/dev/pts"
|
|
|
|
|
# Sources and PKGBUILDs, already fetched by stage 1. Bind-mounting them
|
|
|
|
|
# means the chroot needs no network at all, which is worth having: this
|
|
|
|
|
# host cannot reach dev.gnupg.org, and a build that silently re-fetches
|
|
|
|
|
# would be a different build.
|
|
|
|
|
mountpoint -q "$ROOT/build" || sudo mount --bind "$WORK/pkg" "$ROOT/build"
|
|
|
|
|
mountpoint -q "$ROOT/repo2" || sudo mount --bind "$REPO2" "$ROOT/repo2"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
umount_chroot() {
|
|
|
|
|
for m in repo2 build dev/pts dev sys proc; do
|
|
|
|
|
mountpoint -q "$ROOT/$m" && sudo umount -l "$ROOT/$m"
|
|
|
|
|
done
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# in_chroot <command...> -- run as the builder, with a sane environment.
|
|
|
|
|
in_chroot() {
|
|
|
|
|
sudo chroot --userspec="$BUILD_UID:$BUILD_GID" "$ROOT" \
|
|
|
|
|
/usr/bin/env -i \
|
[FIX] stage 2: produce its first package
Stage 2 could build and could not deliver. Four obstacles, all in the tail of
package(), after a compile that had already succeeded.
Our meson ships arch-meson and it passes --auto-features enabled, so each of
the nine documentation tools this chroot lacks was a hard error, not a skipped
feature. A wrapper appends --auto-features auto; meson honours the last one.
Building those tools was the alternative and it is not close -- doxygen alone
wants clang, fmt, spdlog, llvm-libs.
Then bsdtar would not start: stage-1 libxml2 asks for the host's
libicuuc.so.76, our icu ships 78, and bsdtar is what writes the package. The
package that would fix it was the one being built. libarchive only links
libxml2 for xar, which nothing here reads, so stage 1 drops it.
Verified: libxml2 rebuilt against libicuuc.so.78, provides libxml2.so=16-64,
zero multiarch paths.
--- FR ---
L'étage 2 savait bâtir et ne savait pas livrer. Quatre obstacles, tous dans la
queue de package(), après une compilation déjà réussie.
Notre meson livre arch-meson, qui passe --auto-features enabled : chacun des
neuf outils de documentation absents de ce chroot devenait une erreur franche
au lieu d'une option écartée. Une enveloppe ajoute --auto-features auto, meson
retenant la dernière occurrence. Bâtir ces outils était l'autre voie et l'écart
est net -- doxygen seul réclame clang, fmt, spdlog, llvm-libs.
Puis bsdtar ne démarrait plus : le libxml2 de l'étage 1 réclame le
libicuuc.so.76 de l'hôte, notre icu livre le 78, et bsdtar est ce qui écrit le
paquet. Le paquet qui corrigeait cela était celui qu'on bâtissait. libarchive
ne lie libxml2 que pour xar, que rien ici ne lit : l'étage 1 l'abandonne.
Vérifié : libxml2 rebâti sur libicuuc.so.78, fournit libxml2.so=16-64, aucun
chemin multiarch.
Assisted-by: Claude Opus 5
2026-08-20 01:12:14 -04:00
|
|
|
HOME=/build PATH=/usr/local/bin:/usr/bin \
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
LC_ALL=C.UTF-8 \
|
|
|
|
|
/usr/bin/bash -lc "$*"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
smoke_test() {
|
|
|
|
|
log "Smoke test: does the chroot build anything at all?"
|
|
|
|
|
# NO PIPELINES IN THESE CHECKS. The first version ran `makeinfo --version |
|
|
|
|
|
# head -1`, and $? came from head, so a perl that could not start was
|
|
|
|
|
# reported as ok with its own error message as the version string. Same
|
|
|
|
|
# shape as the `if build_package` bug that once reported "51 built, 0
|
|
|
|
|
# failed" while four packages had failed. Each check runs one command and
|
|
|
|
|
# its status is the command's.
|
|
|
|
|
# HARD versus KNOWN-DRIFT, because they mean different things. A hard
|
|
|
|
|
# check failing means the chroot cannot build and stage 2 must not start.
|
|
|
|
|
# A drift check failing means a stage-1 package carries a host version
|
|
|
|
|
# mismatch that STAGE 2 ITSELF repairs, by rebuilding that package before
|
|
|
|
|
# the ones that need it. Treating the second as fatal would refuse to run
|
|
|
|
|
# the very thing that fixes it.
|
|
|
|
|
local drift="makeinfo"
|
|
|
|
|
local ok=0 fail=0 noted=0
|
|
|
|
|
while read -r desc cmd; do
|
|
|
|
|
[ -n "$desc" ] || continue
|
|
|
|
|
local out rc
|
|
|
|
|
out=$(in_chroot "$cmd" 2>&1); rc=$?
|
|
|
|
|
if [ "$rc" -eq 0 ]; then
|
|
|
|
|
printf ' ok %-12s %s\n' "$desc" "${out%%$'\n'*}"; ok=$((ok+1))
|
|
|
|
|
elif [[ " $drift " == *" $desc "* ]]; then
|
|
|
|
|
printf ' note %-12s %s\n' "$desc" "${out%%$'\n'*}"; noted=$((noted+1))
|
|
|
|
|
else
|
|
|
|
|
printf ' FAIL %-12s rc=%s %s\n' "$desc" "$rc" "${out%%$'\n'*}"; fail=$((fail+1))
|
|
|
|
|
fi
|
|
|
|
|
done <<'CHECKS'
|
|
|
|
|
bash bash --version
|
|
|
|
|
gcc gcc --version
|
|
|
|
|
ld ld --version
|
|
|
|
|
make make --version
|
|
|
|
|
makepkg makepkg --version
|
|
|
|
|
fakeroot fakeroot -- /usr/bin/id -u
|
|
|
|
|
bison bison --version
|
|
|
|
|
flex flex --version
|
|
|
|
|
perl perl -e 'print "perl $]\n"'
|
|
|
|
|
makeinfo makeinfo --version
|
|
|
|
|
compile cd /build && mkdir -p .stage2-smoke && cd .stage2-smoke && printf 'int main(void){return 0;}' > t.c && gcc t.c -o t && ./t && echo compiled-and-ran
|
|
|
|
|
CHECKS
|
|
|
|
|
printf '\n %s ok, %s failed, %s known drift\n' "$ok" "$fail" "$noted"
|
|
|
|
|
if [ "$noted" -gt 0 ]; then
|
|
|
|
|
cat <<'NOTE'
|
|
|
|
|
|
|
|
|
|
makeinfo is the one expected failure, and it is what stage 2 exists for:
|
|
|
|
|
texinfo was built against the HOST's perl 5.40 and our perl package is 5.42,
|
|
|
|
|
so its XS module refuses to load ("Perl API version ... does not match").
|
|
|
|
|
Rebuilding texinfo inside this chroot fixes it -- which is why texinfo has to
|
|
|
|
|
come EARLY in the rebuild order, before gcc, glibc and binutils, all of which
|
|
|
|
|
call makeinfo.
|
|
|
|
|
NOTE
|
|
|
|
|
fi
|
|
|
|
|
[ "$fail" -eq 0 ]
|
|
|
|
|
}
|
|
|
|
|
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
|
|
|
|
|
# Hooks that must NOT run in stage 2.
|
|
|
|
|
#
|
|
|
|
|
# Most stage-1 hooks are still right inside the chroot: the architectural ones
|
|
|
|
|
# (systemd's EFI, glibc's SFrame, gcc's multilib) describe s390x, and the
|
|
|
|
|
# host-absence ones (pam's fop, gnutls's leancrypto, krb5's ss) describe a
|
|
|
|
|
# build environment that has not changed. A few are no-ops here and harmless
|
|
|
|
|
# -- the libdir hooks insert a value meson would already have chosen.
|
|
|
|
|
#
|
|
|
|
|
# This list is for the ones that would actively BREAK. libgcrypt.sh extracts
|
|
|
|
|
# our libgpg-error into $WORK/stage1-prefix and injects that absolute host path
|
|
|
|
|
# into build(); the path does not exist in the chroot. It is also unnecessary
|
|
|
|
|
# there, because the chroot HAS our libgpg-error 1.61 installed, so
|
|
|
|
|
# /usr/bin/gpgrt-config is already the new one. That is stage 2 working as
|
|
|
|
|
# intended: the reason for the hook disappears.
|
|
|
|
|
# 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.
|
[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
2026-08-19 20:19:37 -04:00
|
|
|
# meson.sh joins them: it moves a wheel out of /usr/local, which only the
|
|
|
|
|
# host's Debian-patched python puts there.
|
[FIX] stage 2: produce its first package
Stage 2 could build and could not deliver. Four obstacles, all in the tail of
package(), after a compile that had already succeeded.
Our meson ships arch-meson and it passes --auto-features enabled, so each of
the nine documentation tools this chroot lacks was a hard error, not a skipped
feature. A wrapper appends --auto-features auto; meson honours the last one.
Building those tools was the alternative and it is not close -- doxygen alone
wants clang, fmt, spdlog, llvm-libs.
Then bsdtar would not start: stage-1 libxml2 asks for the host's
libicuuc.so.76, our icu ships 78, and bsdtar is what writes the package. The
package that would fix it was the one being built. libarchive only links
libxml2 for xar, which nothing here reads, so stage 1 drops it.
Verified: libxml2 rebuilt against libicuuc.so.78, provides libxml2.so=16-64,
zero multiarch paths.
--- FR ---
L'étage 2 savait bâtir et ne savait pas livrer. Quatre obstacles, tous dans la
queue de package(), après une compilation déjà réussie.
Notre meson livre arch-meson, qui passe --auto-features enabled : chacun des
neuf outils de documentation absents de ce chroot devenait une erreur franche
au lieu d'une option écartée. Une enveloppe ajoute --auto-features auto, meson
retenant la dernière occurrence. Bâtir ces outils était l'autre voie et l'écart
est net -- doxygen seul réclame clang, fmt, spdlog, llvm-libs.
Puis bsdtar ne démarrait plus : le libxml2 de l'étage 1 réclame le
libicuuc.so.76 de l'hôte, notre icu livre le 78, et bsdtar est ce qui écrit le
paquet. Le paquet qui corrigeait cela était celui qu'on bâtissait. libarchive
ne lie libxml2 que pour xar, que rien ici ne lit : l'étage 1 l'abandonne.
Vérifié : libxml2 rebâti sur libicuuc.so.78, fournit libxml2.so=16-64, aucun
chemin multiarch.
Assisted-by: Claude Opus 5
2026-08-20 01:12:14 -04:00
|
|
|
# libarchive: stage 1 drops xar to break the bsdtar -> libxml2 -> libicuuc.so.76
|
|
|
|
|
# cycle. In here the versions agree, so build it as Arch does.
|
[FIX] stage 2: the build tools the host was providing silently
glibc rebuilt inside the chroot; binutils died on `make info-recursive`, and
makeinfo said why: its XS module was compiled against the host's perl 5.40 and
our perl is 5.42. Stage-1 texinfo cannot run in there. Everything that builds
.info documentation needs it, and it sat near the end of the list because that
is where its own dependencies are, so stage 2 hoists it.
linux-api-headers stopped earlier on `rsync: command not found` -- the kernel's
headers_install runs it. apt had put it there and a build that finds its tool
says nothing, so stage 1 never mentioned it. Its man pages come from Markdown
the git tag does not pre-render, hence --disable-md2man.
An inventory of the 110 apt packages against the chroot found 84 more such
binaries. Most are documentation; the rest wait until something needs them.
--- FR ---
glibc a été rebâti dans le chroot ; binutils est mort sur `make info-recursive`,
et makeinfo en donne la raison : son module XS a été compilé contre le perl 5.40
de l'hôte, or le nôtre est en 5.42. Le texinfo de l'étage 1 ne peut pas tourner
là-dedans. Tout ce qui bâtit de la documentation .info en dépend, et il figurait
en fin de liste, là où sont ses propres dépendances : l'étage 2 le remonte.
linux-api-headers s'était arrêté avant sur `rsync: command not found` — le
headers_install du noyau l'appelle. apt l'avait posé, et une construction qui
trouve son outil n'en dit rien : l'étage 1 ne l'a jamais mentionné. Ses pages de
manuel viennent d'un Markdown que l'étiquette git ne rend pas, d'où
--disable-md2man.
Un inventaire des 110 paquets apt face au chroot a trouvé 84 autres binaires du
même genre. La plupart sont de la documentation ; le reste attendra qu'un paquet
les réclame.
Assisted-by: Claude Opus 5
2026-08-20 01:34:41 -04:00
|
|
|
# Hoisted to the front of the stage-2 order.
|
|
|
|
|
#
|
|
|
|
|
# Stage 1's order is the real dependency closure and stage 2 keeps it -- but
|
|
|
|
|
# stage 1 had the HOST for its build tools, and a few of those tools are
|
|
|
|
|
# themselves in the list, sitting wherever their runtime dependencies put them.
|
|
|
|
|
# In the chroot the stage-1 copy is what is available, and for these it does
|
|
|
|
|
# not work:
|
|
|
|
|
#
|
|
|
|
|
# texinfo -- binutils died on `make info-recursive`, and makeinfo says why:
|
|
|
|
|
#
|
|
|
|
|
# Perl API version v5.40.0 of Texinfo::TreeElement does not match v5.42.0
|
|
|
|
|
#
|
|
|
|
|
# Its XS module was compiled against the HOST's perl 5.40; our perl package
|
|
|
|
|
# is 5.42. Every package that builds .info documentation -- binutils, gcc,
|
|
|
|
|
# glibc, coreutils, gettext, m4 -- needs makeinfo, and texinfo sits near the
|
|
|
|
|
# end of the list because that is where its own dependencies are. Rebuilding
|
|
|
|
|
# it in here against our perl fixes it, which is the entire point of stage 2;
|
|
|
|
|
# it just has to happen first.
|
|
|
|
|
#
|
|
|
|
|
# Duplicates need no handling: these names appear again later in
|
|
|
|
|
# STAGE1_PACKAGES, and rebuild() skips anything already in stage2.state. The
|
|
|
|
|
# list is prepended rather than reordered, so stage 1's order stays the single
|
|
|
|
|
# statement of the closure.
|
[ADD] the seven tools stage 2 asked for by name
Six built on the host. libxslt will not: its configure demands libxml2 2.15.1
and Ubuntu ships 2.14.5. Ours is 2.15.3 and exists only inside the stage-2
chroot, so that chroot is the only place libxslt can come from -- built BY
stage 2 rather than installed into it, and hoisted so it precedes shadow, which
died on `xsltproc is missing`.
That is a shape worth naming: a package the host cannot build because the host
is behind. Not contamination and not a closure gap -- the wrong machine.
Three more tools were asked for and refused: po4a for fakeroot, a2x for
ca-certificates, asciidoctor for cryptsetup. Perl, Python and Ruby respectively,
each to render a man page. Hooks instead.
--- FR ---
Six se bâtissent sur l'hôte. Pas libxslt : son configure exige libxml2 2.15.1 et
Ubuntu livre 2.14.5. Le nôtre est en 2.15.3 et n'existe que dans le chroot de
l'étage 2 : ce chroot est donc le seul endroit d'où libxslt puisse venir — bâti
PAR l'étage 2 plutôt qu'installé dedans, et hissé pour précéder shadow, mort sur
`xsltproc is missing`.
La forme mérite un nom : un paquet que l'hôte ne peut pas bâtir parce que l'hôte
est en retard. Ni contamination ni trou de fermeture — la mauvaise machine.
Trois autres outils réclamés et refusés : po4a pour fakeroot, a2x pour
ca-certificates, asciidoctor pour cryptsetup. Perl, Python et Ruby
respectivement, chacun pour rendre une page de manuel. Des hooks à la place.
Assisted-by: Claude Opus 5
2026-08-21 00:29:40 -04:00
|
|
|
# libxslt -- shadow died on `configure: error: xsltproc is missing.` and
|
|
|
|
|
# libxslt cannot be built on the host: its configure demands libxml2 2.15.1
|
|
|
|
|
# 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)
|
[FIX] stage 2: the build tools the host was providing silently
glibc rebuilt inside the chroot; binutils died on `make info-recursive`, and
makeinfo said why: its XS module was compiled against the host's perl 5.40 and
our perl is 5.42. Stage-1 texinfo cannot run in there. Everything that builds
.info documentation needs it, and it sat near the end of the list because that
is where its own dependencies are, so stage 2 hoists it.
linux-api-headers stopped earlier on `rsync: command not found` -- the kernel's
headers_install runs it. apt had put it there and a build that finds its tool
says nothing, so stage 1 never mentioned it. Its man pages come from Markdown
the git tag does not pre-render, hence --disable-md2man.
An inventory of the 110 apt packages against the chroot found 84 more such
binaries. Most are documentation; the rest wait until something needs them.
--- FR ---
glibc a été rebâti dans le chroot ; binutils est mort sur `make info-recursive`,
et makeinfo en donne la raison : son module XS a été compilé contre le perl 5.40
de l'hôte, or le nôtre est en 5.42. Le texinfo de l'étage 1 ne peut pas tourner
là-dedans. Tout ce qui bâtit de la documentation .info en dépend, et il figurait
en fin de liste, là où sont ses propres dépendances : l'étage 2 le remonte.
linux-api-headers s'était arrêté avant sur `rsync: command not found` — le
headers_install du noyau l'appelle. apt l'avait posé, et une construction qui
trouve son outil n'en dit rien : l'étage 1 ne l'a jamais mentionné. Ses pages de
manuel viennent d'un Markdown que l'étiquette git ne rend pas, d'où
--disable-md2man.
Un inventaire des 110 paquets apt face au chroot a trouvé 84 autres binaires du
même genre. La plupart sont de la documentation ; le reste attendra qu'un paquet
les réclame.
Assisted-by: Claude Opus 5
2026-08-20 01:34:41 -04:00
|
|
|
|
[FIX] stage 2: produce its first package
Stage 2 could build and could not deliver. Four obstacles, all in the tail of
package(), after a compile that had already succeeded.
Our meson ships arch-meson and it passes --auto-features enabled, so each of
the nine documentation tools this chroot lacks was a hard error, not a skipped
feature. A wrapper appends --auto-features auto; meson honours the last one.
Building those tools was the alternative and it is not close -- doxygen alone
wants clang, fmt, spdlog, llvm-libs.
Then bsdtar would not start: stage-1 libxml2 asks for the host's
libicuuc.so.76, our icu ships 78, and bsdtar is what writes the package. The
package that would fix it was the one being built. libarchive only links
libxml2 for xar, which nothing here reads, so stage 1 drops it.
Verified: libxml2 rebuilt against libicuuc.so.78, provides libxml2.so=16-64,
zero multiarch paths.
--- FR ---
L'étage 2 savait bâtir et ne savait pas livrer. Quatre obstacles, tous dans la
queue de package(), après une compilation déjà réussie.
Notre meson livre arch-meson, qui passe --auto-features enabled : chacun des
neuf outils de documentation absents de ce chroot devenait une erreur franche
au lieu d'une option écartée. Une enveloppe ajoute --auto-features auto, meson
retenant la dernière occurrence. Bâtir ces outils était l'autre voie et l'écart
est net -- doxygen seul réclame clang, fmt, spdlog, llvm-libs.
Puis bsdtar ne démarrait plus : le libxml2 de l'étage 1 réclame le
libicuuc.so.76 de l'hôte, notre icu livre le 78, et bsdtar est ce qui écrit le
paquet. Le paquet qui corrigeait cela était celui qu'on bâtissait. libarchive
ne lie libxml2 que pour xar, que rien ici ne lit : l'étage 1 l'abandonne.
Vérifié : libxml2 rebâti sur libicuuc.so.78, fournit libxml2.so=16-64, aucun
chemin multiarch.
Assisted-by: Claude Opus 5
2026-08-20 01:12:14 -04:00
|
|
|
STAGE2_SKIP_HOOKS=(libgcrypt git meson libarchive)
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
|
|
|
|
|
# Packages whose sources must be extracted on the HOST, because the chroot
|
|
|
|
|
# cannot extract anything until they are rebuilt.
|
|
|
|
|
#
|
|
|
|
|
# THE CYCLE. makepkg extracts with bsdtar. bsdtar is libarchive, libarchive
|
|
|
|
|
# links libxml2 for xar support, and the stage-1 libxml2 was linked against the
|
|
|
|
|
# HOST's ICU 76 while our icu package ships ICU 78:
|
|
|
|
|
#
|
|
|
|
|
# bsdtar: error while loading shared libraries: libicuuc.so.76
|
|
|
|
|
#
|
|
|
|
|
# So nothing unpacks in the chroot until libxml2 is rebuilt, and libxml2 cannot
|
|
|
|
|
# unpack in the chroot. One of the nine soname drifts, turned into a bootstrap
|
|
|
|
|
# cycle by the one tool that has to work first.
|
|
|
|
|
#
|
|
|
|
|
# Extraction is not compilation, so doing it outside is less of an impurity
|
|
|
|
|
# than it looks -- the host's bsdtar unpacks a tarball byte for byte. What DOES
|
|
|
|
|
# leak is prepare(), which `makepkg -o` also runs: mostly patching, but where
|
|
|
|
|
# it runs autoreconf the generated configure carries the host's autotools.
|
|
|
|
|
# That is why this is a LIST and not the default. Once libxml2 is rebuilt,
|
|
|
|
|
# bsdtar works and everything after it extracts in the chroot.
|
|
|
|
|
STAGE2_HOST_EXTRACT=(libxml2)
|
|
|
|
|
|
|
|
|
|
host_extract() {
|
|
|
|
|
local n="$1" h
|
|
|
|
|
for h in "${STAGE2_HOST_EXTRACT[@]}"; do [ "$n" = "$h" ] && return 0; done
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
skip_hook() {
|
|
|
|
|
local n="$1" h
|
|
|
|
|
for h in "${STAGE2_SKIP_HOOKS[@]}"; do [ "$n" = "$h" ] && return 0; done
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# stage2_build <name> -- 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
|
|
|
|
|
# PKGBUILD, and /build is the same directory seen from both sides, so the
|
|
|
|
|
# patched file is what makepkg reads. Nothing needs to be duplicated inside.
|
|
|
|
|
stage2_build() {
|
|
|
|
|
local name="$1"
|
|
|
|
|
local dir="$WORK/pkg/$name"
|
|
|
|
|
[ -d "$dir" ] || { echo " no checkout for $name" >&2; return 1; }
|
|
|
|
|
|
|
|
|
|
( 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" ) || {
|
|
|
|
|
echo " hook failed" >&2; return 1; }
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
( cd "$dir" && rm -f ./*.pkg.tar.* ) || true
|
|
|
|
|
# NO --nocheck. Stage 1 skipped the test suites because they ran against
|
|
|
|
|
# the host's libraries and their verdict said nothing about the port. Here
|
|
|
|
|
# they test what was actually built, which is the whole point of stage 2.
|
|
|
|
|
local mkflags="--nodeps --ignorearch --skippgpcheck --skipchecksums"
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
# EL_NOCHECK=1 for the FIRST pass over the list, and only that.
|
|
|
|
|
#
|
|
|
|
|
# Stage 1 skipped every test suite because it ran against the host's
|
|
|
|
|
# libraries, so its verdict said nothing about the port. In here a suite
|
|
|
|
|
# tests what was actually built, which is worth having -- but not on the
|
|
|
|
|
# pass whose job is to find out whether a hundred and thirty-three packages
|
|
|
|
|
# can be rebuilt at all. glibc's suite alone is longer than most of the
|
|
|
|
|
# builds around it, and a suite is the likeliest place in a build to wait
|
|
|
|
|
# forever on a tty or a socket.
|
|
|
|
|
#
|
|
|
|
|
# So: one pass to get a complete stage-2 repository, then the suites, then
|
|
|
|
|
# stage 3 -- where they run on a self-hosted toolchain and their verdict is
|
|
|
|
|
# about the port rather than about the bootstrap. Off by default is wrong
|
|
|
|
|
# here; this must be asked for.
|
|
|
|
|
[ "${EL_NOCHECK:-0}" = "1" ] && mkflags="$mkflags --nocheck"
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
if host_extract "$name"; then
|
|
|
|
|
echo " extracting on the host (chroot bsdtar not usable yet)"
|
|
|
|
|
( cd "$dir" && LC_ALL=C.UTF-8 makepkg $mkflags -o -C -f ) || return 1
|
|
|
|
|
# -e: build in the tree already there. -C would wipe it again.
|
|
|
|
|
in_chroot "cd /build/$name && makepkg $mkflags -e -f" || return 1
|
|
|
|
|
else
|
|
|
|
|
in_chroot "cd /build/$name && makepkg $mkflags -C -f" || return 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# An exit code is not proof. Only the artefact is.
|
|
|
|
|
local produced=()
|
|
|
|
|
shopt -s nullglob; produced=("$dir"/*.pkg.tar.*); shopt -u nullglob
|
|
|
|
|
[ "${#produced[@]}" -gt 0 ] || { echo " no package produced" >&2; return 1; }
|
|
|
|
|
|
|
|
|
|
cp -f "${produced[@]}" "$REPO2/" || return 1
|
|
|
|
|
local names=() f
|
|
|
|
|
for f in "${produced[@]}"; do names+=("$(basename "$f")"); done
|
|
|
|
|
( cd "$REPO2" && repo-add core.db.tar.gz "${names[@]}" ) > /dev/null || return 1
|
|
|
|
|
|
|
|
|
|
# Install into the chroot so the NEXT package builds against it. With the
|
|
|
|
|
# host's pacman and --root, because the chroot's own pacman does not start
|
|
|
|
|
# until stage 2 has rebuilt it.
|
[FIX] stage 2: produce its first package
Stage 2 could build and could not deliver. Four obstacles, all in the tail of
package(), after a compile that had already succeeded.
Our meson ships arch-meson and it passes --auto-features enabled, so each of
the nine documentation tools this chroot lacks was a hard error, not a skipped
feature. A wrapper appends --auto-features auto; meson honours the last one.
Building those tools was the alternative and it is not close -- doxygen alone
wants clang, fmt, spdlog, llvm-libs.
Then bsdtar would not start: stage-1 libxml2 asks for the host's
libicuuc.so.76, our icu ships 78, and bsdtar is what writes the package. The
package that would fix it was the one being built. libarchive only links
libxml2 for xar, which nothing here reads, so stage 1 drops it.
Verified: libxml2 rebuilt against libicuuc.so.78, provides libxml2.so=16-64,
zero multiarch paths.
--- FR ---
L'étage 2 savait bâtir et ne savait pas livrer. Quatre obstacles, tous dans la
queue de package(), après une compilation déjà réussie.
Notre meson livre arch-meson, qui passe --auto-features enabled : chacun des
neuf outils de documentation absents de ce chroot devenait une erreur franche
au lieu d'une option écartée. Une enveloppe ajoute --auto-features auto, meson
retenant la dernière occurrence. Bâtir ces outils était l'autre voie et l'écart
est net -- doxygen seul réclame clang, fmt, spdlog, llvm-libs.
Puis bsdtar ne démarrait plus : le libxml2 de l'étage 1 réclame le
libicuuc.so.76 de l'hôte, notre icu livre le 78, et bsdtar est ce qui écrit le
paquet. Le paquet qui corrigeait cela était celui qu'on bâtissait. libarchive
ne lie libxml2 que pour xar, que rien ici ne lit : l'étage 1 l'abandonne.
Vérifié : libxml2 rebâti sur libicuuc.so.78, fournit libxml2.so=16-64, aucun
chemin multiarch.
Assisted-by: Claude Opus 5
2026-08-20 01:12:14 -04:00
|
|
|
#
|
|
|
|
|
# --nodeps, and ONLY here. The first rebuilt package would not install:
|
|
|
|
|
#
|
|
|
|
|
# unable to satisfy dependency 'libicuuc.so=78-64' required by libxml2
|
|
|
|
|
#
|
|
|
|
|
# Both halves of that are correct. This chroot runs makepkg's soname scan,
|
|
|
|
|
# so a package built in here declares versioned soname dependencies the way
|
|
|
|
|
# Arch's really do -- which is the faithful metadata stage 2 exists to
|
|
|
|
|
# produce. The stage-1 packages around it were built on the host under
|
|
|
|
|
# !autodeps, so our icu ships no `provides = libicuuc.so=78-64` to match.
|
|
|
|
|
#
|
|
|
|
|
# For the length of stage 2 the chroot is therefore a MIXED POPULATION:
|
|
|
|
|
# some packages describe their dependencies in Arch's terms and some in
|
|
|
|
|
# names only. No resolver can satisfy that, and none should be asked to --
|
|
|
|
|
# it is a property of a bootstrap halfway through, not of the output. It
|
|
|
|
|
# dissolves on its own as the last package is rebuilt.
|
|
|
|
|
#
|
|
|
|
|
# What is NOT relaxed is the metadata in the packages: they keep their
|
|
|
|
|
# versioned depends, and test-chroot.sh's RESOLVE check runs the resolver
|
|
|
|
|
# against the finished repository with --nodeps OFF. The relaxation is one
|
|
|
|
|
# install command wide, and the check that would catch its consequences is
|
|
|
|
|
# still there.
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
sudo pacman --root "$ROOT" --config "$CONF2" --cachedir "$CACHE" \
|
[FIX] stage 2: produce its first package
Stage 2 could build and could not deliver. Four obstacles, all in the tail of
package(), after a compile that had already succeeded.
Our meson ships arch-meson and it passes --auto-features enabled, so each of
the nine documentation tools this chroot lacks was a hard error, not a skipped
feature. A wrapper appends --auto-features auto; meson honours the last one.
Building those tools was the alternative and it is not close -- doxygen alone
wants clang, fmt, spdlog, llvm-libs.
Then bsdtar would not start: stage-1 libxml2 asks for the host's
libicuuc.so.76, our icu ships 78, and bsdtar is what writes the package. The
package that would fix it was the one being built. libarchive only links
libxml2 for xar, which nothing here reads, so stage 1 drops it.
Verified: libxml2 rebuilt against libicuuc.so.78, provides libxml2.so=16-64,
zero multiarch paths.
--- FR ---
L'étage 2 savait bâtir et ne savait pas livrer. Quatre obstacles, tous dans la
queue de package(), après une compilation déjà réussie.
Notre meson livre arch-meson, qui passe --auto-features enabled : chacun des
neuf outils de documentation absents de ce chroot devenait une erreur franche
au lieu d'une option écartée. Une enveloppe ajoute --auto-features auto, meson
retenant la dernière occurrence. Bâtir ces outils était l'autre voie et l'écart
est net -- doxygen seul réclame clang, fmt, spdlog, llvm-libs.
Puis bsdtar ne démarrait plus : le libxml2 de l'étage 1 réclame le
libicuuc.so.76 de l'hôte, notre icu livre le 78, et bsdtar est ce qui écrit le
paquet. Le paquet qui corrigeait cela était celui qu'on bâtissait. libarchive
ne lie libxml2 que pour xar, que rien ici ne lit : l'étage 1 l'abandonne.
Vérifié : libxml2 rebâti sur libicuuc.so.78, fournit libxml2.so=16-64, aucun
chemin multiarch.
Assisted-by: Claude Opus 5
2026-08-20 01:12:14 -04:00
|
|
|
--noconfirm --nodeps -U "${produced[@]}" > "$WORK/stage2-inst-$name.txt" 2>&1 || {
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
tail -10 "$WORK/stage2-inst-$name.txt" >&2; return 1; }
|
|
|
|
|
printf ' installed %s package(s)\n' "${#produced[@]}"
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
|
|
|
|
|
# Clean up, but only now, and only because it worked.
|
|
|
|
|
#
|
|
|
|
|
# A hundred and thirty-three source trees plus their pkg/ staging do not fit
|
|
|
|
|
# on this disk. Filling it is not a hypothetical here: it happened once, and
|
|
|
|
|
# what it looked like was not "no space" -- it was I/O errors from unrelated
|
|
|
|
|
# virtual machines on the same host. Cheap to prevent, expensive to explain.
|
|
|
|
|
#
|
|
|
|
|
# AFTER the install, so nothing is thrown away until the package is proven
|
|
|
|
|
# to exist and to install. NOT on failure -- src/ and pkg/ are the whole
|
|
|
|
|
# evidence of what went wrong, and a build that failed is exactly the one
|
|
|
|
|
# worth looking at. makepkg -c would delete them either way.
|
|
|
|
|
#
|
|
|
|
|
# Only makepkg's own two directories, and only inside this package's
|
|
|
|
|
# checkout. The git tree, the PKGBUILD, the downloaded sources and the built
|
|
|
|
|
# package are all left alone.
|
|
|
|
|
( cd "$dir" && rm -rf src pkg ) || true
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# A pacman.conf that sees BOTH repositories: stage 2's output first, so a
|
|
|
|
|
# rebuilt package wins, and stage 1 behind it for everything not yet redone.
|
|
|
|
|
write_conf2() {
|
|
|
|
|
cat > "$CONF2" <<EOF
|
|
|
|
|
[options]
|
|
|
|
|
Architecture = s390x
|
|
|
|
|
SigLevel = Never
|
|
|
|
|
[stage2]
|
|
|
|
|
Server = file://$REPO2
|
|
|
|
|
[core]
|
|
|
|
|
Server = file://$REPO1
|
|
|
|
|
EOF
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rebuild() {
|
|
|
|
|
write_conf2
|
|
|
|
|
mkdir -p "$REPO2"
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
local ok=0 fail=0 rc=0 failed=()
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
for p in "$@"; do
|
|
|
|
|
if grep -qxF "$p" "$STATE2" 2>/dev/null; then
|
|
|
|
|
echo "== $p already rebuilt, skipping =="; continue
|
|
|
|
|
fi
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
# Per package, not once at the start. The run is long enough that the
|
|
|
|
|
# disk state at the end has nothing to do with the disk state when it
|
|
|
|
|
# was checked, and the failure mode is not local to this script.
|
|
|
|
|
space_ok || { echo "== stage 2: stopping, disk too low =="; break; }
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
log "stage 2: $p"
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
# watched, not a plain call: see bootstrap-pacman.sh. A hundred and
|
|
|
|
|
# thirty-three packages is far too many to sit in front of, and one
|
|
|
|
|
# silent build would hold the whole run.
|
|
|
|
|
if watched "$WORK/stage2-log-$p.txt" stage2_build "$p"; then
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
echo "$p" >> "$STATE2"; ok=$((ok + 1)); echo "OK $p"
|
|
|
|
|
else
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
rc=$?
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
fail=$((fail + 1)); failed+=("$p")
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
if [ "$rc" -eq 2 ]; then
|
|
|
|
|
echo "STALL $p (no output for ${EL_STALL_MIN:-45} min, killed)"
|
|
|
|
|
else
|
|
|
|
|
echo "FAIL $p (see $WORK/stage2-log-$p.txt)"
|
|
|
|
|
fi
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
tail -5 "$WORK/stage2-log-$p.txt" | sed 's/^/ /'
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
echo
|
|
|
|
|
echo "== stage 2: $ok rebuilt, $fail failed =="
|
|
|
|
|
[ "$fail" -eq 0 ] || printf ' failed: %s\n' "${failed[*]}"
|
|
|
|
|
}
|
|
|
|
|
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
main() {
|
|
|
|
|
require_space
|
|
|
|
|
[ -f "$REPO1/core.db.tar.gz" ] || die "no stage-1 repository at $REPO1"
|
|
|
|
|
trap umount_chroot EXIT
|
|
|
|
|
make_rootfs
|
|
|
|
|
configure_chroot
|
|
|
|
|
mount_chroot
|
|
|
|
|
smoke_test || die "the chroot cannot build; stage 2 stops here"
|
|
|
|
|
log "Chroot ready"
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
if [ "$#" -eq 0 ]; then
|
|
|
|
|
echo " enter it with:"
|
|
|
|
|
echo " sudo chroot --userspec=$BUILD_UID:$BUILD_GID $ROOT /usr/bin/bash -l"
|
|
|
|
|
echo " or rebuild packages:"
|
|
|
|
|
echo " bash $0 texinfo perl m4 autoconf ..."
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
echo " bash $0 --all # all ${#STAGE1_PACKAGES[@]}, in order"
|
[ADD] stage 2: the rebuild loop, and git to feed it
The loop applies each hook on the HOST -- /build is the same directory from
both sides, so makepkg in the chroot reads the patched PKGBUILD and nothing is
duplicated inside. It drops --nocheck: stage 1 skipped the test suites because
they ran against the host's libraries, and here they test what was built.
Each rebuilt package is installed into the chroot before the next one, with
the host's pacman and --root, because the chroot's own pacman will not start
until stage 2 has rebuilt it.
Two hooks must NOT run there, and both for the same satisfying reason: the
condition they work around does not exist in the chroot. libgcrypt.sh points
at a host prefix holding our libgpg-error, which the chroot has installed
properly. git.sh drops ZLIB_NG=1 because Ubuntu ships no zlib-ng headers,
while our own zlib-ng package ships them.
git is here because STAGE 2 needs it, not the repository: 51 of the 159
PKGBUILDs take their sources from git+https, and makepkg validates that clone
even under --noextract. Nothing depends on git. Its three -- perl-error,
perl-mailtools with perl-timedate, zlib-ng -- were read from the depends array
rather than from my own tool, which had reported `zsh` as a dependency of git.
It is not; the tool's regex was catching a neighbouring array.
--- FR ---
La boucle applique chaque crochet sur l'HÔTE — /build est le même répertoire
des deux côtés, donc makepkg dans le chroot lit le PKGBUILD corrigé et rien
n'est dupliqué dedans. Elle abandonne --nocheck : l'étage 1 sautait les suites
de tests parce qu'elles s'exécutaient contre les bibliothèques de l'hôte ; ici
elles éprouvent ce qui a été bâti. Chaque paquet reconstruit est installé dans
le chroot avant le suivant, avec le pacman de l'hôte et --root, celui du
chroot ne démarrant pas avant que l'étage 2 ne l'ait reconstruit.
Deux crochets ne doivent PAS y tourner, et pour la même raison satisfaisante :
la condition qu'ils contournent n'existe pas dans le chroot. libgcrypt.sh
pointe sur un préfixe hôte contenant notre libgpg-error, que le chroot a
installé correctement. git.sh retire ZLIB_NG=1 parce qu'Ubuntu ne livre pas
les en-têtes zlib-ng, alors que notre propre paquet zlib-ng les livre.
git est là parce que l'ÉTAGE 2 en a besoin, pas le dépôt : 51 des 159
PKGBUILD prennent leurs sources en git+https, et makepkg valide ce clone même
sous --noextract. Rien ne dépend de git. Ses trois dépendances — perl-error,
perl-mailtools avec perl-timedate, zlib-ng — ont été lues dans le tableau
depends plutôt que dans mon propre outil, qui annonçait `zsh` comme dépendance
de git. Elle ne l'est pas : la regex de l'outil attrapait un tableau voisin.
Assisted-by: Claude Opus 5
2026-08-19 08:56:41 -04:00
|
|
|
return 0
|
|
|
|
|
fi
|
|
|
|
|
touch "$STATE2"
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
# --all: the shared list, in its order. Typing a hundred and thirty-three
|
|
|
|
|
# names is not a workflow, and typing a subset of them is how an ordering
|
|
|
|
|
# gets quietly reinvented.
|
|
|
|
|
if [ "$1" = "--all" ]; then
|
[FIX] stage 2: the build tools the host was providing silently
glibc rebuilt inside the chroot; binutils died on `make info-recursive`, and
makeinfo said why: its XS module was compiled against the host's perl 5.40 and
our perl is 5.42. Stage-1 texinfo cannot run in there. Everything that builds
.info documentation needs it, and it sat near the end of the list because that
is where its own dependencies are, so stage 2 hoists it.
linux-api-headers stopped earlier on `rsync: command not found` -- the kernel's
headers_install runs it. apt had put it there and a build that finds its tool
says nothing, so stage 1 never mentioned it. Its man pages come from Markdown
the git tag does not pre-render, hence --disable-md2man.
An inventory of the 110 apt packages against the chroot found 84 more such
binaries. Most are documentation; the rest wait until something needs them.
--- FR ---
glibc a été rebâti dans le chroot ; binutils est mort sur `make info-recursive`,
et makeinfo en donne la raison : son module XS a été compilé contre le perl 5.40
de l'hôte, or le nôtre est en 5.42. Le texinfo de l'étage 1 ne peut pas tourner
là-dedans. Tout ce qui bâtit de la documentation .info en dépend, et il figurait
en fin de liste, là où sont ses propres dépendances : l'étage 2 le remonte.
linux-api-headers s'était arrêté avant sur `rsync: command not found` — le
headers_install du noyau l'appelle. apt l'avait posé, et une construction qui
trouve son outil n'en dit rien : l'étage 1 ne l'a jamais mentionné. Ses pages de
manuel viennent d'un Markdown que l'étiquette git ne rend pas, d'où
--disable-md2man.
Un inventaire des 110 paquets apt face au chroot a trouvé 84 autres binaires du
même genre. La plupart sont de la documentation ; le reste attendra qu'un paquet
les réclame.
Assisted-by: Claude Opus 5
2026-08-20 01:34:41 -04:00
|
|
|
rebuild "${STAGE2_FIRST[@]}" "${STAGE1_PACKAGES[@]}"
|
[IMP] stage 2: resumable, guarded, driven by the shared list
Stage 2 could rebuild one named package. It could not rebuild a hundred and
thirty-three, for reasons that were all about the driver.
The order is not re-derived: it is the closure stage 1 arrived at over four
rounds of resolver output and then confirmed by 179 builds, so it moves to
packages.sh and both stages read it. make_rootfs wipes the chroot every run,
which is what makes it reproducible and what made stage 2 unresumable --
stage2.state outlived the packages it named. Its output is now put back.
The stall guard is shared rather than copied: stage 2 needs it more, since a
test suite is the likeliest thing in a build to wait forever. Build trees are
removed after a package installs, never after it fails.
--- FR ---
L'étage 2 savait rebâtir un paquet nommé. Il ne savait pas en rebâtir cent
trente-trois, pour des raisons qui tenaient toutes au pilote.
L'ordre n'est pas réinventé : c'est la fermeture obtenue à l'étage 1 en quatre
tours de sortie du résolveur, puis confirmée par 179 constructions. Il passe
donc dans packages.sh, que les deux étages lisent. make_rootfs efface le chroot
à chaque passage — ce qui le rend reproductible et rendait l'étage 2
irreprenable, stage2.state survivant aux paquets qu'il nommait. Sa production y
est désormais réinstallée.
La garde d'immobilité est partagée plutôt que recopiée : l'étage 2 en a plus
besoin, une suite de tests étant ce qui attend le plus volontiers pour
toujours. Les arbres de compilation sont effacés après installation, jamais
après un échec.
Assisted-by: Claude Opus 5
2026-08-20 01:16:49 -04:00
|
|
|
else
|
|
|
|
|
rebuild "$@"
|
|
|
|
|
fi
|
[ADD] stage 2: a chroot that builds
Stage 1 is done and its output is provably wrong in nine places: binaries
asking for libgpgme.so.11, libnettle.so.8, libicuuc.so.76 and six more at the
HOST's soname versions. Stage 2 dissolves all nine by rebuilding each package
against what the repository actually ships.
The constraint that shapes it: pacman cannot run inside the stage-1 rootfs,
because libalpm was linked against the host's gpgme. So the rootfs is
populated from OUTSIDE, with the host's pacman and --root, and the chroot is
used only to build. That is not a workaround, it is the order the problem has
-- stage 2's own output is the first pacman that will run on the target.
Five packages were added to stage 1 for this, and the resolver could never
have named them: nothing DEPENDS on fakeroot or bison, they are simply what a
build needs to happen. makepkg refuses to run as root, so the chroot carries a
user with the host's uid -- a bind mount keeps the numeric owner, and a
different uid inside could not write $srcdir.
The smoke test separates hard failures from known drift. makeinfo fails
because texinfo was built against the host's perl; that is what stage 2
repairs, so refusing to start over it would refuse to run the fix.
--- FR ---
L'étage 1 est terminé et sa sortie est démontrablement fausse en neuf points :
des binaires réclamant libgpgme.so.11, libnettle.so.8, libicuuc.so.76 et six
autres, aux versions de soname de l'HÔTE. L'étage 2 les dissout tous les neuf
en reconstruisant chaque paquet contre ce que le dépôt livre réellement.
La contrainte qui le façonne : pacman ne peut pas tourner dans le rootfs
d'étage 1, libalpm ayant été lié contre le gpgme de l'hôte. Le rootfs est donc
peuplé depuis l'EXTÉRIEUR, avec le pacman de l'hôte et --root, et le chroot ne
sert qu'à bâtir. Ce n'est pas un contournement mais l'ordre qu'a le problème :
la sortie de l'étage 2 est le premier pacman qui tournera sur la cible.
Cinq paquets ont rejoint l'étage 1 pour cela, et le résolveur n'aurait jamais
pu les nommer : rien ne DÉPEND de fakeroot ni de bison, ils sont simplement ce
qu'il faut pour qu'une compilation ait lieu. makepkg refuse de tourner en
root, le chroot porte donc un utilisateur avec l'uid de l'hôte — un bind mount
conserve le propriétaire numérique, et un uid différent ne pourrait pas
écrire $srcdir.
Le smoke test sépare les échecs durs des dérives connues. makeinfo échoue
parce que texinfo a été bâti contre le perl de l'hôte ; c'est précisément ce
que l'étage 2 répare, donc refuser de démarrer pour cela serait refuser de
lancer le correctif.
Assisted-by: Claude Opus 5
2026-08-19 08:30:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main "$@"
|