Compare commits
1 Commits
fix/121-li
...
fix/115-ci
| Author | SHA1 | Date | |
|---|---|---|---|
| ff66ecc0c9 |
@@ -1,6 +1,5 @@
|
|||||||
.git/
|
.git/
|
||||||
bin/
|
bin/
|
||||||
.lint-cache/
|
|
||||||
*.md
|
*.md
|
||||||
LICENSE
|
LICENSE
|
||||||
.editorconfig
|
.editorconfig
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,7 +1,6 @@
|
|||||||
bin/
|
bin/
|
||||||
vendor/
|
vendor/
|
||||||
data/
|
data/
|
||||||
.lint-cache/
|
|
||||||
.env
|
.env
|
||||||
*.exe
|
*.exe
|
||||||
/dnswatcher
|
/dnswatcher
|
||||||
|
|||||||
21
Dockerfile
21
Dockerfile
@@ -15,8 +15,25 @@ RUN go mod download
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Run all checks - build fails if any check fails
|
# Run all checks - build fails if any check fails.
|
||||||
RUN make check
|
#
|
||||||
|
# CHECK_EPOCH is a cache-busting build argument. Without it, an
|
||||||
|
# unchanged tree leaves this layer's cache key identical and Docker
|
||||||
|
# serves the previous verdict instead of re-running the suite, so the
|
||||||
|
# build reports a green it did not earn. A build argument's value
|
||||||
|
# participates in the cache key of later instructions in the stage even
|
||||||
|
# when they do not reference it, so a fresh value busts this layer
|
||||||
|
# either way. It is expanded into the command deliberately: that makes
|
||||||
|
# the invalidation a property of the command string itself rather than
|
||||||
|
# of how a given builder treats unreferenced args, and it surfaces the
|
||||||
|
# epoch in the build log as a diagnostic.
|
||||||
|
#
|
||||||
|
# Placing the ARG here and nowhere earlier keeps everything above it
|
||||||
|
# (toolchain install, go mod download) cached, so only the check and the
|
||||||
|
# steps after it re-run. script/cibuild passes a fresh value per run; a
|
||||||
|
# plain `docker build` without it caches as before.
|
||||||
|
ARG CHECK_EPOCH
|
||||||
|
RUN echo "check epoch: ${CHECK_EPOCH}" && make check
|
||||||
|
|
||||||
# Build the binary
|
# Build the binary
|
||||||
RUN make build
|
RUN make build
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -387,16 +387,16 @@ them. We provide:
|
|||||||
- `script/projectname` — print the project name (used for the Docker
|
- `script/projectname` — print the project name (used for the Docker
|
||||||
image tag)
|
image tag)
|
||||||
- `script/test` — run the test suite (race detector, coverage)
|
- `script/test` — run the test suite (race detector, coverage)
|
||||||
- `script/lint` — run golangci-lint, with its cache and its lock file
|
- `script/lint` — run golangci-lint
|
||||||
isolated to this checkout (under the git-ignored `.lint-cache/`) so
|
|
||||||
concurrent checkouts on one host cannot share cache entries or
|
|
||||||
contend on a single lock
|
|
||||||
- `script/fmt` — format all code (gofmt -s, goimports)
|
- `script/fmt` — format all code (gofmt -s, goimports)
|
||||||
- `script/fmt-check` — check formatting (read-only)
|
- `script/fmt-check` — check formatting (read-only)
|
||||||
- `script/check` — run test, lint, and fmt-check
|
- `script/check` — run test, lint, and fmt-check
|
||||||
- `script/docker` — build the Docker image tagged via
|
- `script/docker` — build the Docker image tagged via
|
||||||
`script/projectname`
|
`script/projectname`
|
||||||
- `script/cibuild` — CI entrypoint: plain `docker build .`
|
- `script/cibuild` — CI entrypoint: `docker build .` with a fresh
|
||||||
|
`CHECK_EPOCH` build argument, so the Dockerfile's `make check` layer
|
||||||
|
is never served from the cache and a green build always means the
|
||||||
|
checks ran on this invocation
|
||||||
- `script/precommit` — run by the git pre-commit hook; `go mod tidy`
|
- `script/precommit` — run by the git pre-commit hook; `go mod tidy`
|
||||||
guard, then `script/check`
|
guard, then `script/check`
|
||||||
- `script/install-precommit` — install the git pre-commit hook
|
- `script/install-precommit` — install the git pre-commit hook
|
||||||
|
|||||||
19
TODO.md
19
TODO.md
@@ -25,15 +25,16 @@ confirm make check still passes.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
- 2026-08-09: `script/lint` now isolates golangci-lint's per-user global
|
- 2026-08-09: `script/cibuild` can no longer report a green it did not
|
||||||
state to the checkout (#121): `GOLANGCI_LINT_CACHE` and `TMPDIR` are
|
earn. The Dockerfile declares `ARG CHECK_EPOCH` immediately above the
|
||||||
both pointed at the git-ignored, Docker-ignored `.lint-cache/`. The
|
check step and expands it into the `RUN` command, and `script/cibuild`
|
||||||
cache fixes cross-contamination; `TMPDIR` is what moves the lock,
|
passes a fresh `$(date +%s%N)` per invocation, so the `make check`
|
||||||
which lives at `$TMPDIR/golangci-lint.lock` and not in the cache
|
layer is always re-executed while the pinned toolchain install and
|
||||||
directory. Reproduced both failure modes on the unfixed script (10 of
|
`go mod download` stay cached. Verified by experiment: before the fix
|
||||||
12 concurrent runs void with `parallel golangci-lint is running`; 11
|
a second run on an unchanged tree returned in 283 ms with the check
|
||||||
of 12 reporting another checkout's paths) and both are gone at 20-way
|
layer `CACHED`; after it the check runs every time, and a deliberately
|
||||||
concurrency after the fix
|
planted always-failing test made the build fail with exactly that
|
||||||
|
test's message
|
||||||
- 2026-08-07: golangci-lint bumped to v2.12.2 (commit-pinned installs
|
- 2026-08-07: golangci-lint bumped to v2.12.2 (commit-pinned installs
|
||||||
in `Dockerfile` and `script/bootstrap`); `.golangci.yml` set to the
|
in `Dockerfile` and `script/bootstrap`); `.golangci.yml` set to the
|
||||||
org-standard v2-schema config used across the org's repos
|
org-standard v2-schema config used across the org's repos
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# script/cibuild: run the CI build. The Dockerfile runs make check, so
|
# script/cibuild: run the CI build. The Dockerfile runs make check, and
|
||||||
# a successful build implies all checks pass.
|
# the CHECK_EPOCH build argument below is fresh on every invocation, so
|
||||||
|
# the check layer is never served from the Docker layer cache: a
|
||||||
|
# successful build means the checks were executed and passed on this
|
||||||
|
# run, not on some earlier one. Only the check step and the steps after
|
||||||
|
# it are invalidated; the toolchain install and go mod download stay
|
||||||
|
# cached.
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
docker build .
|
docker build --build-arg CHECK_EPOCH="$(date +%s%N)" .
|
||||||
}
|
}
|
||||||
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
29
script/lint
29
script/lint
@@ -1,40 +1,11 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# script/lint: run the linter.
|
# script/lint: run the linter.
|
||||||
#
|
|
||||||
# golangci-lint keeps two pieces of per-user global state, and both of
|
|
||||||
# them break when several checkouts on one host lint concurrently:
|
|
||||||
#
|
|
||||||
# 1. Its analysis cache (GOLANGCI_LINT_CACHE, default
|
|
||||||
# ~/.cache/golangci-lint). Entries are keyed by content, not by
|
|
||||||
# checkout, so a hit written by another checkout is replayed
|
|
||||||
# verbatim - including that checkout's file paths. The run then
|
|
||||||
# reports findings for files it never linted.
|
|
||||||
#
|
|
||||||
# 2. Its "one runner at a time" lock, which does NOT live in the
|
|
||||||
# cache directory: golangci-lint locks
|
|
||||||
# $(os.TempDir())/golangci-lint.lock, i.e.
|
|
||||||
# "$TMPDIR"/golangci-lint.lock (pkg/commands/run.go,
|
|
||||||
# acquireFileLock). It waits 5s, then aborts with "parallel
|
|
||||||
# golangci-lint is running" - a non-result that looks like a lint
|
|
||||||
# failure. Setting GOLANGCI_LINT_CACHE alone does not move it.
|
|
||||||
#
|
|
||||||
# So both are pinned under the checkout root. The cache is never shared,
|
|
||||||
# and TMPDIR makes the lock file per-checkout, which keeps the lock
|
|
||||||
# doing its actual job (serialising runs that share one cache) at the
|
|
||||||
# right scope. .lint-cache/ is git-ignored and Docker-ignored, and
|
|
||||||
# caching still works: it persists across runs in this checkout.
|
|
||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
cd "$ROOT"
|
cd "$ROOT"
|
||||||
|
|
||||||
GOLANGCI_LINT_CACHE="$ROOT/.lint-cache/cache"
|
|
||||||
TMPDIR="$ROOT/.lint-cache/tmp"
|
|
||||||
export GOLANGCI_LINT_CACHE TMPDIR
|
|
||||||
mkdir -p "$GOLANGCI_LINT_CACHE" "$TMPDIR"
|
|
||||||
|
|
||||||
golangci-lint run --config .golangci.yml ./...
|
golangci-lint run --config .golangci.yml ./...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user