This commit was merged in pull request #30.
This commit is contained in:
24
Dockerfile
24
Dockerfile
@@ -3,6 +3,10 @@
|
||||
# build, then the read-only prettier docs check), so the image build
|
||||
# fails on any formatting or Hugo build error. This is what CI
|
||||
# (script/cibuild) runs on every push.
|
||||
#
|
||||
# Build this only via script/cibuild or script/docker: both pass the
|
||||
# CHECK_EPOCH build argument that this file requires, and a bare
|
||||
# `docker build .` fails by design. See the guard below for why.
|
||||
# alpine 3.21, 2026-02-28
|
||||
FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
|
||||
|
||||
@@ -19,5 +23,23 @@ RUN script/bootstrap
|
||||
|
||||
COPY . .
|
||||
|
||||
# CHECK_EPOCH is a per-invocation nonce supplied by script/cibuild and
|
||||
# script/docker. Without it an unchanged tree serves this layer from
|
||||
# cache and the build reports a green it never ran. ARG is stage-scoped,
|
||||
# so it must be redeclared in every stage that runs checks - this image
|
||||
# has one stage, so one declaration. Declared with no default: a default
|
||||
# would be a constant, and a constant is a stable cache key. The guard
|
||||
# makes a bare `docker build .` fail loudly instead of silently reusing
|
||||
# the empty (and therefore stable) cache key. Expand the value into the
|
||||
# command so the cache miss does not depend on BuildKit's handling of an
|
||||
# unreferenced ARG. Both the guard and the check RUN reference the value,
|
||||
# so both are value-keyed: there are two independent invalidation points
|
||||
# here, not one. Keep both.
|
||||
#
|
||||
# Everything above this point still caches, so the script/bootstrap
|
||||
# layer - which compiles Hugo from source - is not rebuilt.
|
||||
ARG CHECK_EPOCH
|
||||
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
||||
|
||||
# Run all checks - build fails if any check fails.
|
||||
RUN make check
|
||||
RUN echo "check epoch: ${CHECK_EPOCH}" && make check
|
||||
|
||||
@@ -60,11 +60,16 @@ provide:
|
||||
- `script/check` — run `script/test`, `script/lint`, then `script/fmt-check`;
|
||||
modifies no tracked files
|
||||
- `script/docker` — build the Docker image tagged with the project name
|
||||
- `script/cibuild` — the CI build (`docker build .`); the Dockerfile runs
|
||||
`make check`
|
||||
- `script/cibuild` — the CI build; the Dockerfile runs `make check`
|
||||
- `script/install-precommit` — install the git pre-commit hook that runs
|
||||
`script/check`
|
||||
|
||||
Build the image through `script/cibuild` or `script/docker` only. Both pass a
|
||||
per-invocation `CHECK_EPOCH` build argument that the Dockerfile requires, so the
|
||||
`make check` layer can never be served from cache — without it Docker returns a
|
||||
green it did not earn. A bare `docker build .` fails closed on the Dockerfile's
|
||||
`CHECK_EPOCH` guard rather than caching its way to a false success.
|
||||
|
||||
A convenience `make serve` target runs `hugo server` for local preview.
|
||||
|
||||
## License
|
||||
|
||||
16
TODO.md
16
TODO.md
@@ -27,6 +27,22 @@ Update `README.md` accordingly.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-09: stopped `script/cibuild` reporting a green it never earned (closes
|
||||
#23). `COPY . .` is keyed on content, so on an unchanged tree Docker served
|
||||
`RUN make check` from cache: the checks never executed and the build still
|
||||
exited 0. Three separate reviewers had already been fooled by it here. The
|
||||
`Dockerfile` now declares `ARG CHECK_EPOCH` below `COPY . .` with no default
|
||||
(a default is a constant, and a constant is a stable cache key), guards it
|
||||
with `RUN [ -n "$CHECK_EPOCH" ] || exit 1`, and expands it into the check
|
||||
command; `script/cibuild` and `script/docker` both pass
|
||||
`epoch="$(date +%s%N)$$"` — assigned on its own line, because a failing
|
||||
command substitution inside an argument does not trip `set -e`, and with `$$`
|
||||
because busybox `date` drops `%N` silently. This is the canonical shape
|
||||
settled upstream in `prompts` #26, which has not merged there yet, so it may
|
||||
need re-syncing. Verified with two consecutive runs on an unchanged tree that
|
||||
both executed the checks while `RUN script/bootstrap` stayed `CACHED`, a
|
||||
constant-epoch counterfactual that restored the false green, and a planted
|
||||
prettier failure that failed the build
|
||||
- 2026-08-09: disabled the unused `taxonomy` and `term` page kinds in
|
||||
`hugo.toml` (closes #13). Hugo enables the `tags` and `categories` taxonomies
|
||||
by default; this single-page site has no taxonomy terms and no taxonomy
|
||||
|
||||
@@ -2,13 +2,29 @@
|
||||
# script/cibuild: run the CI build. The Dockerfile runs `make check`,
|
||||
# so a successful build implies all checks pass. The Gitea workflow
|
||||
# runs this on push.
|
||||
#
|
||||
# That implication only holds because of CHECK_EPOCH. Docker keys the
|
||||
# `RUN make check` layer on content, so on an unchanged tree it is
|
||||
# served from cache: the checks never execute and the build still exits
|
||||
# 0. Passing a value that differs on every invocation invalidates that
|
||||
# layer and everything below it, while the script/bootstrap toolchain
|
||||
# layer above it keeps caching.
|
||||
set -eu
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
docker build .
|
||||
# Assigned to a variable rather than substituted inline in the
|
||||
# argument list: a command substitution that fails inside an
|
||||
# argument does not trip `set -e`, so the inline form would quietly
|
||||
# pass an empty string and restore the cached false green. As the
|
||||
# whole of an assignment its exit status is the command's, so
|
||||
# `set -e` catches it. `%N` keeps two invocations within the same
|
||||
# second distinct; busybox date silently drops `%N` and still exits
|
||||
# 0, so `$$` is appended to cover that degradation.
|
||||
epoch="$(date +%s%N)$$"
|
||||
docker build --build-arg CHECK_EPOCH="$epoch" .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#!/bin/sh
|
||||
# script/docker: build the Docker image tagged with the project name.
|
||||
# The tag comes from script/projectname.
|
||||
#
|
||||
# The Dockerfile requires the CHECK_EPOCH build argument, generated here
|
||||
# exactly as script/cibuild generates it: see that script for why the
|
||||
# check layer must not be allowed to cache, and why the value is built
|
||||
# in an assignment rather than inline.
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||
@@ -8,7 +13,9 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||
|
||||
main() {
|
||||
cd "$ROOT"
|
||||
docker build -t "$("$SCRIPT_DIR/projectname")" .
|
||||
epoch="$(date +%s%N)$$"
|
||||
docker build --build-arg CHECK_EPOCH="$epoch" \
|
||||
-t "$("$SCRIPT_DIR/projectname")" .
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user