Files
gastownhall__beads/scripts/build-examples.sh
T

96 lines
4.0 KiB
Bash
Raw Normal View History

fix(examples): tidy both example modules and build them in CI (#5229) * fix(examples): tidy both example modules and build them in CI Both Go modules under examples/ fail a plain `go build` on current main: go: updates to go.mod needed; to update it: go mod tidy They are separate modules that reach the parent through `replace github.com/steveyegge/beads => ../..`, so their go.mod and go.sum record the parent's entire dependency graph. Every root dependency change therefore invalidates them — and nothing in CI ever compiled them, so the drift accumulated silently. examples/ is the first code a new user copies, which makes this a bad first five minutes rather than a cosmetic wart. Two parts: 1. `go mod tidy` in examples/bd-example-extension-go and examples/library-usage. The extension example carries most of the churn (~1.6k lines of go.sum), which is inherent to recording the parent graph through the replace directive and is why an earlier fix (#4942, the Go 1.26.5 bump) deliberately left it out. Both modules now build clean with the project's canonical `-tags gms_pure_go`. 2. scripts/build-examples.sh plus a `build-examples` job in the PR workflow, so the same drift cannot recur unnoticed. The script discovers example modules from `git ls-files 'examples/*/go.mod'` (no hardcoded list), sources .buildflags for the canonical CGO/tag settings, builds into a scratch directory so it leaves no untracked binaries, reports every failing module rather than stopping at the first, and prints the exact `go mod tidy` command to run. The job is deliberately NOT added to ci-gate's required list. Because of the replace directive it would fail on any root go.mod change not mirrored into the examples, so making it blocking imposes a "tidy the examples too" step on every dependency bump. That is a maintainer call about contributor friction; the job is visible on the checks list either way, and pr.yml records how to promote it. Verified locally: both modules build with -tags gms_pure_go; the script exits 1 and names the module when an example go.mod is reverted to its pre-tidy state; scripts/check-build-tags.sh stays clean (the script sources .buildflags). Agent-Signature: claude-opus-5-high on behalf of matt wilkie * fix(examples): address dual-vendor review — vet not build, advisory job, portability Reviewed by a claude reviewer and scripts/codex-agent reviewer (gpt-5.6-sol). Four fixes, two of them correctness. 1. `go build -o <dir>/ ./...` was a FALSE GREEN: with -o naming a directory, Go compiles only the MAIN packages and silently skips every library package. Reproduced in an isolated module with a good main package plus a library package containing a type error — `go build -o dir/ ./...` exits 0 while `go build ./...` and `go vet ./...` both exit 1. The mirror-image bug: a library-only example module fails `go build` with "no main packages to build", a false red. Switched to `go vet ./...`, which type-checks every package INCLUDING test files (examples/library-usage/main_test.go exercises a lot of live API, and `go mod tidy` counts its imports, so a build that never compiles it left part of the recorded graph unverified), writes no artifacts, and still fails on the stale-go.mod condition this exists for. That also deletes the mktemp/trap/scratch-dir machinery entirely. 2. "Not in ci-gate" is NOT "non-blocking", which the previous comment claimed. Verified in source: pr-preflight.sh gates on every FAILURE in the raw statusCheckRollup and calls block(), and pr-babysit requires all rollup entries SUCCESS/NEUTRAL/SKIPPED before merging. Both ignore ci-gate membership. So the previous state was the one posture that stalls the merge patrol repo-wide after any un-mirrored dependency bump while advertising itself as optional. The job is now continue-on-error: true — genuinely advisory — and the comment records how to promote it to a real gate. Whether it SHOULD be a gate remains the maintainers' call. 3. Portability and robustness in the script, all reproduced by the reviewers: - `mapfile` does not exist in bash 3.2 (stock macOS) and `xargs -r` is GNU-only, so the advertised local check could not run on macOS. Replaced with a NUL-delimited read loop, which also fixes module paths containing whitespace (`git ls-files | xargs -n1 dirname` turned "examples/has space/go.mod" into two bogus entries). - `source ./.buildflags` was unguarded under `set -uo pipefail`; a failure continued with GOFLAGS unset and would type-check the ICU path while check-build-tags.sh still passed, since that only greps for the literal string. Now a hard exit. - Finding zero modules exited 0. A job that checks nothing must not report success; it is now an error. Verified: both modules vet clean; reverting an example go.mod to its pre-tidy state still exits 1 with the exact `go mod tidy` command; a broken library package now fails where it previously passed; check-build-tags.sh clean (97 files); shellcheck clean. Agent-Signature: claude-opus-5-high on behalf of matt wilkie * fix(examples): drop GNU-only sort -z from the module discovery pipeline BSD sort has no -z, so on stock macOS — the exact platform the Bash-3.2 compatibility block targets — the sort stage emptied the pipeline and the script exited claiming 'found no example modules'. git ls-files output is already sorted, so the stage bought nothing. Found by cross-vendor review (codex gpt-5.6-sol) of this branch. Agent-Signature: claude-fable-5-high on behalf of maphew * fix(ci,examples): disable setup-go cache in build-examples; re-tidy examples after merge TestGoCacheOwnershipTopology requires every setup-go step in pr.yml to set cache: false (caching is owned by explicit restore/save steps); the new build-examples job predates that policy landing on main. Also re-run go mod tidy in both example modules so their recorded dependency graphs match the merged main — exactly the drift this PR's CI job exists to catch. Agent-Signature: claude-fable-5-high on behalf of maphew * ci(examples): bound build-examples with timeout-minutes: 10 continue-on-error keeps a red result advisory, but a hung job at the 6-hour default timeout holds the check pending, stalling merge-lane consumers the advisory posture was meant to protect. Matches pr-preflight-platforms' bound. Agent-Signature: claude-fable-5-high on behalf of maphew * ci(examples): make the advisory posture real - step-level continue-on-error Job-level continue-on-error still reports the check run as FAILURE in the PR rollup (only the workflow-run conclusion flips), and pr-preflight / pr-babysit gate on per-check-run conclusions - so the job as written was de facto blocking while advertising itself advisory. Follow the cygwin-leg precedent: continue-on-error on the build step, outcome-guarded ::warning annotation on failure. Also restore the repo-wide Go module cache (the examples' graph is the parent's via the replace directive) so the job stops cold-downloading the full dependency graph on every PR. Agent-Signature: claude-fable-5-high on behalf of maphew * test: register build-examples' module-cache restore in the cache topology registry TestGoCacheOwnershipTopology keeps an explicit inventory of every cache step per job; the advisory-lane fix added a restore step to build-examples without registering it, so the whole scripts package went red on this head. Register the job (inventory, managed map, ordering, setup-go id) and drop the step's unused 'restore-cache' id — the registry compares ids, and nothing references it. Agent-Signature: claude-fable-5-high on behalf of maphew Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019CwiLhLbAGZdJtKYT76CPp --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 21:16:27 -07:00
#!/usr/bin/env bash
# build-examples.sh — type-check every Go module under examples/.
#
# The example modules are separate Go modules that reach the parent through a
# `replace github.com/steveyegge/beads => ../..` directive, so their go.mod and
# go.sum record the parent's full dependency graph. Nothing in CI compiled them,
# which meant they went stale silently: on 2026-08-01 both
# examples/bd-example-extension-go and examples/library-usage failed a plain
# `go build` on main with "updates to go.mod needed; to update it: go mod tidy".
# Examples are the first code a new user copies, so a broken one is a bad first
# five minutes.
#
# Usage: scripts/build-examples.sh
#
# Exits non-zero listing every module that failed, so one broken example does
# not hide another.
#
# Why `go vet` and not `go build`: with `-o <dir>/`, `go build ./...` compiles
# only the MAIN packages and silently skips every library package — a module
# with a broken internal package would pass. Without `-o` it litters the tree
# with binaries. `go vet ./...` type-checks every package INCLUDING test files
# (examples/library-usage/main_test.go exercises a lot of live API, and
# `go mod tidy` counts its imports, so a build that never compiles it leaves
# part of the recorded dependency graph unverified), writes no artifacts, and
# still fails on the stale-go.mod condition this script exists for. It also
# avoids `go build`'s "no main packages to build" false failure on a
# library-only example module.
set -uo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" || exit 1
cd "$REPO_ROOT" || exit 1
# Canonical build settings (CGO_ENABLED, -tags=gms_pure_go via GOFLAGS). The
# examples link the same go-mysql-server that pulls in ICU under cgo, so they
# need the project tag exactly as the main build does. Guarded: with no errexit,
# a failed source would otherwise continue with GOFLAGS unset and silently
# type-check the ICU path instead, while check-build-tags.sh still passes (it
# only greps for the literal string `.buildflags`).
# shellcheck source=/dev/null
source ./.buildflags || { echo "build-examples: cannot source .buildflags" >&2; exit 1; }
# Bash 3.2 compatible (stock macOS ships 3.2, which has no `mapfile`), and no
# GNU-only `xargs -r` or `sort -z` (BSD sort has no -z; a sort stage would have
# emptied the pipeline on macOS — and `git ls-files` output is already sorted).
# NUL-delimited so a path containing whitespace cannot be split —
# `git ls-files | xargs -n1 dirname` turned "examples/has space/go.mod" into
# two bogus entries.
modules=()
while IFS= read -r -d '' f; do
modules+=("$(dirname "$f")")
done < <(git ls-files -z 'examples/*/go.mod')
if [[ "${#modules[@]}" -eq 0 ]]; then
# Not a pass. Either examples/ was restructured, or this is running outside
# a git checkout (a release tarball), and a job that compiles nothing must
# not report success.
echo "build-examples: found no example modules (git ls-files 'examples/*/go.mod' was empty)" >&2
echo "build-examples: refusing to report success having checked nothing" >&2
exit 1
fi
failed=()
for mod in "${modules[@]}"; do
echo "==> checking $mod"
if ( cd "$mod" && go vet ./... ); then
echo " ok"
else
echo " FAILED" >&2
failed+=("$mod")
fi
done
if [[ "${#failed[@]}" -eq 0 ]]; then
echo "build-examples: ${#modules[@]} example module(s) type-check cleanly"
exit 0
fi
{
echo
echo "build-examples: ${#failed[@]} of ${#modules[@]} example module(s) failed:"
printf ' %s\n' "${failed[@]}"
echo
echo "If the error is \"updates to go.mod needed\", the example's recorded"
echo "dependency graph has drifted from the parent module — usually because a"
echo "change to the root go.mod was not mirrored into the examples. Fix with:"
echo
for mod in "${failed[@]}"; do
echo " (cd $mod && go mod tidy)"
done
echo
echo "then commit the resulting go.mod/go.sum changes."
} >&2
exit 1