|
1 |
+ |
#!/usr/bin/env bash
|
|
2 |
+ |
#
|
|
3 |
+ |
# check-rust-stage.sh — build the Containerfile's `rust-build` stage and
|
|
4 |
+ |
# nothing else, to prove the image still compiles.
|
|
5 |
+ |
#
|
|
6 |
+ |
# The stage that breaks silently. It clones shop at ${SHOP_REV} and builds it
|
|
7 |
+ |
# with the console, so it is the one part of the image whose inputs live in
|
|
8 |
+ |
# another repo and move without any edit here. On 2026-08-09 shop@53551eb added
|
|
9 |
+ |
# `fontconfig = "0.11"`, yeslogic-fontconfig-sys links libfontconfig through
|
|
10 |
+ |
# pkg-config rather than dlopening it, and the stage installed
|
|
11 |
+ |
# `wayland-devel libxkbcommon-devel pkgconf` and nothing else. `cargo install`
|
|
12 |
+ |
# died on both profiles. The broken commit reached astra, srht and mnw before
|
|
13 |
+ |
# anyone built it, because a SHOP_REV bump is a one-line edit that looks like
|
|
14 |
+ |
# it cannot fail.
|
|
15 |
+ |
#
|
|
16 |
+ |
# Why a build rather than a static check. The obvious cheap guard is a test
|
|
17 |
+ |
# that reads shop's dependencies and asserts every one has its -devel package
|
|
18 |
+ |
# on the dnf line. It was rejected: it needs a crate-name to package-name table
|
|
19 |
+ |
# that is not mechanical (yeslogic-fontconfig-sys -> fontconfig-devel), and it
|
|
20 |
+ |
# passes whenever shop adds a dependency the table has not learned — a guard
|
|
21 |
+ |
# whose correctness depends on the same discipline that failed in the first
|
|
22 |
+ |
# place. Building the stage proves the property directly and needs to know
|
|
23 |
+ |
# nothing about what shop links. It also catches every other way this stage can
|
|
24 |
+ |
# break, rather than the one way it broke last.
|
|
25 |
+ |
#
|
|
26 |
+ |
# Only the builder. `--target rust-build` stops before the bootc stage, so this
|
|
27 |
+ |
# skips the 7 GB runtime, the fonts, the browser and the profile split. What is
|
|
28 |
+ |
# left is the toolchain layer, shop, and the console.
|
|
29 |
+ |
#
|
|
30 |
+ |
# Usage:
|
|
31 |
+ |
# build/check-rust-stage.sh # build it; say what happened
|
|
32 |
+ |
#
|
|
33 |
+ |
# Run it after moving SHOP_REV, before pushing. The astra sweep runs it nightly
|
|
34 |
+ |
# (`rust-stage` in sweep.toml), which is the backstop for when nobody does.
|
|
35 |
+ |
#
|
|
36 |
+ |
# Exit codes follow refresh-base-digests.sh, for the same reason:
|
|
37 |
+ |
#
|
|
38 |
+ |
# 0 the stage builds.
|
|
39 |
+ |
# 1 the stage does not build. The image is unbuildable and, since the
|
|
40 |
+ |
# install path is "build it yourself" (docs/IMAGE.md), so is Alloy.
|
|
41 |
+ |
# 3 something about the run, not about the image: no podman, no network, a
|
|
42 |
+ |
# registry or a git remote nobody could reach. Prints `error:`, which the
|
|
43 |
+ |
# sweep turns into an error cell rather than a finding.
|
|
44 |
+ |
#
|
|
45 |
+ |
# Rootless on purpose, unlike build-image.sh, which needs `sudo podman` for the
|
|
46 |
+ |
# bootc conversion. Nothing here leaves the builder stage, so the root store is
|
|
47 |
+ |
# not involved and a check should not ask for privilege it does not need. The
|
|
48 |
+ |
# consequence worth knowing: this populates the *rootless* layer cache, so it
|
|
49 |
+ |
# does not warm a subsequent `build/build-image.sh`.
|
|
50 |
+ |
|
|
51 |
+ |
set -Eeuo pipefail
|
|
52 |
+ |
|
|
53 |
+ |
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
54 |
+ |
CONTAINERFILE="$REPO_ROOT/Containerfile"
|
|
55 |
+ |
|
|
56 |
+ |
# Replaced rather than accumulated. An untagged `--target` build leaves a
|
|
57 |
+ |
# dangling image behind every night, and a nightly cell is exactly the thing
|
|
58 |
+ |
# that fills a disk quietly.
|
|
59 |
+ |
IMAGE="localhost/alloy-rust-stage:check"
|
|
60 |
+ |
|
|
61 |
+ |
# Exit 3, never 1: 1 is reserved for "the image does not build", and a caller
|
|
62 |
+ |
# that cannot tell a broken build from a broken network learns the wrong thing
|
|
63 |
+ |
# from both.
|
|
64 |
+ |
die() { printf 'error: %s\n' "$*" >&2; exit 3; }
|
|
65 |
+ |
|
|
66 |
+ |
trap 'rc=$?; [ "$rc" -eq 3 ] && exit 3; die "unexpected failure at line ${LINENO}"' ERR
|
|
67 |
+ |
|
|
68 |
+ |
case "${1:-}" in
|
|
69 |
+ |
-h|--help) sed -n '2,48p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
70 |
+ |
"") ;;
|
|
71 |
+ |
*) die "unknown argument: $1 (see --help)" ;;
|
|
72 |
+ |
esac
|
|
73 |
+ |
|
|
74 |
+ |
command -v podman >/dev/null || die "podman not found"
|
|
75 |
+ |
[ -f "$CONTAINERFILE" ] || die "no Containerfile at $CONTAINERFILE"
|
|
76 |
+ |
|
|
77 |
+ |
# Reported whatever happens, so a red cell names the revision it was red about
|
|
78 |
+ |
# without anyone opening the Containerfile at that commit.
|
|
79 |
+ |
SHOP_REV="$(grep -oE '^ARG SHOP_REV=[0-9a-f]{7,40}' "$CONTAINERFILE" | head -1 | cut -d= -f2 || true)"
|
|
80 |
+ |
[ -n "$SHOP_REV" ] || die "no ARG SHOP_REV line in $CONTAINERFILE"
|
|
81 |
+ |
printf 'building the rust-build stage; SHOP_REV=%s\n' "$SHOP_REV"
|
|
82 |
+ |
|
|
83 |
+ |
log="$(mktemp -t alloy-rust-stage.XXXXXX.log)"
|
|
84 |
+ |
trap 'rm -f "$log"' EXIT
|
|
85 |
+ |
|
|
86 |
+ |
status=0
|
|
87 |
+ |
podman build \
|
|
88 |
+ |
--target rust-build \
|
|
89 |
+ |
--pull=missing \
|
|
90 |
+ |
-t "$IMAGE" \
|
|
91 |
+ |
-f "$CONTAINERFILE" \
|
|
92 |
+ |
"$REPO_ROOT" 2>&1 | tee "$log" || status="${PIPESTATUS[0]}"
|
|
93 |
+ |
|
|
94 |
+ |
if [ "$status" -eq 0 ]; then
|
|
95 |
+ |
echo
|
|
96 |
+ |
echo "rust-build stage builds at SHOP_REV ${SHOP_REV}"
|
|
97 |
+ |
exit 0
|
|
98 |
+ |
fi
|
|
99 |
+ |
|
|
100 |
+ |
# Failed. Which kind of failed is the whole value of this script: a red cell
|
|
101 |
+ |
# saying "podman exited 1" costs a person the build they are trying to avoid.
|
|
102 |
+ |
|
|
103 |
+ |
# The evidence-hole cases first. Each of these is something outside the image:
|
|
104 |
+ |
# the registry serving the pinned base, or the git remote serving shop. The
|
|
105 |
+ |
# ls-remote wording is the Containerfile's own guard talking (it prints
|
|
106 |
+ |
# `cannot reach ... to check SHOP_REV`), and the zero-refs case is the one that
|
|
107 |
+ |
# already happened once, recorded there against GoingsOn problem 49732815.
|
|
108 |
+ |
if grep -qEi 'cannot reach .* to check SHOP_REV|serves zero refs|no such host|temporary failure in name resolution|connection refused|i/o timeout|TLS handshake timeout|error pinging container registry' "$log"; then
|
|
109 |
+ |
die "the build could not reach something it needs (registry or git remote), so this says nothing about the image"
|
|
110 |
+ |
fi
|
|
111 |
+ |
|
|
112 |
+ |
echo
|
|
113 |
+ |
echo "the rust-build stage does not build at SHOP_REV ${SHOP_REV}."
|
|
114 |
+ |
|
|
115 |
+ |
# The class this script exists for, named explicitly when it recurs. Anything
|
|
116 |
+ |
# reaching a linker or a pkg-config probe has got past the network and past
|
|
117 |
+ |
# cargo's resolver, so it is a missing header on the dnf line until proven
|
|
118 |
+ |
# otherwise.
|
|
119 |
+ |
if grep -qEi 'could not find .* pkg-config|pkg-config .* not found|No package .* found|PKG_CONFIG_PATH|cannot find -l[a-z]' "$log"; then
|
|
120 |
+ |
cat <<'EOF'
|
|
121 |
+ |
|
|
122 |
+ |
It looks like a missing system library rather than a code error: a build script
|
|
123 |
+ |
probed pkg-config, or the linker wanted a library nobody installed.
|
|
124 |
+ |
|
|
125 |
+ |
shop links its system libraries rather than dlopening them, so a SHOP_REV move
|
|
126 |
+ |
can add one. Its README's Building section is the list at any revision. Add the
|
|
127 |
+ |
Fedora -devel package to the dnf line in the rust-build stage of the
|
|
128 |
+ |
Containerfile, above the shop install.
|
|
129 |
+ |
EOF
|
|
130 |
+ |
fi
|
|
131 |
+ |
|
|
132 |
+ |
exit 1
|