[ADD] stage 1: build a self-hosting core for s390x
Forty-odd packages in link-time order, from linux-api-headers to
pacman itself. The order follows what a compiler actually needs, not
pacman metadata: --nodeps means nothing is ever checked, so anything
required at link time has to exist already.
A failure does not stop the run. One missing package must not hide the
state of the forty that follow, so failures are collected and reported
at the end, each with its own log. A state file makes the run
resumable, which matters when a single gcc build is measured in tens
of minutes.
The header states why stage 1 is not the port: everything here links
against the host glibc, because Arch glibc needs an Arch gcc which
needs an Arch glibc. Stages 2 and 3 break that circle.
bootstrap-pacman.sh now guards its own main so it can be sourced for
build_package without re-running the whole bootstrap.
--- FR ---
Une quarantaine de paquets dans l ordre des dependances de lien, de
linux-api-headers a pacman lui-meme. L ordre suit ce dont un
compilateur a reellement besoin, pas les metadonnees de pacman :
--nodeps ne verifie jamais rien, donc tout ce qui sert au lien doit
deja exister.
Un echec n arrete pas la course. Un paquet manquant ne doit pas
masquer l etat des quarante suivants : les echecs sont collectes et
rapportes a la fin, chacun avec son journal. Un fichier d etat rend la
reprise possible, ce qui compte quand un seul gcc se compte en
dizaines de minutes.
L en-tete dit pourquoi l etage 1 n est pas le portage : tout s y lie a
la glibc de l hote, parce que la glibc d Arch reclame un gcc d Arch
qui reclame une glibc d Arch. Les etages 2 et 3 brisent ce cercle.
bootstrap-pacman.sh garde desormais son main pour etre sourcable et
fournir build_package sans relancer tout l amorcage.
Assisted-by: Claude Opus 5
2026-08-15 15:48:30 -04:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# Stage 1 of the port: build a self-hosting Arch `core` for s390x.
|
|
|
|
|
#
|
|
|
|
|
# THE THREE-STAGE DISCIPLINE, AND WHY IT IS NOT OPTIONAL
|
|
|
|
|
#
|
|
|
|
|
# Stage 1 builds with the HOST toolchain (Ubuntu gcc/glibc). Every package it
|
|
|
|
|
# produces is therefore linked against the host's glibc, not Arch's. That is
|
|
|
|
|
# acceptable -- and unavoidable, since Arch's glibc needs an Arch gcc which
|
|
|
|
|
# needs an Arch glibc -- but it is not a port yet.
|
|
|
|
|
#
|
|
|
|
|
# Stage 2 chroots into the stage-1 result and rebuilds everything with the
|
|
|
|
|
# stage-1 toolchain. Stage 3 repeats it, and a port is self-hosting once
|
|
|
|
|
# stage 3 reproduces stage 2. Skipping this leaves host artefacts baked into
|
|
|
|
|
# packages that will fail months later, far from their cause.
|
|
|
|
|
#
|
|
|
|
|
# This script is stage 1 only.
|
|
|
|
|
set -uo pipefail
|
|
|
|
|
|
|
|
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
|
source "$HERE/bootstrap-pacman.sh"
|
|
|
|
|
|
|
|
|
|
WORK="${WORK:-$HOME/work/arch-s390x}"
|
|
|
|
|
REPO="${REPO:-$WORK/repo/s390x}"
|
|
|
|
|
STATE="$WORK/stage1.state"
|
|
|
|
|
|
|
|
|
|
# Build order. It follows link-time dependencies, not pacman metadata:
|
|
|
|
|
# --nodeps means pacman never checks, so anything a compiler actually needs
|
|
|
|
|
# must already exist. Within a group the order is free.
|
|
|
|
|
STAGE1_PACKAGES=(
|
|
|
|
|
# Foundation: headers, then the C library, then the compiler chain.
|
|
|
|
|
linux-api-headers glibc binutils gcc
|
|
|
|
|
# Compression and crypto, needed by libarchive and curl further down.
|
|
|
|
|
zlib bzip2 xz zstd lz4 openssl
|
|
|
|
|
# Terminal handling: bash links against readline, readline against ncurses.
|
|
|
|
|
ncurses readline
|
|
|
|
|
# The shell, and the coreutils prerequisites Arch declares.
|
|
|
|
|
attr acl gmp mpfr libcap bash coreutils
|
|
|
|
|
# Text and file tools the build systems themselves call.
|
|
|
|
|
sed grep gawk findutils diffutils file which patch
|
|
|
|
|
# Archivers, then the library pacman reads packages with.
|
|
|
|
|
tar gzip expat libarchive
|
|
|
|
|
# Build systems.
|
|
|
|
|
m4 autoconf automake libtool make pkgconf
|
|
|
|
|
# pacman's network and signature stack.
|
|
|
|
|
libnghttp2 libpsl curl libgpg-error libassuan gnupg gpgme
|
|
|
|
|
# System skeleton: without these a rootfs has no /etc/passwd, no zones,
|
|
|
|
|
# no /etc/services -- and nothing boots to a usable shell.
|
|
|
|
|
filesystem iana-etc tzdata licenses shadow util-linux
|
[ADD] the packages the chroot named, and grep builds
Arch Linux runs on s390x. Installing the repository into a rootfs and
entering it:
bash : 5.3.15(1)-release
uname -m : s390x
glibc : ldd (GNU libc) 2.44
Two things the test taught that sixty-eight successful builds had not.
bash would not start at all: "chroot: No such file or directory" on a
binary that was plainly there. That is the dynamic linker being absent
-- bash asks for /lib/ld64.so.1, and that path exists only through the
usr-merge symlinks the filesystem package installs. Without it an Arch
rootfs starts nothing.
coreutils then failed on libselinux.so.1. Not a port defect: a genuine
dependency that was never built. pacman had in fact listed all of them
before I passed --nodeps -- brotli, libxml2, pam, systemd,
pacman-mirrorlist, libmakepkg-dropins. The resolver works; the
repository was incomplete.
They are added to stage 1 by name, with the reason recorded. A build
that succeeds proves the compiler accepted the source. Only running the
binary proves the package.
--- FR ---
Arch Linux tourne sur s390x. Le depot installe dans un rootfs, puis on
y entre :
bash : 5.3.15(1)-release
uname -m : s390x
glibc : ldd (GNU libc) 2.44
Deux enseignements que soixante-huit compilations reussies n avaient
pas donnes.
bash ne demarrait pas du tout : « chroot: No such file or directory »
sur un binaire pourtant present. C est l interpreteur dynamique qui
manque -- bash reclame /lib/ld64.so.1, chemin qui n existe que par les
liens usr-merge du paquet filesystem. Sans lui, un rootfs Arch ne lance
rien.
coreutils echouait ensuite sur libselinux.so.1. Non pas un defaut du
portage : une dependance reelle jamais batie. pacman les avait
d ailleurs toutes nommees avant que je passe --nodeps -- brotli,
libxml2, pam, systemd, pacman-mirrorlist, libmakepkg-dropins. Le
resolveur fonctionne ; c est le depot qui etait incomplet.
Elles rejoignent l etage 1, avec la raison consignee. Une compilation
reussie prouve que le compilateur a accepte la source. Seul le binaire
qui s execute prouve le paquet.
Assisted-by: Claude Opus 5
2026-08-17 00:23:36 -04:00
|
|
|
# Named by the chroot test, not guessed. Installing the repo into a
|
|
|
|
|
# rootfs and entering it turned "does it work?" into a precise list:
|
|
|
|
|
# - libcap needs pam; openssl needs brotli; libarchive needs libxml2
|
|
|
|
|
# - pacman itself asks for systemd, pacman-mirrorlist and
|
|
|
|
|
# libmakepkg-dropins
|
|
|
|
|
# Sixty-eight successful builds proved none of this. One chroot did.
|
[FIX] selinux: the chroot named a host artefact, not a dependency
The previous commit read the chroot's "coreutils needs libselinux.so.1"
as a missing package. It is not one. Arch has no libselinux at all -- the
clone 404s -- and Arch's coreutils declares no selinux dependency,
because its build chroot has no selinux/selinux.h to find.
Ours found one. Ubuntu carries libselinux1-dev, gnulib probes for the
header unconditionally, and the audit names every victim:
coreutils 13 binaries findutils find sed tar glibc makedb
--without-selinux per package, which is what Arch gets for free. Arch
ships neither chcon nor runcon either, so this converges with Arch rather
than diverging. tar and find matter most: stage 2 runs makepkg inside
this rootfs, and makepkg calls both.
glibc is deliberately left alone. Nothing runs makedb, and rebuilding
glibc would relink the foundation under sixty-nine other packages; stage
2 does it in a chroot where the header cannot be found.
--- FR ---
Le commit précédent a lu le « coreutils réclame libselinux.so.1 » du
chroot comme un paquet manquant. Ce n'en est pas un. Arch n'a aucun
libselinux — le clone rend un 404 — et son coreutils ne déclare aucune
dépendance selinux, faute de selinux/selinux.h dans son chroot de
construction.
Le nôtre en a trouvé un. Ubuntu embarque libselinux1-dev, gnulib sonde
l'en-tête sans condition, et l'audit nomme chaque victime :
coreutils 13 binaires findutils find sed tar glibc makedb
--without-selinux par paquet, ce qu'Arch obtient gratuitement. Arch ne
livre ni chcon ni runcon non plus : on converge donc vers Arch au lieu de
s'en écarter. tar et find sont les plus critiques — l'étage 2 lance
makepkg dans ce rootfs, et makepkg les appelle tous deux.
glibc est laissé tel quel, délibérément. Rien n'exécute makedb, et le
reconstruire relierait la fondation sous soixante-neuf autres paquets ;
l'étage 2 s'en charge dans un chroot où l'en-tête est introuvable.
Assisted-by: Claude Opus 5
2026-08-17 01:33:41 -04:00
|
|
|
#
|
|
|
|
|
# The chroot also named libselinux, and libselinux is NOT on this line,
|
|
|
|
|
# because that reading of it was wrong. Arch has no libselinux package at
|
|
|
|
|
# all -- the clone 404s. What the chroot saw was a HOST artefact: Ubuntu
|
|
|
|
|
# carries libselinux1-dev, coreutils probes for selinux/selinux.h
|
|
|
|
|
# unconditionally, and ours came out linked to a library the target will
|
|
|
|
|
# never contain. The answer is --without-selinux per package, which is
|
|
|
|
|
# what Arch's own build chroot gets for free by not having the header.
|
|
|
|
|
pam brotli libxml2 systemd pacman-mirrorlist libmakepkg-dropins
|
[ADD] the closure pacman's resolver named
Everything in stage 1 until now was added because a BUILD stopped. These were
added because test-chroot.sh ran the resolver with --nodeps OFF -- the check
the whole bootstrap skips -- and it listed exactly what the repository owed.
Nothing here is speculative.
Thirty-five packages, then five rounds of failures that each got further than
the last: 12 failed, then 7, then 4, then 0. The pattern was almost always a
host tool nobody had declared, and the fix for one uncovered the next --
ducktype then yelp-build, ss then lmdb, autoconf-archive then cmocka.
Two corrections worth keeping. libargon2-dev was the wrong package: openldap
passes --with-argon2=libsodium, so the error named argon2 and the answer was
sodium. And apt-cache reported NONE for all eight candidates because this host
answers `Candidat :`, not `Candidate:` -- the same locale trap that had broken
util-linux hours earlier, met again inside the script written to verify its
fix. Query apt under LC_ALL=C.
--- FR ---
Tout ce qui composait l'étage 1 jusqu'ici avait été ajouté parce qu'une
COMPILATION s'arrêtait. Ceux-ci l'ont été parce que test-chroot.sh a lancé le
résolveur avec --nodeps DÉSACTIVÉ — le contrôle que tout l'amorçage saute — et
qu'il a listé exactement ce que le dépôt devait. Rien ici n'est spéculatif.
Trente-cinq paquets, puis cinq tours d'échecs allant chacun plus loin que le
précédent : 12, puis 7, puis 4, puis 0. Le motif était presque toujours un
outil hôte que personne n'avait déclaré, et corriger l'un dévoilait le
suivant — ducktype puis yelp-build, ss puis lmdb, autoconf-archive puis
cmocka.
Deux corrections à garder. libargon2-dev était le mauvais paquet : openldap
passe --with-argon2=libsodium, l'erreur nommait donc argon2 quand la réponse
était sodium. Et apt-cache annonçait NONE pour les huit candidats parce que
cet hôte répond « Candidat : » et non « Candidate: » — le piège de locale même
qui avait cassé util-linux quelques heures plus tôt, retrouvé dans le script
écrit pour en vérifier le correctif. Interroger apt sous LC_ALL=C.
Assisted-by: Claude Opus 5
2026-08-19 05:28:32 -04:00
|
|
|
# The closure, named by pacman's own resolver rather than guessed.
|
|
|
|
|
#
|
|
|
|
|
# Everything above was added because a BUILD stopped. These were added
|
|
|
|
|
# because scripts/test-chroot.sh ran `pacman -Syp` with --nodeps OFF --
|
|
|
|
|
# the check the whole bootstrap skips -- and the resolver listed exactly
|
|
|
|
|
# what the repository still owes. Nothing here is speculative.
|
|
|
|
|
#
|
|
|
|
|
# It is the MINIMAL closure, and two measurements shaped it. Dropping the
|
|
|
|
|
# python-brotli sub-package took the list from 70 unresolved names to 47
|
|
|
|
|
# and removed `python` outright, with libffi, mpdecimal and gdbm behind
|
|
|
|
|
# it. Dropping systemd-ukify and systemd-tests removed five more python
|
|
|
|
|
# packages, and ukify cannot run on s390x at all. Both are recorded in
|
|
|
|
|
# TODO.md rather than left implicit.
|
|
|
|
|
#
|
|
|
|
|
# An audit of the remaining names against the ARTEFACTS found two that
|
|
|
|
|
# were declared and never linked: guile by make, and libisl.so by gcc.
|
|
|
|
|
# Both get their declaration removed, because it should describe the
|
|
|
|
|
# binary we shipped.
|
|
|
|
|
#
|
|
|
|
|
# Building isl instead was the first plan, and it is recorded here because
|
|
|
|
|
# the reason it failed is the kind that wastes an afternoon: the Arch
|
|
|
|
|
# packaging repo for isl was last touched in 2017 and its only source URL
|
|
|
|
|
# is isl.gforge.inria.fr, which died with INRIA's GForge. There is nothing
|
|
|
|
|
# to build. That flipped the decision -- not a change of mind, new
|
|
|
|
|
# evidence.
|
|
|
|
|
#
|
|
|
|
|
# These will pull their own dependencies. That is expected: the resolver
|
|
|
|
|
# will name the next round as precisely as it named this one.
|
|
|
|
|
audit ca-certificates cryptsetup dbus elfutils gettext gnutls hwdata
|
|
|
|
|
icu jansson kbd kmod krb5 libcap-ng libgcrypt libidn2 libksba
|
|
|
|
|
libmpc libnsl libseccomp libssh2 libtirpc libunistring libusb libxcrypt
|
|
|
|
|
nettle npth openldap pambase pcre2 perl pinentry sqlite tpm2-tss
|
[ADD] closure rounds two and three, and the CA bundle
Thirty-five packages pulled in nineteen more, and those two. The resolver
named each round as precisely as the first, and it now reports satisfied:
101 packages, --nodeps off.
THE MEASUREMENT THAT CHANGED ITS ANSWER. Four of round two were going to be
avoided by dropping sub-packages -- sqlite-tcl, sqlite-analyzer, the openldap
server, debuginfod -- reasoning that had been right for python-brotli.
Measured instead: tcl, unixodbc and libmicrohttpd each cost ZERO new
packages, because the closure had filled in around them. Building them beats
four hooks, and it does not leave sqlite shipping an sqltclsh that cannot
start. The arithmetic that was right at forty packages was wrong at a hundred
and thirty.
ca-certificates-mozilla is the only entry here that is not a library: it is
the root certificate list itself, and ca-certificates is only the machinery
around it. Without it the target trusts nothing and every HTTPS verification
fails. It has no packaging repo -- nss produces it -- so nss and nspr came
too, measured first: nspr free, nss needing only mercurial on the host, and
hg.mozilla.org answering in 0.3s. 172 certificates shipped.
--- FR ---
Trente-cinq paquets en ont tiré dix-neuf autres, puis ces deux-là. Le
résolveur a nommé chaque tour aussi précisément que le premier, et il se
déclare maintenant satisfait : 101 paquets, --nodeps désactivé.
LA MESURE QUI A CHANGÉ SA RÉPONSE. Quatre paquets du deuxième tour allaient
être évités en écartant des sous-paquets — sqlite-tcl, sqlite-analyzer, le
serveur openldap, debuginfod — par un raisonnement juste pour python-brotli.
Mesuré plutôt que supposé : tcl, unixodbc et libmicrohttpd coûtent ZÉRO
paquet nouveau, la fermeture s'étant refermée autour d'eux. Les bâtir vaut
mieux que quatre crochets, et évite de livrer un sqlite contenant un sqltclsh
incapable de démarrer. L'arithmétique juste à quarante paquets était fausse à
cent trente.
ca-certificates-mozilla est la seule entrée ici qui ne soit pas une
bibliothèque : c'est la liste des certificats racine elle-même, et
ca-certificates n'en est que la mécanique. Sans lui la cible ne fait
confiance à rien et toute vérification HTTPS échoue. Il n'a pas de dépôt de
packaging — nss le produit — donc nss et nspr ont suivi, mesurés d'abord :
nspr gratuit, nss ne réclamant que mercurial sur l'hôte, et hg.mozilla.org
répondant en 0,3 s. 172 certificats livrés.
Assisted-by: Claude Opus 5
2026-08-19 06:24:11 -04:00
|
|
|
# Closure, second round. The first thirty-five pulled these in, exactly as
|
|
|
|
|
# the comment above predicted, and the resolver named them just as
|
|
|
|
|
# precisely.
|
|
|
|
|
#
|
|
|
|
|
# THE MEASUREMENT THAT MATTERS HERE IS THE ONE THAT CHANGED ITS ANSWER.
|
|
|
|
|
# Four of these were going to be avoided by dropping a sub-package --
|
|
|
|
|
# sqlite-tcl, sqlite-analyzer, the openldap server, debuginfod -- on the
|
|
|
|
|
# reasoning that had removed python-brotli earlier. Measured instead of
|
|
|
|
|
# assumed, tcl, unixodbc and libmicrohttpd each cost ZERO new packages:
|
|
|
|
|
# the closure had filled in around them. Building them is cheaper than
|
|
|
|
|
# four hooks, and it does not leave sqlite shipping an sqltclsh that
|
|
|
|
|
# cannot start.
|
|
|
|
|
#
|
|
|
|
|
# python still costs three (libffi, mpdecimal, gdbm) and is still avoided
|
|
|
|
|
# -- but for the ABI reason, not the cost one: python-audit and
|
|
|
|
|
# python-capng are cp313 wheels and Arch ships 3.14. Their hooks say so.
|
|
|
|
|
#
|
|
|
|
|
db5.3 gdbm e2fsprogs gnulib-l10n json-c keyutils p11-kit libsasl
|
|
|
|
|
libsodium libtasn1 libverto lmdb popt lvm2 tcl unixodbc libmicrohttpd
|
|
|
|
|
# ca-certificates-mozilla, which is the only thing here that is not a
|
|
|
|
|
# library. It is the LIST OF ROOT CERTIFICATES -- ca-certificates itself
|
|
|
|
|
# is only the trust machinery around it, so without this the target has a
|
|
|
|
|
# trust store containing nothing, and every HTTPS verification fails.
|
|
|
|
|
# curl declares ca-certificates, and pacman fetches through curl.
|
|
|
|
|
#
|
|
|
|
|
# It has no packaging repo of its own: the clone 404s, which
|
|
|
|
|
# gitlab.archlinux.org reports by asking for a login -- the same
|
|
|
|
|
# misleading shape that made libselinux look like a network problem. nss
|
|
|
|
|
# produces it as a sub-package, so nss is what gets built, and nspr comes
|
|
|
|
|
# with it.
|
|
|
|
|
#
|
|
|
|
|
# Measured before committing to it rather than estimated: nspr costs
|
|
|
|
|
# nothing new, nss needs only nspr plus mercurial on the host, and
|
|
|
|
|
# hg.mozilla.org answers from this build host (HTTP 302 in 0.3s -- worth
|
|
|
|
|
# checking, since dev.gnupg.org does not answer at all and libassuan
|
|
|
|
|
# needed a source change because of it).
|
|
|
|
|
nspr nss
|
|
|
|
|
# Closure, third round, and it is two packages. Both were pulled in by
|
|
|
|
|
# what the second round added, and both cost nothing further: libffi by
|
|
|
|
|
# libp11-kit, libevent by libverto.
|
|
|
|
|
#
|
|
|
|
|
# They are worth a line because of what they unblocked. p11-kit builds
|
|
|
|
|
# into three packages, and libp11-kit's dependency on libffi was holding
|
|
|
|
|
# up the whole chain ca-certificates -> ca-certificates-utils -> p11-kit
|
|
|
|
|
# -> libp11-kit. The resolver reported it as "ca-certificates required by
|
|
|
|
|
# curl" -- four links away from the missing name, and nothing in that
|
|
|
|
|
# message points at libffi.
|
|
|
|
|
libffi libevent
|
[FIX] lvm2: only device-mapper, and the closure closes
The fourth closure round asked for libaio and thin-provisioning-tools, both
wanted by lvm2 alone. thin-provisioning-tools is Rust now, so a language
runtime was arriving through a fourth-order dependency.
Nothing in the repository wants `lvm2`. Checked across every .PKGINFO:
cryptsetup wants device-mapper, and device-mapper is the other package this
same source produces. Dropping the sub-package removed both entries and the
Rust question with them.
The measurement is the argument, and it went the other way an hour earlier.
tcl, unixodbc and libmicrohttpd each cost zero new packages, so building them
beat dropping sub-packages. This one costs a language runtime, so dropping
wins. The rule is not to prefer either -- it is to measure each time.
device-mapper had also picked up the host's libselinux, silently. Seventh
package to do it, second found by reading binaries rather than by a build.
35 packages, then 19, then 2, then 0.
--- FR ---
Le quatrième tour de fermeture réclamait libaio et thin-provisioning-tools,
tous deux voulus par lvm2 seul. thin-provisioning-tools est en Rust
désormais : un environnement de langage arrivait par une dépendance de
quatrième ordre.
Rien dans le dépôt ne veut `lvm2`. Vérifié sur chaque .PKGINFO : cryptsetup
veut device-mapper, et device-mapper est l'autre paquet que cette même source
produit. Écarter le sous-paquet a retiré les deux entrées et la question Rust
avec elles.
La mesure est l'argument, et elle avait tranché dans l'autre sens une heure
plus tôt. tcl, unixodbc et libmicrohttpd coûtaient zéro paquet nouveau : les
bâtir valait mieux qu'écarter des sous-paquets. Celui-ci coûte un
environnement de langage : écarter gagne. La règle n'est de préférer ni l'un
ni l'autre — c'est de mesurer chaque fois.
device-mapper avait aussi ramassé le libselinux de l'hôte, en silence.
Septième paquet à le faire, deuxième trouvé en lisant des binaires plutôt que
par une compilation.
35 paquets, puis 19, puis 2, puis 0.
Assisted-by: Claude Opus 5
2026-08-19 06:40:50 -04:00
|
|
|
# Closure, fourth round -- and it closed to NOTHING, which is the point
|
|
|
|
|
# worth recording. It asked for libaio and thin-provisioning-tools, both
|
|
|
|
|
# wanted by lvm2 alone. thin-provisioning-tools is Rust now, so that was a
|
|
|
|
|
# language runtime arriving through a fourth-order dependency.
|
|
|
|
|
#
|
|
|
|
|
# Nothing in the repository wants `lvm2`. Checked across every .PKGINFO:
|
|
|
|
|
# cryptsetup wants device-mapper, and device-mapper is the OTHER package
|
|
|
|
|
# this same source produces. Dropping the lvm2 sub-package removed both
|
|
|
|
|
# entries and the Rust question with them -- see patches/pkgbuild/lvm2.sh.
|
|
|
|
|
#
|
|
|
|
|
# 35 packages, then 19, then 2, then 0. The closure has to be recomputed
|
|
|
|
|
# after each round rather than once: lvm2 DECLARED libaio all along, and
|
|
|
|
|
# the third round could not see it because lvm2 had not been built yet.
|
[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
|
|
|
# What STAGE 2 needs in order to exist, which is a different question from
|
|
|
|
|
# what the repository needs to resolve.
|
|
|
|
|
#
|
|
|
|
|
# Stage 2 rebuilds everything INSIDE the stage-1 rootfs, so the tools that
|
|
|
|
|
# do the rebuilding have to be in that rootfs. The resolver never asked
|
|
|
|
|
# for these -- nothing in the repository depends on them -- so the closure
|
|
|
|
|
# rounds could not surface them. Enumerated instead from what makepkg and
|
|
|
|
|
# an autotools build actually invoke.
|
|
|
|
|
#
|
|
|
|
|
# fakeroot is the one that decides whether stage 2 can start at all:
|
|
|
|
|
# makepkg runs package() under it, and refuses to run as root. bison,
|
|
|
|
|
# flex, texinfo and groff are what the sources themselves call -- gcc,
|
|
|
|
|
# glibc and binutils all want makeinfo, and a great many configure scripts
|
|
|
|
|
# want bison.
|
|
|
|
|
#
|
|
|
|
|
# sudo is deliberately NOT here. makepkg needs it only for --syncdeps, and
|
|
|
|
|
# stage 2 passes --nodeps, so it would be a setuid binary in the chroot
|
|
|
|
|
# for no reason.
|
|
|
|
|
fakeroot bison flex texinfo groff
|
[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, and it is not optional: 51 of the 159 PKGBUILDs take their sources
|
|
|
|
|
# from a git+https URL, and makepkg re-validates that clone even with
|
|
|
|
|
# --noextract. Without git in the chroot, stage 2 can rebuild a third of
|
|
|
|
|
# the repository and no more.
|
|
|
|
|
#
|
|
|
|
|
# Its own three: perl-error, perl-mailtools (which brings perl-timedate)
|
|
|
|
|
# and zlib-ng. Measured, not guessed -- and 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.
|
|
|
|
|
perl-error perl-timedate perl-mailtools zlib-ng git
|
[FIX] driver: kill a build that has stopped producing output
python's PKGBUILD hunts for a free X display before running make:
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run is not installed here. It exits 127, `!` inverts that to true, and
the loop tries the next number. There is no exit condition for "the command
does not exist", and the 2>/dev/null swallows the one line that would have
said so.
Measured before anyone noticed: ten hours of wall clock, four million PIDs,
and a log frozen at `configure: creating Makefile`. Every signal said the
build was healthy -- alive, 40% CPU, sitting in a plausible source directory.
A process list cannot tell work from spinning. A stalled log next to a live
makepkg can, and that is all this guard does.
Forty-five minutes of COMPLETE silence, because real builds do go quiet: a
long link, a test suite that prints nothing, gcc between bootstrap stages.
The case this was written for ran two hundred times longer. EL_STALL_MIN=0
disables it.
Tested against a real spin loop: detected, whole process tree killed, rc=2 so
STALL reads differently from FAIL in the summary.
--- FR ---
Le PKGBUILD de python cherche un numéro d'affichage X libre avant make :
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run n'est pas installé ici. Il sort en 127, le `!` inverse, et la boucle
essaie le numéro suivant. Il n'existe aucune condition de sortie pour « la
commande n'existe pas », et le 2>/dev/null avale la seule ligne qui l'aurait
dit.
Mesuré avant que quiconque s'en aperçoive : dix heures d'horloge, quatre
millions de PID, un journal figé à `configure: creating Makefile`. Tous les
signaux disaient que le build allait bien — vivant, 40 % de CPU, dans un
répertoire source plausible. Une liste de processus ne distingue pas le
travail du sur-place. Un journal figé à côté d'un makepkg vivant, si — et
c'est tout ce que fait ce garde-fou.
Quarante-cinq minutes de silence COMPLET, car de vrais builds se taisent :
une longue édition de liens, une suite de tests muette, gcc entre deux étages.
Le cas qui l'a motivé a tourné deux cents fois plus longtemps. EL_STALL_MIN=0
le désactive.
Éprouvé contre une vraie boucle infinie : détecté, arbre de processus tué en
entier, rc=2 pour que STALL se lise autrement que FAIL dans le bilan.
Assisted-by: Claude Opus 5
2026-08-19 19:53:46 -04:00
|
|
|
# meson and cmake, which is where python re-enters -- and the distinction
|
|
|
|
|
# matters, because dropping python earlier was not a mistake.
|
|
|
|
|
#
|
|
|
|
|
# At stage 1 the question was what the REPOSITORY must supply: python was
|
|
|
|
|
# wanted only by python-brotli and python-libseccomp, wheels that Arch's
|
|
|
|
|
# own python could not load anyway, so they went and python went with them.
|
|
|
|
|
# Here the question is what the BUILD ENVIRONMENT must contain, and meson
|
|
|
|
|
# is written in Python. Ten of the 159 PKGBUILDs call arch-meson; four call
|
|
|
|
|
# cmake. Different question, different answer.
|
|
|
|
|
#
|
|
|
|
|
# Measured: mpdecimal, python, ninja, python-tqdm and meson, then cmake
|
|
|
|
|
# with cppdap, jsoncpp, libuv, rhash and hicolor-icon-theme behind it.
|
|
|
|
|
# Nothing further.
|
|
|
|
|
mpdecimal python ninja python-tqdm meson
|
|
|
|
|
cppdap jsoncpp libuv rhash hicolor-icon-theme cmake
|
[ADD] stage 1: build a self-hosting core for s390x
Forty-odd packages in link-time order, from linux-api-headers to
pacman itself. The order follows what a compiler actually needs, not
pacman metadata: --nodeps means nothing is ever checked, so anything
required at link time has to exist already.
A failure does not stop the run. One missing package must not hide the
state of the forty that follow, so failures are collected and reported
at the end, each with its own log. A state file makes the run
resumable, which matters when a single gcc build is measured in tens
of minutes.
The header states why stage 1 is not the port: everything here links
against the host glibc, because Arch glibc needs an Arch gcc which
needs an Arch glibc. Stages 2 and 3 break that circle.
bootstrap-pacman.sh now guards its own main so it can be sourced for
build_package without re-running the whole bootstrap.
--- FR ---
Une quarantaine de paquets dans l ordre des dependances de lien, de
linux-api-headers a pacman lui-meme. L ordre suit ce dont un
compilateur a reellement besoin, pas les metadonnees de pacman :
--nodeps ne verifie jamais rien, donc tout ce qui sert au lien doit
deja exister.
Un echec n arrete pas la course. Un paquet manquant ne doit pas
masquer l etat des quarante suivants : les echecs sont collectes et
rapportes a la fin, chacun avec son journal. Un fichier d etat rend la
reprise possible, ce qui compte quand un seul gcc se compte en
dizaines de minutes.
L en-tete dit pourquoi l etage 1 n est pas le portage : tout s y lie a
la glibc de l hote, parce que la glibc d Arch reclame un gcc d Arch
qui reclame une glibc d Arch. Les etages 2 et 3 brisent ce cercle.
bootstrap-pacman.sh garde desormais son main pour etre sourcable et
fournir build_package sans relancer tout l amorcage.
Assisted-by: Claude Opus 5
2026-08-15 15:48:30 -04:00
|
|
|
# And finally the package manager itself, built as an Arch package.
|
|
|
|
|
pacman
|
|
|
|
|
)
|
|
|
|
|
|
[FIX] driver: kill a build that has stopped producing output
python's PKGBUILD hunts for a free X display before running make:
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run is not installed here. It exits 127, `!` inverts that to true, and
the loop tries the next number. There is no exit condition for "the command
does not exist", and the 2>/dev/null swallows the one line that would have
said so.
Measured before anyone noticed: ten hours of wall clock, four million PIDs,
and a log frozen at `configure: creating Makefile`. Every signal said the
build was healthy -- alive, 40% CPU, sitting in a plausible source directory.
A process list cannot tell work from spinning. A stalled log next to a live
makepkg can, and that is all this guard does.
Forty-five minutes of COMPLETE silence, because real builds do go quiet: a
long link, a test suite that prints nothing, gcc between bootstrap stages.
The case this was written for ran two hundred times longer. EL_STALL_MIN=0
disables it.
Tested against a real spin loop: detected, whole process tree killed, rc=2 so
STALL reads differently from FAIL in the summary.
--- FR ---
Le PKGBUILD de python cherche un numéro d'affichage X libre avant make :
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run n'est pas installé ici. Il sort en 127, le `!` inverse, et la boucle
essaie le numéro suivant. Il n'existe aucune condition de sortie pour « la
commande n'existe pas », et le 2>/dev/null avale la seule ligne qui l'aurait
dit.
Mesuré avant que quiconque s'en aperçoive : dix heures d'horloge, quatre
millions de PID, un journal figé à `configure: creating Makefile`. Tous les
signaux disaient que le build allait bien — vivant, 40 % de CPU, dans un
répertoire source plausible. Une liste de processus ne distingue pas le
travail du sur-place. Un journal figé à côté d'un makepkg vivant, si — et
c'est tout ce que fait ce garde-fou.
Quarante-cinq minutes de silence COMPLET, car de vrais builds se taisent :
une longue édition de liens, une suite de tests muette, gcc entre deux étages.
Le cas qui l'a motivé a tourné deux cents fois plus longtemps. EL_STALL_MIN=0
le désactive.
Éprouvé contre une vraie boucle infinie : détecté, arbre de processus tué en
entier, rc=2 pour que STALL se lise autrement que FAIL dans le bilan.
Assisted-by: Claude Opus 5
2026-08-19 19:53:46 -04:00
|
|
|
# Kill a build that has stopped producing output.
|
|
|
|
|
#
|
|
|
|
|
# WHY THIS EXISTS. python's PKGBUILD contains
|
|
|
|
|
#
|
|
|
|
|
# while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
|
|
|
|
|
# servernum=$((servernum+1)); done
|
|
|
|
|
#
|
|
|
|
|
# and xvfb-run is not installed here. It exits 127, `!` inverts that to true,
|
|
|
|
|
# and the loop tries the next display number. Forever -- there is no exit
|
|
|
|
|
# condition for "the command does not exist", and the 2>/dev/null swallows the
|
|
|
|
|
# one line that would have said so.
|
|
|
|
|
#
|
|
|
|
|
# Measured before anyone noticed: TEN HOURS of wall clock, four million PIDs,
|
|
|
|
|
# and a log frozen at `configure: creating Makefile`. Every signal I had said
|
|
|
|
|
# the build was healthy -- the process was alive, burning 40% CPU, sitting in
|
|
|
|
|
# a plausible source directory. A process list cannot tell work from spinning.
|
|
|
|
|
#
|
|
|
|
|
# A STALLED LOG NEXT TO A LIVE makepkg CAN. That is the whole idea here.
|
|
|
|
|
#
|
|
|
|
|
# The threshold is deliberately generous. Real builds do go quiet: a long link,
|
|
|
|
|
# a test suite that prints nothing, gcc between bootstrap stages. Forty-five
|
|
|
|
|
# minutes of COMPLETE silence is not one of those, and the case this was
|
|
|
|
|
# written for ran two hundred times longer. EL_STALL_MIN=0 disables it.
|
|
|
|
|
#
|
|
|
|
|
# `set -m` rather than setsid or a bare `&`. The whole tree has to die, because
|
|
|
|
|
# makepkg forks children that outlive it -- killing the parent alone leaves
|
|
|
|
|
# them spinning, which is how ten hours happened. Job control gives the
|
|
|
|
|
# background job its own process group, so `kill -- -$pid` reaches all of it.
|
|
|
|
|
#
|
|
|
|
|
# A subshell, NOT `setsid bash -c "$(declare -f ...)"`. The first attempt here
|
|
|
|
|
# re-declared build_package into a fresh shell, which loses $WORK, $REPO,
|
|
|
|
|
# $PATCH_DIR and every other function it calls -- a guard that would have
|
|
|
|
|
# broken the thing it was guarding.
|
|
|
|
|
build_watched() {
|
|
|
|
|
local p="$1" log="$2"
|
|
|
|
|
local limit="${EL_STALL_MIN:-45}"
|
|
|
|
|
if [ "$limit" -eq 0 ]; then
|
|
|
|
|
build_package "$p" > "$log" 2>&1
|
|
|
|
|
return $?
|
|
|
|
|
fi
|
|
|
|
|
set -m
|
|
|
|
|
( build_package "$p" ) > "$log" 2>&1 &
|
|
|
|
|
local pid=$! last=0 still=0 sz
|
|
|
|
|
set +m
|
|
|
|
|
while kill -0 "$pid" 2>/dev/null; do
|
|
|
|
|
sleep 60
|
|
|
|
|
sz=$(stat -c %s "$log" 2>/dev/null || echo 0)
|
|
|
|
|
if [ "$sz" -eq "$last" ]; then still=$((still + 1)); else still=0; fi
|
|
|
|
|
last="$sz"
|
|
|
|
|
if [ "$still" -ge "$limit" ]; then
|
|
|
|
|
printf '\n== driver: no output for %s minutes, killing ==\n' "$limit" >> "$log"
|
|
|
|
|
kill -9 -- "-$pid" 2>/dev/null
|
|
|
|
|
wait "$pid" 2>/dev/null
|
|
|
|
|
return 2
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
wait "$pid"
|
|
|
|
|
}
|
|
|
|
|
|
[ADD] stage 1: build a self-hosting core for s390x
Forty-odd packages in link-time order, from linux-api-headers to
pacman itself. The order follows what a compiler actually needs, not
pacman metadata: --nodeps means nothing is ever checked, so anything
required at link time has to exist already.
A failure does not stop the run. One missing package must not hide the
state of the forty that follow, so failures are collected and reported
at the end, each with its own log. A state file makes the run
resumable, which matters when a single gcc build is measured in tens
of minutes.
The header states why stage 1 is not the port: everything here links
against the host glibc, because Arch glibc needs an Arch gcc which
needs an Arch glibc. Stages 2 and 3 break that circle.
bootstrap-pacman.sh now guards its own main so it can be sourced for
build_package without re-running the whole bootstrap.
--- FR ---
Une quarantaine de paquets dans l ordre des dependances de lien, de
linux-api-headers a pacman lui-meme. L ordre suit ce dont un
compilateur a reellement besoin, pas les metadonnees de pacman :
--nodeps ne verifie jamais rien, donc tout ce qui sert au lien doit
deja exister.
Un echec n arrete pas la course. Un paquet manquant ne doit pas
masquer l etat des quarante suivants : les echecs sont collectes et
rapportes a la fin, chacun avec son journal. Un fichier d etat rend la
reprise possible, ce qui compte quand un seul gcc se compte en
dizaines de minutes.
L en-tete dit pourquoi l etage 1 n est pas le portage : tout s y lie a
la glibc de l hote, parce que la glibc d Arch reclame un gcc d Arch
qui reclame une glibc d Arch. Les etages 2 et 3 brisent ce cercle.
bootstrap-pacman.sh garde desormais son main pour etre sourcable et
fournir build_package sans relancer tout l amorcage.
Assisted-by: Claude Opus 5
2026-08-15 15:48:30 -04:00
|
|
|
built() { grep -qxF "$1" "$STATE" 2>/dev/null; }
|
|
|
|
|
mark() { echo "$1" >> "$STATE"; }
|
|
|
|
|
|
|
|
|
|
main() {
|
|
|
|
|
mkdir -p "$WORK/pkg" "$REPO"
|
|
|
|
|
touch "$STATE"
|
[FIX] libdir: the Debian host hid the libraries from Arch
meson and cmake both ask the HOST where libraries go. On Ubuntu the
answer is lib/s390x-linux-gnu, so five packages already in the repository
ship theirs where Arch's ld.so and pkgconf never look. Measured:
expat 12 lz4 6 pacman 6 pkgconf 6 zstd 12 entries
Nothing failed. util-linux is where it finally shouted, and even there
the rm was the first victim, not the cause.
pacman is the one that matters: libalpm.so.16 landed where pacman's own
binary cannot load it, and a target installed from that package has no
working package manager left to repair itself with.
arch-meson now states the libdir devtools has no need to state, and is
reinstalled on every run -- editing the copy here changed nothing while
/usr/local/bin held a stale one. Four packages call bare meson or cmake
and get their own hook. util-linux's old hook chased lib64, which never
existed here; it is deleted.
--- FR ---
meson et cmake demandent tous deux à l'HÔTE où vont les bibliothèques.
Sous Ubuntu la réponse est lib/s390x-linux-gnu : cinq paquets déjà dans
le dépôt livrent donc les leurs là où ld.so et pkgconf d'Arch ne
regarderont jamais. Mesuré :
expat 12 lz4 6 pacman 6 pkgconf 6 zstd 12 entrées
Rien n'a échoué. util-linux est l'endroit où cela a fini par crier, et
même là le rm était la première victime, pas la cause.
pacman est celui qui compte : libalpm.so.16 atterrissait là où le binaire
de pacman ne peut pas la charger, et une cible installée depuis ce paquet
n'a plus de gestionnaire de paquets pour se réparer.
arch-meson énonce désormais le libdir que devtools n'a pas besoin
d'énoncer, et se réinstalle à chaque exécution : éditer la copie du dépôt
ne changeait rien tant que /usr/local/bin en gardait une périmée. Quatre
paquets appellent meson ou cmake nu et reçoivent leur crochet. L'ancien
crochet util-linux poursuivait un lib64 qui n'a jamais existé ici : il
est supprimé.
Assisted-by: Claude Opus 5
2026-08-17 01:33:41 -04:00
|
|
|
# The stand-ins are read from /usr/local/bin, so a stage-1 run that never
|
|
|
|
|
# reinstalls them silently builds with whatever was deployed weeks ago.
|
|
|
|
|
# Cheap, idempotent, and it makes this repository the source of truth.
|
|
|
|
|
install_host_shims
|
[FIX] driver: kill a build that has stopped producing output
python's PKGBUILD hunts for a free X display before running make:
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run is not installed here. It exits 127, `!` inverts that to true, and
the loop tries the next number. There is no exit condition for "the command
does not exist", and the 2>/dev/null swallows the one line that would have
said so.
Measured before anyone noticed: ten hours of wall clock, four million PIDs,
and a log frozen at `configure: creating Makefile`. Every signal said the
build was healthy -- alive, 40% CPU, sitting in a plausible source directory.
A process list cannot tell work from spinning. A stalled log next to a live
makepkg can, and that is all this guard does.
Forty-five minutes of COMPLETE silence, because real builds do go quiet: a
long link, a test suite that prints nothing, gcc between bootstrap stages.
The case this was written for ran two hundred times longer. EL_STALL_MIN=0
disables it.
Tested against a real spin loop: detected, whole process tree killed, rc=2 so
STALL reads differently from FAIL in the summary.
--- FR ---
Le PKGBUILD de python cherche un numéro d'affichage X libre avant make :
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run n'est pas installé ici. Il sort en 127, le `!` inverse, et la boucle
essaie le numéro suivant. Il n'existe aucune condition de sortie pour « la
commande n'existe pas », et le 2>/dev/null avale la seule ligne qui l'aurait
dit.
Mesuré avant que quiconque s'en aperçoive : dix heures d'horloge, quatre
millions de PID, un journal figé à `configure: creating Makefile`. Tous les
signaux disaient que le build allait bien — vivant, 40 % de CPU, dans un
répertoire source plausible. Une liste de processus ne distingue pas le
travail du sur-place. Un journal figé à côté d'un makepkg vivant, si — et
c'est tout ce que fait ce garde-fou.
Quarante-cinq minutes de silence COMPLET, car de vrais builds se taisent :
une longue édition de liens, une suite de tests muette, gcc entre deux étages.
Le cas qui l'a motivé a tourné deux cents fois plus longtemps. EL_STALL_MIN=0
le désactive.
Éprouvé contre une vraie boucle infinie : détecté, arbre de processus tué en
entier, rc=2 pour que STALL se lise autrement que FAIL dans le bilan.
Assisted-by: Claude Opus 5
2026-08-19 19:53:46 -04:00
|
|
|
local ok=0 fail=0 rc=0 failed=()
|
[ADD] stage 1: build a self-hosting core for s390x
Forty-odd packages in link-time order, from linux-api-headers to
pacman itself. The order follows what a compiler actually needs, not
pacman metadata: --nodeps means nothing is ever checked, so anything
required at link time has to exist already.
A failure does not stop the run. One missing package must not hide the
state of the forty that follow, so failures are collected and reported
at the end, each with its own log. A state file makes the run
resumable, which matters when a single gcc build is measured in tens
of minutes.
The header states why stage 1 is not the port: everything here links
against the host glibc, because Arch glibc needs an Arch gcc which
needs an Arch glibc. Stages 2 and 3 break that circle.
bootstrap-pacman.sh now guards its own main so it can be sourced for
build_package without re-running the whole bootstrap.
--- FR ---
Une quarantaine de paquets dans l ordre des dependances de lien, de
linux-api-headers a pacman lui-meme. L ordre suit ce dont un
compilateur a reellement besoin, pas les metadonnees de pacman :
--nodeps ne verifie jamais rien, donc tout ce qui sert au lien doit
deja exister.
Un echec n arrete pas la course. Un paquet manquant ne doit pas
masquer l etat des quarante suivants : les echecs sont collectes et
rapportes a la fin, chacun avec son journal. Un fichier d etat rend la
reprise possible, ce qui compte quand un seul gcc se compte en
dizaines de minutes.
L en-tete dit pourquoi l etage 1 n est pas le portage : tout s y lie a
la glibc de l hote, parce que la glibc d Arch reclame un gcc d Arch
qui reclame une glibc d Arch. Les etages 2 et 3 brisent ce cercle.
bootstrap-pacman.sh garde desormais son main pour etre sourcable et
fournir build_package sans relancer tout l amorcage.
Assisted-by: Claude Opus 5
2026-08-15 15:48:30 -04:00
|
|
|
for p in "${STAGE1_PACKAGES[@]}"; do
|
|
|
|
|
if built "$p"; then
|
|
|
|
|
echo "== $p already built, skipping =="
|
|
|
|
|
continue
|
|
|
|
|
fi
|
|
|
|
|
# A failure must not stop the run: one missing package should not hide
|
|
|
|
|
# the state of the forty that follow. They are collected and reported.
|
[FIX] driver: kill a build that has stopped producing output
python's PKGBUILD hunts for a free X display before running make:
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run is not installed here. It exits 127, `!` inverts that to true, and
the loop tries the next number. There is no exit condition for "the command
does not exist", and the 2>/dev/null swallows the one line that would have
said so.
Measured before anyone noticed: ten hours of wall clock, four million PIDs,
and a log frozen at `configure: creating Makefile`. Every signal said the
build was healthy -- alive, 40% CPU, sitting in a plausible source directory.
A process list cannot tell work from spinning. A stalled log next to a live
makepkg can, and that is all this guard does.
Forty-five minutes of COMPLETE silence, because real builds do go quiet: a
long link, a test suite that prints nothing, gcc between bootstrap stages.
The case this was written for ran two hundred times longer. EL_STALL_MIN=0
disables it.
Tested against a real spin loop: detected, whole process tree killed, rc=2 so
STALL reads differently from FAIL in the summary.
--- FR ---
Le PKGBUILD de python cherche un numéro d'affichage X libre avant make :
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run n'est pas installé ici. Il sort en 127, le `!` inverse, et la boucle
essaie le numéro suivant. Il n'existe aucune condition de sortie pour « la
commande n'existe pas », et le 2>/dev/null avale la seule ligne qui l'aurait
dit.
Mesuré avant que quiconque s'en aperçoive : dix heures d'horloge, quatre
millions de PID, un journal figé à `configure: creating Makefile`. Tous les
signaux disaient que le build allait bien — vivant, 40 % de CPU, dans un
répertoire source plausible. Une liste de processus ne distingue pas le
travail du sur-place. Un journal figé à côté d'un makepkg vivant, si — et
c'est tout ce que fait ce garde-fou.
Quarante-cinq minutes de silence COMPLET, car de vrais builds se taisent :
une longue édition de liens, une suite de tests muette, gcc entre deux étages.
Le cas qui l'a motivé a tourné deux cents fois plus longtemps. EL_STALL_MIN=0
le désactive.
Éprouvé contre une vraie boucle infinie : détecté, arbre de processus tué en
entier, rc=2 pour que STALL se lise autrement que FAIL dans le bilan.
Assisted-by: Claude Opus 5
2026-08-19 19:53:46 -04:00
|
|
|
if build_watched "$p" "$WORK/log-$p.txt"; then
|
[ADD] stage 1: build a self-hosting core for s390x
Forty-odd packages in link-time order, from linux-api-headers to
pacman itself. The order follows what a compiler actually needs, not
pacman metadata: --nodeps means nothing is ever checked, so anything
required at link time has to exist already.
A failure does not stop the run. One missing package must not hide the
state of the forty that follow, so failures are collected and reported
at the end, each with its own log. A state file makes the run
resumable, which matters when a single gcc build is measured in tens
of minutes.
The header states why stage 1 is not the port: everything here links
against the host glibc, because Arch glibc needs an Arch gcc which
needs an Arch glibc. Stages 2 and 3 break that circle.
bootstrap-pacman.sh now guards its own main so it can be sourced for
build_package without re-running the whole bootstrap.
--- FR ---
Une quarantaine de paquets dans l ordre des dependances de lien, de
linux-api-headers a pacman lui-meme. L ordre suit ce dont un
compilateur a reellement besoin, pas les metadonnees de pacman :
--nodeps ne verifie jamais rien, donc tout ce qui sert au lien doit
deja exister.
Un echec n arrete pas la course. Un paquet manquant ne doit pas
masquer l etat des quarante suivants : les echecs sont collectes et
rapportes a la fin, chacun avec son journal. Un fichier d etat rend la
reprise possible, ce qui compte quand un seul gcc se compte en
dizaines de minutes.
L en-tete dit pourquoi l etage 1 n est pas le portage : tout s y lie a
la glibc de l hote, parce que la glibc d Arch reclame un gcc d Arch
qui reclame une glibc d Arch. Les etages 2 et 3 brisent ce cercle.
bootstrap-pacman.sh garde desormais son main pour etre sourcable et
fournir build_package sans relancer tout l amorcage.
Assisted-by: Claude Opus 5
2026-08-15 15:48:30 -04:00
|
|
|
mark "$p"; ok=$((ok + 1))
|
|
|
|
|
echo "OK $p"
|
|
|
|
|
else
|
[FIX] driver: kill a build that has stopped producing output
python's PKGBUILD hunts for a free X display before running make:
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run is not installed here. It exits 127, `!` inverts that to true, and
the loop tries the next number. There is no exit condition for "the command
does not exist", and the 2>/dev/null swallows the one line that would have
said so.
Measured before anyone noticed: ten hours of wall clock, four million PIDs,
and a log frozen at `configure: creating Makefile`. Every signal said the
build was healthy -- alive, 40% CPU, sitting in a plausible source directory.
A process list cannot tell work from spinning. A stalled log next to a live
makepkg can, and that is all this guard does.
Forty-five minutes of COMPLETE silence, because real builds do go quiet: a
long link, a test suite that prints nothing, gcc between bootstrap stages.
The case this was written for ran two hundred times longer. EL_STALL_MIN=0
disables it.
Tested against a real spin loop: detected, whole process tree killed, rc=2 so
STALL reads differently from FAIL in the summary.
--- FR ---
Le PKGBUILD de python cherche un numéro d'affichage X libre avant make :
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run n'est pas installé ici. Il sort en 127, le `!` inverse, et la boucle
essaie le numéro suivant. Il n'existe aucune condition de sortie pour « la
commande n'existe pas », et le 2>/dev/null avale la seule ligne qui l'aurait
dit.
Mesuré avant que quiconque s'en aperçoive : dix heures d'horloge, quatre
millions de PID, un journal figé à `configure: creating Makefile`. Tous les
signaux disaient que le build allait bien — vivant, 40 % de CPU, dans un
répertoire source plausible. Une liste de processus ne distingue pas le
travail du sur-place. Un journal figé à côté d'un makepkg vivant, si — et
c'est tout ce que fait ce garde-fou.
Quarante-cinq minutes de silence COMPLET, car de vrais builds se taisent :
une longue édition de liens, une suite de tests muette, gcc entre deux étages.
Le cas qui l'a motivé a tourné deux cents fois plus longtemps. EL_STALL_MIN=0
le désactive.
Éprouvé contre une vraie boucle infinie : détecté, arbre de processus tué en
entier, rc=2 pour que STALL se lise autrement que FAIL dans le bilan.
Assisted-by: Claude Opus 5
2026-08-19 19:53:46 -04:00
|
|
|
rc=$?
|
[ADD] stage 1: build a self-hosting core for s390x
Forty-odd packages in link-time order, from linux-api-headers to
pacman itself. The order follows what a compiler actually needs, not
pacman metadata: --nodeps means nothing is ever checked, so anything
required at link time has to exist already.
A failure does not stop the run. One missing package must not hide the
state of the forty that follow, so failures are collected and reported
at the end, each with its own log. A state file makes the run
resumable, which matters when a single gcc build is measured in tens
of minutes.
The header states why stage 1 is not the port: everything here links
against the host glibc, because Arch glibc needs an Arch gcc which
needs an Arch glibc. Stages 2 and 3 break that circle.
bootstrap-pacman.sh now guards its own main so it can be sourced for
build_package without re-running the whole bootstrap.
--- FR ---
Une quarantaine de paquets dans l ordre des dependances de lien, de
linux-api-headers a pacman lui-meme. L ordre suit ce dont un
compilateur a reellement besoin, pas les metadonnees de pacman :
--nodeps ne verifie jamais rien, donc tout ce qui sert au lien doit
deja exister.
Un echec n arrete pas la course. Un paquet manquant ne doit pas
masquer l etat des quarante suivants : les echecs sont collectes et
rapportes a la fin, chacun avec son journal. Un fichier d etat rend la
reprise possible, ce qui compte quand un seul gcc se compte en
dizaines de minutes.
L en-tete dit pourquoi l etage 1 n est pas le portage : tout s y lie a
la glibc de l hote, parce que la glibc d Arch reclame un gcc d Arch
qui reclame une glibc d Arch. Les etages 2 et 3 brisent ce cercle.
bootstrap-pacman.sh garde desormais son main pour etre sourcable et
fournir build_package sans relancer tout l amorcage.
Assisted-by: Claude Opus 5
2026-08-15 15:48:30 -04:00
|
|
|
fail=$((fail + 1)); failed+=("$p")
|
[FIX] driver: kill a build that has stopped producing output
python's PKGBUILD hunts for a free X display before running make:
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run is not installed here. It exits 127, `!` inverts that to true, and
the loop tries the next number. There is no exit condition for "the command
does not exist", and the 2>/dev/null swallows the one line that would have
said so.
Measured before anyone noticed: ten hours of wall clock, four million PIDs,
and a log frozen at `configure: creating Makefile`. Every signal said the
build was healthy -- alive, 40% CPU, sitting in a plausible source directory.
A process list cannot tell work from spinning. A stalled log next to a live
makepkg can, and that is all this guard does.
Forty-five minutes of COMPLETE silence, because real builds do go quiet: a
long link, a test suite that prints nothing, gcc between bootstrap stages.
The case this was written for ran two hundred times longer. EL_STALL_MIN=0
disables it.
Tested against a real spin loop: detected, whole process tree killed, rc=2 so
STALL reads differently from FAIL in the summary.
--- FR ---
Le PKGBUILD de python cherche un numéro d'affichage X libre avant make :
export servernum=99
while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do
servernum=$((servernum+1)); done
xvfb-run n'est pas installé ici. Il sort en 127, le `!` inverse, et la boucle
essaie le numéro suivant. Il n'existe aucune condition de sortie pour « la
commande n'existe pas », et le 2>/dev/null avale la seule ligne qui l'aurait
dit.
Mesuré avant que quiconque s'en aperçoive : dix heures d'horloge, quatre
millions de PID, un journal figé à `configure: creating Makefile`. Tous les
signaux disaient que le build allait bien — vivant, 40 % de CPU, dans un
répertoire source plausible. Une liste de processus ne distingue pas le
travail du sur-place. Un journal figé à côté d'un makepkg vivant, si — et
c'est tout ce que fait ce garde-fou.
Quarante-cinq minutes de silence COMPLET, car de vrais builds se taisent :
une longue édition de liens, une suite de tests muette, gcc entre deux étages.
Le cas qui l'a motivé a tourné deux cents fois plus longtemps. EL_STALL_MIN=0
le désactive.
Éprouvé contre une vraie boucle infinie : détecté, arbre de processus tué en
entier, rc=2 pour que STALL se lise autrement que FAIL dans le bilan.
Assisted-by: Claude Opus 5
2026-08-19 19:53:46 -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/log-$p.txt)"
|
|
|
|
|
fi
|
[ADD] stage 1: build a self-hosting core for s390x
Forty-odd packages in link-time order, from linux-api-headers to
pacman itself. The order follows what a compiler actually needs, not
pacman metadata: --nodeps means nothing is ever checked, so anything
required at link time has to exist already.
A failure does not stop the run. One missing package must not hide the
state of the forty that follow, so failures are collected and reported
at the end, each with its own log. A state file makes the run
resumable, which matters when a single gcc build is measured in tens
of minutes.
The header states why stage 1 is not the port: everything here links
against the host glibc, because Arch glibc needs an Arch gcc which
needs an Arch glibc. Stages 2 and 3 break that circle.
bootstrap-pacman.sh now guards its own main so it can be sourced for
build_package without re-running the whole bootstrap.
--- FR ---
Une quarantaine de paquets dans l ordre des dependances de lien, de
linux-api-headers a pacman lui-meme. L ordre suit ce dont un
compilateur a reellement besoin, pas les metadonnees de pacman :
--nodeps ne verifie jamais rien, donc tout ce qui sert au lien doit
deja exister.
Un echec n arrete pas la course. Un paquet manquant ne doit pas
masquer l etat des quarante suivants : les echecs sont collectes et
rapportes a la fin, chacun avec son journal. Un fichier d etat rend la
reprise possible, ce qui compte quand un seul gcc se compte en
dizaines de minutes.
L en-tete dit pourquoi l etage 1 n est pas le portage : tout s y lie a
la glibc de l hote, parce que la glibc d Arch reclame un gcc d Arch
qui reclame une glibc d Arch. Les etages 2 et 3 brisent ce cercle.
bootstrap-pacman.sh garde desormais son main pour etre sourcable et
fournir build_package sans relancer tout l amorcage.
Assisted-by: Claude Opus 5
2026-08-15 15:48:30 -04:00
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
echo
|
|
|
|
|
echo "== stage 1: $ok built, $fail failed =="
|
|
|
|
|
[ "$fail" -eq 0 ] || printf ' failed: %s\n' "${failed[*]}"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
main "$@"
|