All checks were successful
check / check (pull_request) Successful in 3m11s
Closes #80. script/lint decided whether it could skip the pinned image by asking what version was on PATH rather than where it was running, and cache isolation is part of that same question. Both issues are that one defect. The cache was one directory per repo, shared by every worktree on the host. Two checkouts of this repo have identical Go file contents, so their cache keys collide and golangci-lint replays the stored analysis, including the paths recorded when it was produced. The loud direction of that failure - a clean tree failed by a dirty sibling - is the harmless one. The silent direction, a dirty tree passed by a clean sibling, is another way for a gate here to report a green it did not earn. The cache is now keyed on a digest of the worktree path, so a collision is not possible, and it stays persistent per worktree: a warm run is still seconds. Each cache records the worktree it belongs to and is collected when that worktree is gone, so throwaway worktrees do not accumulate caches; the tree lives under XDG_CACHE_HOME and is disposable by definition. script/lint-audit is the backstop, and runs on every lint: it rejects output citing any file that is not in the tree being linted, so a result built out of another checkout's analysis is a hard error instead of a silent pass. It runs on clean output too, because that is the case nobody investigates. It never certifies that a run passed - it does not look at whether there were findings - so it cannot itself become a gate that reports a green. golangci-lint's "parallel golangci-lint is running" refusal is now a bounded retry rather than a verdict. It is not a lint result, and exiting non-zero on it is indistinguishable to a caller from real findings; issue #88 measured that a private cache does not remove the contention. Exhausting the retries fails with a message that says the tree was never analysed. The native path now requires VAULTIK_LINT_IN_CONTAINER=1, which only the Dockerfile's lint stage sets, in addition to matching the pin. A developer's locally installed 2.12.2 is a different build reached by a different code path and no longer bypasses the digest pin. /.dockerenv was considered and rejected as the signal: dockerd creates it for `docker run`, but it is not reliably present during a BuildKit `docker build`, which is exactly the case the exception exists for. Inside the container a version mismatch is now a hard error rather than a fall-through, since there is no daemon there to fall through to. Version detection uses `golangci-lint version --short`, the interface meant for it, keeping the banner scrape only as a fallback. script/bootstrap no longer prints "bootstrap complete" on a machine that cannot run the gate. Docker missing, or present with an unreachable daemon, is a hard failure naming exactly what breaks. Installing docker from bootstrap was rejected: it needs root, a daemon, and on macOS a GUI cask, so the attempt would itself fail in the common case and trade one false success for a second failure mode. TODO.md's claim that `make check` became "as trustworthy as script/cibuild" is corrected to what README.md already said: only the lint leg is equivalent, while tests and gofmt still run against the host toolchain. README.md's requirements section gains docker and sqlite3. Verified by reproduction, not inspection: two concurrent lints from two worktrees of differing cleanliness each reported only their own findings with no lock error; a real run made to report paths outside its tree exits 1; a matching linter shimmed onto PATH is never invoked while the pinned image runs; a PATH without docker makes bootstrap fail. script/ cibuild exits 0 with the lint layer executing in the pinned image, which is what proves the in-container path still works.
127 lines
4.1 KiB
Bash
Executable File
127 lines
4.1 KiB
Bash
Executable File
#!/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 (not git,
|
|
# make, or go).
|
|
set -eu
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
|
|
|
PKGMGR=""
|
|
SUDO=""
|
|
|
|
detect_pkgmgr() {
|
|
[ -n "$PKGMGR" ] && return 0
|
|
if command -v nix-env >/dev/null 2>&1; then
|
|
PKGMGR="nix"
|
|
elif command -v apt-get >/dev/null 2>&1; then
|
|
PKGMGR="apt"
|
|
elif command -v brew >/dev/null 2>&1; then
|
|
PKGMGR="brew"
|
|
elif command -v apk >/dev/null 2>&1; then
|
|
PKGMGR="apk"
|
|
else
|
|
echo "bootstrap: no supported package manager (nix, apt, brew, apk)" >&2
|
|
exit 1
|
|
fi
|
|
if [ "$PKGMGR" = "apt" ]; then
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
if [ "$(id -u)" != "0" ]; then
|
|
SUDO="sudo"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# pkg_install <nix-attr> <apt-pkg> <brew-formula> <apk-pkg>
|
|
pkg_install() {
|
|
detect_pkgmgr
|
|
case "$PKGMGR" in
|
|
nix) nix-env -iA "nixpkgs.$1" ;;
|
|
apt) $SUDO env DEBIAN_FRONTEND=noninteractive apt-get install -y "$2" ;;
|
|
brew) brew install "$3" ;;
|
|
apk) apk add --no-cache "$4" ;;
|
|
esac
|
|
}
|
|
|
|
missing() {
|
|
! command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
# Docker is a hard requirement, not a nice-to-have: script/lint runs the
|
|
# digest-pinned golangci-lint image from the Dockerfile's lint stage, and
|
|
# script/check and script/precommit both run script/lint. A bootstrap
|
|
# that prints "bootstrap complete" on a machine where `make check` cannot
|
|
# run is a false success, so this fails instead.
|
|
#
|
|
# Installing docker from here was considered and rejected: it needs root,
|
|
# a running daemon, and on macOS a GUI cask, so an attempt would itself
|
|
# fail in the common case - trading one false success for a second
|
|
# failure mode. Naming exactly what breaks is more useful.
|
|
# Prints the problem and returns 0 when docker cannot be used; returns
|
|
# 1 (and prints nothing) when it can.
|
|
docker_problem() {
|
|
if missing docker; then
|
|
echo "docker is not installed"
|
|
return 0
|
|
fi
|
|
if ! docker info >/dev/null 2>&1; then
|
|
echo "the docker daemon is not reachable"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
require_docker() {
|
|
reason="$(docker_problem)" || return 0
|
|
cat >&2 <<EOF
|
|
bootstrap: FAILED - $reason.
|
|
|
|
Docker is required to develop this repo. Without it these do not work:
|
|
|
|
script/lint runs the digest-pinned golangci-lint image declared
|
|
by the Dockerfile's lint stage, which is the single
|
|
source of truth for the linter version
|
|
script/check runs script/lint
|
|
script/precommit runs script/check, so commits are blocked by the
|
|
pre-commit hook installed by script/setup
|
|
script/cibuild builds the Dockerfile, which is what CI runs
|
|
|
|
Install docker (and start the daemon, checking DOCKER_HOST and your
|
|
group membership), then re-run script/bootstrap. golangci-lint on PATH
|
|
is deliberately not a substitute: script/lint will not use it.
|
|
EOF
|
|
exit 1
|
|
}
|
|
|
|
main() {
|
|
cd "$ROOT"
|
|
|
|
# Base tooling (every repo)
|
|
if missing git; then pkg_install git git git git; fi
|
|
if missing make; then pkg_install gnumake make make make; fi
|
|
|
|
# Go toolchain
|
|
if missing go; then pkg_install go golang go go; fi
|
|
|
|
# golangci-lint is deliberately NOT installed: script/lint runs the
|
|
# digest-pinned golangci-lint image from the Dockerfile's lint stage,
|
|
# so whatever a package manager happens to ship would only be a
|
|
# shadow of the pinned version that could drift from CI. script/lint
|
|
# will not use a PATH binary on a host at any version, so installing
|
|
# one here would buy nothing.
|
|
|
|
# sqlite3 CLI: the test suite shells out to it (VACUUM).
|
|
if missing sqlite3; then pkg_install sqlite sqlite3 sqlite sqlite; fi
|
|
|
|
go mod download
|
|
|
|
# Last, so that everything installable is installed before the one
|
|
# thing this script cannot install decides the outcome.
|
|
require_docker
|
|
|
|
echo "bootstrap complete"
|
|
}
|
|
|
|
main "$@"
|