diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 485e03c..888dc7c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,7 +49,7 @@ jobs: # failure is version-specific. - name: Lowest supported Go if: ${{ !cancelled() && steps.changes.outputs.code == 'true' }} - run: docker build --build-arg GO_VERSION=1.22.12 . + run: docker build --build-arg GO_VERSION=1.22.12 --target portable . release: name: Release from CHANGELOG.md diff --git a/Dockerfile b/Dockerfile index 2a9909c..f6bbac1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,10 @@ # The merge gate: `docker build .` fails if vet, formatting or tests fail, and CI # runs exactly this. Day-to-day, prefer `docker compose run` (bind-mounts source, -# no rebuilds). GO_VERSION defaults to the latest stable Go; override it to test -# the lowest supported version: docker build --build-arg GO_VERSION=1.22.12 . +# no rebuilds). GO_VERSION defaults to the latest stable Go; the lowest supported +# version builds the `portable` stage, which every supported Go must pass: +# docker build --build-arg GO_VERSION=1.22.12 --target portable . ARG GO_VERSION=1.27.1 -FROM golang:${GO_VERSION} +FROM golang:${GO_VERSION} AS portable WORKDIR /app @@ -14,6 +15,8 @@ RUN go mod download COPY . . RUN go vet ./... && \ go run github.com/fzipp/gocyclo/cmd/gocyclo@v0.6.0 -over 14 -ignore _test . && \ - { unformatted="$(gofmt -l .)"; test -z "$unformatted" || \ - { echo "unformatted files:"; echo "$unformatted"; exit 1; }; } && \ go test -race ./... + +FROM portable +RUN unformatted="$(gofmt -l .)"; test -z "$unformatted" || \ + { echo "unformatted files:"; echo "$unformatted"; exit 1; } diff --git a/README.md b/README.md index b9b8f25..0eeec0f 100644 --- a/README.md +++ b/README.md @@ -940,6 +940,13 @@ the Development section below, and who ships a register the four above then draw - **Raising the lowest supported Go is a major.** A consumer building on it breaks, which is the one test every rule above applies; Go's convention of a minor is not followed. +- **The format check runs on the latest Go only.** `gofmt`'s output is the + toolchain's, not the code's: 1.27 stopped padding a map literal's values out to a + lone long key, so no source satisfies both it and 1.22's. A consumer on the lowest + supported Go depends on the code compiling and its tests passing there, which is + the `portable` stage, and never runs `gofmt` over this source, so the latest + toolchain alone defines the one canonical form goal 2 asks a reader to meet. Valid + while the lowest supported Go is not the latest. - **The changelog heading is the one spelling of a release; CI cuts the tag.** A tag pushed by hand is served by `go get` at once, so a tag whose commit lacks its heading is burnt, not fixed. The heading on a gate-passed `main` commit is the @@ -1367,12 +1374,13 @@ supported Go, and must pass before it can be merged — unless it changes none o the files the build and its tests read, nor the workflow itself, in which case it's skipped (see [Decisions](#decisions)). That build is the whole gate but the changelog check, which CI runs against the PR base — vet, complexity, format check -and tests — so run it locally before pushing: +and tests — so run it locally before pushing. The lowest Go builds the `portable` +stage, all of that bar the format check: ```sh -docker build . # latest -docker build --build-arg GO_VERSION=1.22.12 . # lowest supported -GO_VERSION=1.22.12 docker compose run --rm test # the same tests, without the image build +docker build . # latest +docker build --build-arg GO_VERSION=1.22.12 --target portable . # lowest supported +GO_VERSION=1.22.12 docker compose run --rm test # the same tests, without the image build ``` A change to the shipped data re-pins [`testdata/shipped_shape.txt`](testdata/shipped_shape.txt)