diff --git a/patches/pkgbuild/cmake.sh b/patches/pkgbuild/cmake.sh new file mode 100755 index 0000000..557f110 --- /dev/null +++ b/patches/pkgbuild/cmake.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# cmake: --qt-gui, on a machine with no display and no Qt. +# +# CMake Error at Source/QtDialog/CMakeLists.txt:33 (message): +# Error when bootstrapping CMake: +# +# cmake-gui is a Qt application. Arch lists qt6-base in makedepends and builds +# it; this port has no Qt and will not grow one to give a bootstrap a dialog +# box. Same reasoning as pinentry's Qt front end, and the cost is the same +# thing: a graphical program nothing here can display. +# +# WHY cmake IS IN STAGE 1 AT ALL, since it is worth being clear that this is +# not scope creep: four PKGBUILDs call cmake directly, and stage 2 rebuilds +# every package inside a chroot that has to contain the build systems. cmake +# is a tool the REBUILD needs, not a dependency of anything in the repository +# -- which is why the dependency resolver never asked for it. +# +# --sphinx-man and --sphinx-html stay: sphinx-build is installed, and the man +# pages are worth having. +# +# The makedepends entry goes with the flag. Nothing checks it under --nodeps, +# but a package declaring a build dependency it does not use is the same +# untruth as gcc-libs declaring libhwasan -- and leaving it would tell the +# next reader that Qt was needed here. +set -euo pipefail +python3 - <<'ZZPY' +import io +s = io.open("PKGBUILD", encoding="utf-8").read() + +old = " --qt-gui \\\n" +assert s.count(old) == 1, "cmake: expected exactly one --qt-gui" +s = s.replace(old, "", 1) + +old = " qt6-base)" +assert s.count(old) == 1, "cmake: makedepends tail not in the expected form" +s = s.replace(old, " )", 1) + +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +ZZPY +grep -q -- '--qt-gui' PKGBUILD && { echo "cmake: --qt-gui survived" >&2; exit 1; } +sed -n '/^makedepends=(/,/)/p' PKGBUILD | grep -q 'qt6-base' && { + echo "cmake: qt6-base still in makedepends" >&2; exit 1; } +grep -q -- '--sphinx-man' PKGBUILD || { + echo "cmake: man pages lost with the GUI" >&2; exit 1; } +echo "cmake: --qt-gui dropped (no Qt, no display)" diff --git a/patches/pkgbuild/python.sh b/patches/pkgbuild/python.sh new file mode 100755 index 0000000..84d2628 --- /dev/null +++ b/patches/pkgbuild/python.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# python: an unbounded loop that hunts for a free X display number. +# +# build() ends with: +# +# export servernum=99 +# while ! xvfb-run -a -n "$servernum" /bin/true 2>/dev/null; do +# servernum=$((servernum+1)) +# done +# LC_CTYPE=en_US.UTF-8 xvfb-run -s "-screen 0 1920x1080x16 ..." -a \ +# -n "$servernum" make EXTRA_CFLAGS="$CFLAGS" +# +# xvfb-run is not installed here. It fails with 127, `!` inverts that to true, +# servernum increments, and the loop tries again. FOREVER. There is no exit +# condition for "the command does not exist" -- the loop can only end by +# finding a display, and it never will. +# +# THE TRAP IS THE `2>/dev/null`. It swallows the one line that would have +# explained everything. Measured before it was killed: ten hours of wall +# clock, four million PIDs consumed, and a build log frozen at +# `configure: creating Makefile` -- not one byte of output in ten hours. strace +# showed clone/wait4 pairs returning status 127 and no execve at all, because +# bash gives up before exec when a command is not on PATH. +# +# It also defeated every heuristic I had for "is the build progressing": +# the process was alive, burning 40% CPU, with a plausible cwd of +# .../pkg/python/src/Python-3.14.7. A stalled LOG next to a running makepkg is +# the signal that finds this; a process list is not. +# +# WHY REMOVE RATHER THAN INSTALL xvfb. Arch wraps the build in a virtual +# display so that steps which touch tkinter or IDLE have one. A headless +# `make` does not need it -- and stage 2 rebuilds this inside a chroot that +# will have no xvfb either, so installing it on the host would fix one stage +# and leave the other spinning for ten hours. The wrapper goes, in both. +# +# THE SAME LOOP IS IN check(), and that one matters more, not less. Stage 1 +# passes --nocheck so it never ran -- but stage 2 drops --nocheck on purpose, +# because that is where a test suite finally means something. It would have +# spun there for ten hours too, on a build nobody was watching. Both copies +# go, and the guard counts them rather than trusting one substitution. +# +# xorg-server-xvfb leaves makedepends with them: it is a package this port +# does not build, so declaring it would be one more entry asking for +# something that will never exist -- the class of gcc-libs/libhwasan. +set -euo pipefail +python3 - <<'PYX' +import io, re +s = io.open("PKGBUILD", encoding="utf-8").read() + +# (a) BOTH loops -- build() and check() -- comment line included. +pat = (r"\n *# Obtain next free server number[^\n]*\n" + r" *export servernum=99\n" + r" *while ! xvfb-run[^\n]*\n") +n = len(re.findall(pat, s)) +assert n == 2, "python: expected 2 xvfb servernum loops, found %d" % n +s = re.sub(pat, "\n", s) + +# (b) every xvfb-run wrapper, whatever it wraps. +pat2 = r'xvfb-run (?:-s "[^"]*" )?-a -n "\$servernum" ' +m = len(re.findall(pat2, s)) +assert m >= 1, "python: no xvfb-run wrapper found" +s = re.sub(pat2, "", s) + +# (c) the makedepend on a package this port does not build. +old = "'gdb' 'xorg-server-xvfb' 'ttf-font'" +assert s.count(old) == 1, "python: makedepends not in the expected form" +s = s.replace(old, "'gdb' 'ttf-font'", 1) + +assert "xvfb" not in s, "python: an xvfb reference survived" +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +print("python: %d xvfb wrapper(s) and 2 loops removed" % m) +PYX +grep -q 'xvfb' PKGBUILD && { echo "python: xvfb still referenced" >&2; exit 1; } +grep -qF 'LC_CTYPE=en_US.UTF-8 make EXTRA_CFLAGS="$CFLAGS"' PKGBUILD || { + echo "python: the build make line was not rebuilt correctly" >&2; exit 1; } +grep -q 'servernum' PKGBUILD && { + echo "python: a servernum reference survived" >&2; exit 1; } +echo "python: xvfb-run wrapper and its unbounded servernum loop removed" diff --git a/scripts/bootstrap-pacman.sh b/scripts/bootstrap-pacman.sh index 7da8697..4a27bbf 100755 --- a/scripts/bootstrap-pacman.sh +++ b/scripts/bootstrap-pacman.sh @@ -119,6 +119,36 @@ install_host_deps() { # stage 1 only because STAGE 2 needs it: 51 of the 159 PKGBUILDs take their # sources from git+https, and makepkg validates that clone even under # --noextract. Nothing in the repository depends on git. + # + # libmpdec-dev is python's, and it is not optional: Python 3.13 removed the + # vendored libmpdec, so --with-system-libmpdec is the only way to build and + # it needs the system headers. Without them the failure is + # + # No rule to make target 'Modules/_decimal/libmpdec/basearith.h' + # + # nine hundred lines into a PGO build -- a missing header reported as a + # missing make rule for a file that used to be vendored. + # + # The last line is the meson/cmake round, and every entry is a tool those + # two need to describe themselves: setuptools-scm and yaml for meson's own + # build, sphinx for libuv's docs, rhash and nlohmann-json for cmake and + # cppdap. Note what they are NOT: none is a dependency of the software + # being ported. They are the cost of the build system, which is why the + # dependency resolver never mentioned one of them. + # + # strictyaml, not yaml: meson's docs/meson.build asks for the module by + # name and PyYAML does not answer to it. `python3-yaml` was installed + # first, the error did not move, and only makedepends said which one -- + # `python-strictyaml`. Reading the PKGBUILD would have been quicker than + # reading the error. + # + # cmake asked for four of these one at a time -- rhash, then jsoncpp, then + # cppdap -- because its bootstrap prefers a system copy of everything it + # bundles. That is a queue, and the rule says a queue is a feature to + # disable. It is not applied here for once, and deliberately: each one + # exists as an Ubuntu -dev package, so the queue terminates. The rule is + # about queues with no end, like dbus asking for a third documentation + # tool that needed Qt. sudo apt-get -o DPkg::Lock::Timeout=600 install -y -qq \ meson ninja-build pkg-config gettext \ libarchive-dev libcurl4-openssl-dev libgpgme-dev libssl-dev \ @@ -145,7 +175,10 @@ install_host_deps() { autoconf-archive ducktype \ yelp-tools liblmdb-dev libcmocka-dev libverto-dev uthash-dev \ libtasn1-bin libevent-dev libaio-dev mercurial libnspr4-dev \ - libglib2.0-dev libsecret-1-dev + libglib2.0-dev libsecret-1-dev libmpdec-dev \ + python3-setuptools-scm python3-yaml python3-sphinx \ + librhash-dev nlohmann-json3-dev python3-strictyaml libjsoncpp-dev \ + libcppdap-dev install_host_shims } diff --git a/scripts/build-stage2.sh b/scripts/build-stage2.sh index e1027e1..a817980 100755 --- a/scripts/build-stage2.sh +++ b/scripts/build-stage2.sh @@ -69,6 +69,10 @@ CHROOT_PKGS=( # git: 51 PKGBUILDs use git sources, and makepkg validates the clone even # under --noextract. git + # 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 ) log() { printf '\n== %s ==\n' "$*"; }