diff --git a/TODO.md b/TODO.md index bc4c519..a724b54 100644 --- a/TODO.md +++ b/TODO.md @@ -25,6 +25,13 @@ confirm make check still passes. # Completed Steps +- 2026-08-09: `script/bootstrap` now installs the pinned `golangci-lint` + and `goimports` unconditionally instead of only when the binary is + absent from `PATH`, so the commit pins actually take effect on + already-provisioned machines; it also warns when `PATH` resolves + either tool to a copy outside the directory `go install` writes to. + The `missing` presence check is retained for `git`, `make`, and `go` + (#117) - 2026-08-07: golangci-lint bumped to v2.12.2 (commit-pinned installs in `Dockerfile` and `script/bootstrap`); `.golangci.yml` set to the org-standard v2-schema config used across the org's repos diff --git a/script/bootstrap b/script/bootstrap index 129cc77..c74ea7a 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -1,10 +1,13 @@ #!/bin/sh # script/bootstrap: install all dependencies needed to build and develop -# this repo. Idempotent: every install is guarded by a check so already -# installed tools are skipped. Base tooling comes from nix, apt, brew, -# or apk (detected in that order); assumes nothing is present. -# golangci-lint and goimports are installed via `go install` at the same -# pinned commits the Dockerfile uses (never "latest"). +# this repo. Base tooling (git, make, go) comes from nix, apt, brew, or +# apk (detected in that order) and is installed only when absent; +# assumes nothing is present. golangci-lint and goimports are always +# (re)installed via `go install` at the same pinned commits the +# Dockerfile uses (never "latest") -- a presence check cannot tell the +# pinned build from an arbitrary one already on PATH, so guarding them +# would make the pins inert. Idempotent either way: running this twice +# succeeds both times and leaves the same result. set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" @@ -62,6 +65,31 @@ missing() { ! command -v "$1" >/dev/null 2>&1 } +# go_bin_dir: directory `go install` writes binaries to. +go_bin_dir() { + gobin="$(go env GOBIN)" + if [ -n "$gobin" ]; then + echo "$gobin" + else + echo "$(go env GOPATH)/bin" + fi +} + +# warn_if_shadowed : the pinned build was just installed +# into . If PATH resolves anywhere else, that other copy is +# what `make lint` and `make fmt` will actually run, and it is not the +# pinned version. Warn loudly rather than failing, since the fix is the +# user's PATH and not anything this script can do. +warn_if_shadowed() { + resolved="$(command -v "$1" 2>/dev/null || true)" + if [ "$resolved" != "$2/$1" ]; then + echo "bootstrap: WARNING: installed pinned $1 to $2/$1, but PATH" >&2 + echo "bootstrap: WARNING: resolves $1 to ${resolved:-(not on PATH)};" >&2 + echo "bootstrap: WARNING: put $2 first on PATH or lint results will" >&2 + echo "bootstrap: WARNING: not match CI." >&2 + fi +} + main() { cd "$ROOT" @@ -69,10 +97,17 @@ main() { if missing make; then pkg_install gnumake make make make; fi if missing go; then pkg_install go golang go go; fi - # Lint/format tools, pinned via go install (installs into - # "$(go env GOPATH)/bin"; ensure that is on your PATH). - if missing golangci-lint; then go install "$GOLANGCI_LINT_REF"; fi - if missing goimports; then go install "$GOIMPORTS_REF"; fi + # Lint/format tools, pinned via go install. These are installed + # unconditionally: `command -v` only proves *some* build is on PATH, + # and a wrong golangci-lint either cannot parse our v2-schema + # .golangci.yml at all or silently disagrees with CI. Installing at + # a fixed commit ref is idempotent and cheap with a warm module + # cache, so there is nothing to save by skipping it. + GOBIN_DIR="$(go_bin_dir)" + go install "$GOLANGCI_LINT_REF" + go install "$GOIMPORTS_REF" + warn_if_shadowed golangci-lint "$GOBIN_DIR" + warn_if_shadowed goimports "$GOBIN_DIR" go mod download