diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b81c7b..4ee990e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,17 +38,43 @@ jobs: # whoever can move it can run code in this job. - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + # mise rather than setup-go, so every repository in the fleet installs its + # toolchain the same way. The cost is that the Go version now lives in + # .mise.toml as well as go.mod; the step below is what keeps them honest. + - uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3 + + - name: Toolchain pins agree + shell: bash + run: | + set -euo pipefail + mod="$(sed -n 's/^go //p' go.mod | head -n1)" + pin="$(sed -n 's/^go = "\(.*\)"$/\1/p' .mise.toml | head -n1)" + if [ -z "$mod" ] || [ -z "$pin" ]; then + echo "::error::could not read the Go version from go.mod ($mod) or .mise.toml ($pin)" + exit 1 + fi + if [ "$(printf '%s\n%s\n' "$mod" "$pin" | sort -V | head -n1)" != "$mod" ]; then + echo "::error::.mise.toml pins Go $pin but go.mod asks for $mod. CI would build against the older one while GOTOOLCHAIN quietly fetched the newer, so the pin would describe nothing." + exit 1 + fi + echo "go.mod wants $mod, .mise.toml pins $pin" + + # What setup-go did for free. mise installs the toolchain and caches + # nothing else, so this is what stops every run recompiling from cold. + - name: Cache Go build + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - # Read from go.mod rather than pinned here, so the version lives in - # one place and a bump cannot leave CI testing the old one. - go-version-file: go.mod - cache-dependency-path: go.sum + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: go-${{ runner.os }}-${{ hashFiles('go.sum', '.mise.toml') }} + restore-keys: | + go-${{ runner.os }}- # Built first: a compile error should fail before a test suite spends # minutes reaching the same conclusion more slowly. - - name: Build - uses: ./.github/actions/build - - name: Test uses: ./.github/actions/test + + - name: Build + uses: ./.github/actions/build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b4b0430..35a2da3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,10 +78,38 @@ jobs: echo "version=$version" >> "$GITHUB_OUTPUT" echo "tag=$tag" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + # mise rather than setup-go, so every repository in the fleet installs its + # toolchain the same way. The cost is that the Go version now lives in + # .mise.toml as well as go.mod; the step below is what keeps them honest. + - uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3 + + - name: Toolchain pins agree + shell: bash + run: | + set -euo pipefail + mod="$(sed -n 's/^go //p' go.mod | head -n1)" + pin="$(sed -n 's/^go = "\(.*\)"$/\1/p' .mise.toml | head -n1)" + if [ -z "$mod" ] || [ -z "$pin" ]; then + echo "::error::could not read the Go version from go.mod ($mod) or .mise.toml ($pin)" + exit 1 + fi + if [ "$(printf '%s\n%s\n' "$mod" "$pin" | sort -V | head -n1)" != "$mod" ]; then + echo "::error::.mise.toml pins Go $pin but go.mod asks for $mod. CI would build against the older one while GOTOOLCHAIN quietly fetched the newer, so the pin would describe nothing." + exit 1 + fi + echo "go.mod wants $mod, .mise.toml pins $pin" + + # What setup-go did for free. mise installs the toolchain and caches + # nothing else, so this is what stops every run recompiling from cold. + - name: Cache Go build + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - go-version-file: go.mod - cache-dependency-path: go.sum + path: | + ~/.cache/go-build + ~/go/pkg/mod + key: go-${{ runner.os }}-${{ hashFiles('go.sum', '.mise.toml') }} + restore-keys: | + go-${{ runner.os }}- # The same steps CI runs, from the same definition. Running them again # against this exact commit is not redundant: CI passing on this SHA diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 0000000..5d52bb8 --- /dev/null +++ b/.mise.toml @@ -0,0 +1,13 @@ +[tools] +# Must be >= the `go` directive in go.mod, which is currently 1.25.0. +# +# CI used to read the version straight out of go.mod via setup-go, so there was +# only ever one place for it to live. Moving to mise makes the toolchain +# consistent across the fleet but creates a second place -- and a pin that +# silently disagrees with go.mod is not hypothetical here: astry ran 1.24.5 +# against a go.mod asking for 1.25, and it built only because GOTOOLCHAIN=auto +# downloaded the real one behind the pin, so the pin described nothing. +# +# The "Toolchain pins agree" step in ci.yml is what stops that happening again. +# If you bump go.mod, bump this too; CI will tell you if you forget. +go = "1.25.0"