Check the golangci-lint version in bootstrap, not just presence (closes #24) #34
36
TODO.md
36
TODO.md
@@ -43,15 +43,33 @@
|
|||||||
(taking the field after the word `version` and tolerating an optional
|
(taking the field after the word `version` and tolerating an optional
|
||||||
leading `v`, which the module ref carries and the binary's output does
|
leading `v`, which the module ref carries and the binary's output does
|
||||||
not), and any version that is not the pin — older, newer, absent or
|
not), and any version that is not the pin — older, newer, absent or
|
||||||
unparseable — is reinstalled. `git`, `make` and `go` keep their
|
unparseable — is reinstalled. The install is then verified against the
|
||||||
presence-only checks and now say why in a comment: they are host
|
binary `PATH` actually resolves: `go install` writes into `GOBIN` (or
|
||||||
package-manager tools the repo deliberately does not pin, with
|
`GOPATH/bin`) while `make lint` runs whichever `golangci-lint` comes
|
||||||
`go.mod` governing the language version and the digest-pinned images
|
first on `PATH`, so a wrong-version one sitting ahead of it — nix,
|
||||||
covering reproducible builds. Verified on this host by bootstrapping
|
apt, brew, apk, or the `/usr/local/bin` copy the `Dockerfile` builder
|
||||||
from v2.10.1 to v2.12.2 and running it again to a no-op, plus stub
|
stage makes — would swallow the install and leave the local gate
|
||||||
runs under `dash` covering the absent, older, newer, image-style and
|
disagreeing with CI under an affirmative `bootstrap complete`.
|
||||||
leading-`v` cases; `make check` and `make lint` are clean at v2.12.2,
|
Bootstrap now re-reads the effective version after installing and, on
|
||||||
so v2.10.1 was not hiding any findings on `main`
|
a mismatch, prints both paths and both versions to stderr and exits
|
||||||
|
non-zero instead of claiming success; it does not reorder anyone's
|
||||||
|
`PATH` or delete their binary. The `--version` call keeps its stderr
|
||||||
|
connected, so a present-but-broken binary says why rather than
|
||||||
|
reinstalling forever in silence, and is bounded by `timeout(1)` where
|
||||||
|
that exists, so a wedged binary cannot hang bootstrap. `git`, `make`
|
||||||
|
and `go` keep their presence-only checks and now say why in a
|
||||||
|
comment: they are host package-manager tools the repo deliberately
|
||||||
|
does not pin, with `go.mod` governing the language version and the
|
||||||
|
digest-pinned images covering reproducible builds. Verified on this
|
||||||
|
host by bootstrapping from v2.10.1 to v2.12.2 and running it again to
|
||||||
|
a no-op, plus stub runs of the real script under `dash` covering a
|
||||||
|
thirteen-input parse matrix (absent, older, newer, host-style,
|
||||||
|
image-style, leading-`v`, stderr-only, empty, non-zero exit, impostor
|
||||||
|
binary, `(devel)`, trailing `version`), a shadowed install that must
|
||||||
|
exit non-zero, an install destination not on `PATH` at all, `GOBIN`
|
||||||
|
set, and a wedged binary that must hit the timeout; `make check` and
|
||||||
|
`make lint` are clean at v2.12.2, so v2.10.1 was not hiding any
|
||||||
|
findings on `main`
|
||||||
- unwind the hash worker pool on the error path (2026-08-09, branch
|
- unwind the hash worker pool on the error path (2026-08-09, branch
|
||||||
`hash-pool-cleanup`, closes #6): `hashPhase` used to return the
|
`hash-pool-cleanup`, closes #6): `hashPhase` used to return the
|
||||||
moment `recordRun` failed and abandon the pool — the feeder parked
|
moment `recordRun` failed and abandon the pool — the feeder parked
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
# or apk (detected in that order); assumes nothing is present.
|
# or apk (detected in that order); assumes nothing is present.
|
||||||
# golangci-lint is installed via `go install` pinned to the same version
|
# golangci-lint is installed via `go install` pinned to the same version
|
||||||
# the Dockerfile lint stage uses (never "latest"), and is reinstalled
|
# the Dockerfile lint stage uses (never "latest"), and is reinstalled
|
||||||
# whenever the installed version differs from that pin.
|
# whenever the installed version differs from that pin. The install is
|
||||||
|
# then verified against the binary PATH actually resolves: if the pin is
|
||||||
|
# still not what would run, bootstrap fails instead of reporting
|
||||||
|
# success.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
@@ -19,6 +22,11 @@ GOLANGCI_LINT_VERSION="2.12.2"
|
|||||||
GOLANGCI_LINT_MODULE="github.com/golangci/golangci-lint/v2/cmd/golangci-lint"
|
GOLANGCI_LINT_MODULE="github.com/golangci/golangci-lint/v2/cmd/golangci-lint"
|
||||||
GOLANGCI_LINT_REF="$GOLANGCI_LINT_MODULE@v$GOLANGCI_LINT_VERSION"
|
GOLANGCI_LINT_REF="$GOLANGCI_LINT_MODULE@v$GOLANGCI_LINT_VERSION"
|
||||||
|
|
||||||
|
# Seconds to allow `golangci-lint --version` to run. Bootstrap now
|
||||||
|
# executes the binary rather than merely locating it, so a wedged one
|
||||||
|
# must not hang the script.
|
||||||
|
GOLANGCI_LINT_VERSION_TIMEOUT="30"
|
||||||
|
|
||||||
PKGMGR=""
|
PKGMGR=""
|
||||||
SUDO=""
|
SUDO=""
|
||||||
APT_UPDATED=""
|
APT_UPDATED=""
|
||||||
@@ -68,13 +76,24 @@ missing() {
|
|||||||
|
|
||||||
# Echo the installed golangci-lint version, or nothing when the tool is
|
# Echo the installed golangci-lint version, or nothing when the tool is
|
||||||
# absent. The binary reports e.g.
|
# absent. The binary reports e.g.
|
||||||
# golangci-lint has version 2.12.2 built with go1.26.5 from abc1234 ...
|
# golangci-lint has version X.Y.Z built with go1.26.5 from abc1234 ...
|
||||||
# so the version is the field after the literal word "version", and it
|
# so the version is the field after the literal word "version", and it
|
||||||
# carries no leading "v" (the module ref does). Some builds do print a
|
# carries no leading "v" (the module ref does). Some builds do print a
|
||||||
# leading "v", so strip one if present and compare bare versions.
|
# leading "v", so strip one if present and compare bare versions.
|
||||||
|
#
|
||||||
|
# Only stdout is parsed; the binary's stderr is deliberately left
|
||||||
|
# connected to ours so that a present-but-broken linter (missing shared
|
||||||
|
# library, wrong architecture) says why instead of silently yielding the
|
||||||
|
# empty string. The call is bounded by timeout(1) where that exists —
|
||||||
|
# stock macOS has no timeout(1), and there the call runs unbounded, as
|
||||||
|
# it did before this check was version-aware.
|
||||||
golangci_lint_version() {
|
golangci_lint_version() {
|
||||||
command -v golangci-lint >/dev/null 2>&1 || return 0
|
command -v golangci-lint >/dev/null 2>&1 || return 0
|
||||||
golangci-lint --version 2>/dev/null | awk '
|
if command -v timeout >/dev/null 2>&1; then
|
||||||
|
timeout "$GOLANGCI_LINT_VERSION_TIMEOUT" golangci-lint --version
|
||||||
|
else
|
||||||
|
golangci-lint --version
|
||||||
|
fi | awk '
|
||||||
{
|
{
|
||||||
for (i = 1; i < NF; i++) {
|
for (i = 1; i < NF; i++) {
|
||||||
if ($i == "version") {
|
if ($i == "version") {
|
||||||
@@ -88,6 +107,40 @@ golangci_lint_version() {
|
|||||||
'
|
'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Confirm that the golangci-lint just installed is the one that will
|
||||||
|
# actually run. `go install` writes into "$(go env GOBIN)" (or
|
||||||
|
# "$(go env GOPATH)/bin"), but `make lint` runs whatever PATH resolves
|
||||||
|
# first. When a wrong-version binary sits ahead of that directory — a
|
||||||
|
# nix profile, apt, brew, apk, or a tarball in /usr/local/bin — the
|
||||||
|
# install lands behind the shadow and changes nothing the gate uses.
|
||||||
|
# Exiting 0 there would leave the local gate linting against a different
|
||||||
|
# ruleset than CI while claiming success, which is the failure this
|
||||||
|
# whole check exists to prevent. Diagnose and stop: naming both paths is
|
||||||
|
# what makes it fixable. Reordering PATH or deleting someone else's
|
||||||
|
# binary is not bootstrap's call.
|
||||||
|
verify_golangci_lint() {
|
||||||
|
# Forget any remembered command locations first: the install may have
|
||||||
|
# created a binary in a directory the shell already searched.
|
||||||
|
hash -r 2>/dev/null || true
|
||||||
|
|
||||||
|
goinstalldir="$(go env GOBIN)"
|
||||||
|
if [ -z "$goinstalldir" ]; then
|
||||||
|
goinstalldir="$(go env GOPATH)/bin"
|
||||||
|
fi
|
||||||
|
resolved="$(command -v golangci-lint 2>/dev/null || true)"
|
||||||
|
effective="$(golangci_lint_version)"
|
||||||
|
if [ "$effective" != "$GOLANGCI_LINT_VERSION" ]; then
|
||||||
|
echo "bootstrap: installed golangci-lint $GOLANGCI_LINT_VERSION into" \
|
||||||
|
"$goinstalldir, but the golangci-lint on PATH is" \
|
||||||
|
"${resolved:-not resolvable} and reports" \
|
||||||
|
"${effective:-no parseable version}" >&2
|
||||||
|
echo "bootstrap: the install is shadowed or unreachable; put" \
|
||||||
|
"$goinstalldir ahead of it on PATH (or remove the shadowing" \
|
||||||
|
"binary) and re-run" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
|
||||||
@@ -107,12 +160,14 @@ main() {
|
|||||||
# the Dockerfile lint stage runs a digest-pinned linter, so a host
|
# the Dockerfile lint stage runs a digest-pinned linter, so a host
|
||||||
# running any other version lints against different rules and
|
# running any other version lints against different rules and
|
||||||
# `make check` can go green on a commit CI then rejects. Any version
|
# `make check` can go green on a commit CI then rejects. Any version
|
||||||
# that is not the pin — older or newer — is reinstalled.
|
# that is not the pin — older or newer — is reinstalled, and the
|
||||||
|
# install is then verified to be the binary PATH resolves.
|
||||||
installed="$(golangci_lint_version)"
|
installed="$(golangci_lint_version)"
|
||||||
if [ "$installed" != "$GOLANGCI_LINT_VERSION" ]; then
|
if [ "$installed" != "$GOLANGCI_LINT_VERSION" ]; then
|
||||||
echo "bootstrap: golangci-lint ${installed:-absent or unparseable}," \
|
echo "bootstrap: golangci-lint ${installed:-absent or unparseable}," \
|
||||||
"want $GOLANGCI_LINT_VERSION; installing"
|
"want $GOLANGCI_LINT_VERSION; installing"
|
||||||
go install "$GOLANGCI_LINT_REF"
|
go install "$GOLANGCI_LINT_REF"
|
||||||
|
verify_golangci_lint
|
||||||
fi
|
fi
|
||||||
|
|
||||||
go mod download
|
go mod download
|
||||||
|
|||||||
Reference in New Issue
Block a user