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
128 lines
5.7 KiB
Bash
Executable file
128 lines
5.7 KiB
Bash
Executable file
#!/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"
|
|
source "$HERE/lib-watch.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.
|
|
# The list and its order live in one file, read by both stages -- see the
|
|
# header of packages.sh for why stage 2 must not derive its own.
|
|
source "$HERE/packages.sh"
|
|
|
|
# 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.
|
|
# The stall guard moved to bootstrap-pacman.sh when stage 2 needed it too.
|
|
build_watched() { watched "$2" build_package "$1"; }
|
|
|
|
built() { grep -qxF "$1" "$STATE" 2>/dev/null; }
|
|
# Appended only once: a forced rebuild of an already-built package must not
|
|
# leave two identical lines in the state file. built() uses grep -qxF, so a
|
|
# duplicate is harmless to the logic and confusing to a reader counting lines.
|
|
mark() { built "$1" || echo "$1" >> "$STATE"; }
|
|
|
|
main() {
|
|
mkdir -p "$WORK/pkg" "$REPO"
|
|
touch "$STATE"
|
|
# 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
|
|
# Named packages rebuild on demand, the way stage 2 already works.
|
|
#
|
|
# Without this, rebuilding one package means deleting its line from
|
|
# stage1.state first -- editing the record of what was built in order to
|
|
# build something. That file is the port's memory; the opening instruction
|
|
# for this work is to regenerate it from what is REALLY in repo/s390x and
|
|
# never from a log, and hand-editing it is the same mistake in a smaller
|
|
# form. Naming a package is also how a hook gets tested: libarchive's xar
|
|
# fix needed exactly one package rebuilt, not a pass over a hundred and
|
|
# ninety.
|
|
local list=("${STAGE1_PACKAGES[@]}") forced=0
|
|
if [ "$#" -gt 0 ]; then
|
|
list=("$@")
|
|
forced=1
|
|
printf '== stage 1: rebuilding on request: %s ==\n' "$*"
|
|
fi
|
|
local ok=0 fail=0 rc=0 failed=()
|
|
for p in "${list[@]}"; do
|
|
# An explicit request outranks the state file. Asking for a package
|
|
# that is already built and being told it was skipped would make the
|
|
# command useless for the one job it exists to do.
|
|
if [ "$forced" -eq 0 ] && 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.
|
|
if build_watched "$p" "$WORK/log-$p.txt"; then
|
|
mark "$p"; ok=$((ok + 1))
|
|
echo "OK $p"
|
|
else
|
|
rc=$?
|
|
fail=$((fail + 1)); failed+=("$p")
|
|
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
|
|
fi
|
|
done
|
|
echo
|
|
echo "== stage 1: $ok built, $fail failed =="
|
|
[ "$fail" -eq 0 ] || printf ' failed: %s\n' "${failed[*]}"
|
|
}
|
|
|
|
main "$@"
|