diff --git a/patches/pkgbuild/lvm2.sh b/patches/pkgbuild/lvm2.sh new file mode 100755 index 0000000..d9aa6f2 --- /dev/null +++ b/patches/pkgbuild/lvm2.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# lvm2: only device-mapper is wanted, and the lvm2 half costs a Rust toolchain. +# +# The chain that puts this PKGBUILD in stage 1 is +# systemd -> cryptsetup -> device-mapper, and device-mapper is one of the two +# packages this source produces. Nothing in the repository asks for `lvm2` +# itself -- checked across every .PKGINFO, the only consumer of device-mapper +# is cryptsetup, and the only consumer of lvm2 is lvm2's own dependency on +# device-mapper. +# +# What the lvm2 half brings with it: +# +# thin-provisioning-tools -- rewritten in Rust. Its prepare() stops on +# `error: Error loading target specification: Could not find +# specification for target "host-tuple"`, and behind that stands cargo, +# a crates.io fetch and the whole Rust bootstrap question. This port +# already declined that detour once, for python-cryptography. +# +# libaio -- and not usefully. Ubuntu renamed its library libaio.so.1t64 for +# the time64 ABI transition, so the lvm2 binaries link a soname Arch has +# never had. Building Arch's libaio would not change what stage 1 links. +# +# Both leave the closure with the sub-package, which is why this is one hook +# rather than three. +# +# THE MEASUREMENT IS THE ARGUMENT, and it is the same one that went the other +# way for tcl and unixodbc an hour earlier. Those cost zero new packages, so +# building them beat dropping a sub-package. This one costs a language +# runtime, so dropping wins. The rule is not "prefer dropping" or "prefer +# building" -- it is to measure each time. +# +# Deferred: a target that wants LVM builds Rust first, restores the sub-package +# and rebuilds. device-mapper alone is what dm-crypt and systemd need. +set -euo pipefail +python3 - <<'PY' +import io +s = io.open("PKGBUILD", encoding="utf-8").read() +old = "pkgname=('lvm2' 'device-mapper')" +assert s.count(old) == 1, "lvm2: pkgname line not in the expected form" +s = s.replace(old, "pkgname=('device-mapper')", 1) +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +PY +grep -q "^pkgname=('device-mapper')$" PKGBUILD || { + echo "lvm2: sub-package not reduced to device-mapper" >&2; exit 1; } +# package_lvm2() stays in the file, uncalled -- makepkg never calls a function +# whose name is absent from pkgname, as with package_libquadmath() in gcc. +grep -q "^package_device-mapper()" PKGBUILD || { + echo "lvm2: device-mapper package function missing" >&2; exit 1; } +echo "lvm2: only device-mapper built (lvm2 would pull Rust via thin-provisioning-tools)" + +# device-mapper also picked up the host's libselinux, silently. +# +# $ readelf -d usr/bin/dmsetup | grep NEEDED +# (NEEDED) Shared library: [libselinux.so.1] +# +# Seventh package in this port to do it, and the second found by the soname +# audit rather than by a build. lvm2's configure has --disable-selinux and +# defaults to detecting; Ubuntu's libselinux1-dev is present because +# libmount-dev and libglib2.0-dev pull it in, so it detected. +# +# Arch has no libselinux package at all -- the reason coreutils, findutils, +# sed, tar and gettext each carry the same three lines. This is the sixth +# copy, and TODO.md already records that the per-package hook does not scale +# to a probe every package can trip. +python3 - <<'PY' +import io +s = io.open("PKGBUILD", encoding="utf-8").read() +assert "--disable-selinux" not in s, "lvm2: selinux already disabled, hook ran twice" +anchor = " --enable-cmdlib \\\n" +assert s.count(anchor) == 1, "lvm2: configure block not in the expected form" +s = s.replace(anchor, " --disable-selinux \\\n" + anchor, 1) +io.open("PKGBUILD", "w", encoding="utf-8").write(s) +PY +[ "$(grep -c -- '--disable-selinux' PKGBUILD)" = 1 ] || { + echo "lvm2: --disable-selinux not inserted exactly once" >&2; exit 1; } +echo "lvm2: --disable-selinux (Arch has no libselinux; the host does)" diff --git a/scripts/build-stage1.sh b/scripts/build-stage1.sh index 247cf7d..9c9dfd6 100755 --- a/scripts/build-stage1.sh +++ b/scripts/build-stage1.sh @@ -141,6 +141,19 @@ STAGE1_PACKAGES=( # curl" -- four links away from the missing name, and nothing in that # message points at libffi. libffi libevent + # 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. # And finally the package manager itself, built as an Arch package. pacman )