diff --git a/.github/workflows/README.md b/.github/workflows/README.md index fe5126d..903f034 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -133,6 +133,10 @@ jobs: A library that bundles a small CLI keeps it in its own module (`cmd//go.mod`), so library consumers do not inherit its dependencies. Pass `module-dir: cmd/` and `main-path: .` to `go-binary-release.yml`. No `go.work` is needed. +## Go version of release builds + +`go-binary-release.yml` builds with the newest patch release of the Go minor version the module declares: `go 1.26.0` (or `go 1.26`) builds with the latest 1.26.x. A `toolchain` line counts when its minor version is newer. The `go` directive stays the floor for users; it does not pin the compiler of the shipped binaries, so a patch release of Go reaches them on the next release. + ## Release notes via RELEASE.md Both release workflows support an optional `RELEASE.md` file in the repo root. When a section matching the new tag is found, its content is used as the GitHub Release body. Otherwise a commit-log changelog is generated automatically. diff --git a/.github/workflows/go-binary-release.yml b/.github/workflows/go-binary-release.yml index 5227d82..0725a92 100644 --- a/.github/workflows/go-binary-release.yml +++ b/.github/workflows/go-binary-release.yml @@ -214,10 +214,29 @@ jobs: exit 1 fi + - name: Resolve Go version + id: go-version + shell: bash + env: + GO_MOD: go.mod + run: | + set -euo pipefail + # The newest patch of the Go minor the module declares (go 1.26.0 -> 1.26, + # built with the latest 1.26.x), or of its toolchain line if that is newer. + # Building with go.mod's exact version would ship old patch releases. + [ -f "$GO_MOD" ] || { echo "::error::$GO_MOD not found"; exit 1; } + minor() { sed -nE "s/^$1[[:space:]]+(go)?([0-9]+\.[0-9]+).*/\2/p" "$GO_MOD" | head -1; } + go=$(minor go); toolchain=$(minor toolchain) + [ -n "$go" ] || { echo "::error::no go directive in $GO_MOD"; exit 1; } + version=$(printf '%s\n%s\n' "$go" "$toolchain" | sed '/^$/d' | sort -t. -k1,1n -k2,2n | tail -1) + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Go $version (latest patch) from $GO_MOD" + - name: Set up Go uses: actions/setup-go@v7 with: - go-version-file: go.mod + go-version: ${{ steps.go-version.outputs.version }} + check-latest: true cache: true cache-dependency-path: | go.sum @@ -397,10 +416,29 @@ jobs: - name: Checkout uses: actions/checkout@v7 + - name: Resolve Go version + id: go-version + shell: bash + env: + GO_MOD: ${{ inputs.module-dir }}/go.mod + run: | + set -euo pipefail + # The newest patch of the Go minor the module declares (go 1.26.0 -> 1.26, + # built with the latest 1.26.x), or of its toolchain line if that is newer. + # Building with go.mod's exact version would ship old patch releases. + [ -f "$GO_MOD" ] || { echo "::error::$GO_MOD not found"; exit 1; } + minor() { sed -nE "s/^$1[[:space:]]+(go)?([0-9]+\.[0-9]+).*/\2/p" "$GO_MOD" | head -1; } + go=$(minor go); toolchain=$(minor toolchain) + [ -n "$go" ] || { echo "::error::no go directive in $GO_MOD"; exit 1; } + version=$(printf '%s\n%s\n' "$go" "$toolchain" | sed '/^$/d' | sort -t. -k1,1n -k2,2n | tail -1) + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "Go $version (latest patch) from $GO_MOD" + - name: Set up Go uses: actions/setup-go@v7 with: - go-version-file: ${{ inputs.module-dir }}/go.mod + go-version: ${{ steps.go-version.outputs.version }} + check-latest: true cache: true cache-dependency-path: | go.sum diff --git a/.github/workflows/selftest.yml b/.github/workflows/selftest.yml index 1dbe327..27bd321 100644 --- a/.github/workflows/selftest.yml +++ b/.github/workflows/selftest.yml @@ -150,3 +150,11 @@ jobs: steps: - uses: actions/checkout@v7 - run: scripts/selftest/release-notes.sh + + go-version: + name: Release Go version resolution + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v7 + - run: scripts/selftest/go-version.sh diff --git a/AGENTS.md b/AGENTS.md index 43d21b6..f51ded1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,6 +34,7 @@ This repository follows **otfabric/engineering policy 1.2**, profile **infrastru | anything | `go run ./cmd/otfabric-policy check ` from otfabric/engineering | | `go-ci.yml` policy steps | `scripts/selftest/policy.sh` | | release notes extraction | `scripts/selftest/release-notes.sh` | +| the Go version of release builds | `scripts/selftest/go-version.sh` | | `docs-check.yml` docsync step | `scripts/selftest/docs-check.sh` | | a workflow | the Selftest workflow, on the pull request | diff --git a/scripts/selftest/go-version.sh b/scripts/selftest/go-version.sh new file mode 100755 index 0000000..5c4e5c2 --- /dev/null +++ b/scripts/selftest/go-version.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Cases of the "Resolve Go version" step of go-binary-release.yml, run on the +# go.mod fixtures with the exact step script. Both copies of the step (the +# prepare job and the build job) must be the same script. +set -euo pipefail + +root=$(cd "$(dirname "$0")/../.." && pwd) +wf="$root/.github/workflows/go-binary-release.yml" +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT + +python3 "$root/scripts/selftest/extract-step.py" "$wf" "Resolve Go version" > "$work/step.sh" +count=$(grep -c -- '- name: Resolve Go version' "$wf") +[ "$count" = 2 ] || { echo "FAIL want 2 Resolve Go version steps, got $count"; exit 1; } +# extract-step.py prints the first step; check the second has the same run block. +python3 - "$wf" "$work/step.sh" <<'EOF' +import sys +lines = open(sys.argv[1]).read().split("\n") +first = open(sys.argv[2]).read().strip() +starts = [i for i, l in enumerate(lines) if l.strip() == "- name: Resolve Go version"] +blocks = [] +for i in starts: + j = next(k for k in range(i, len(lines)) if lines[k].strip() == "run: |") + ind = len(lines[j]) - len(lines[j].lstrip()) + body = [] + for l in lines[j + 1:]: + if l.strip() and len(l) - len(l.lstrip()) <= ind: + break + body.append(l[ind + 2:] if l.strip() else "") + blocks.append("\n".join(body).strip()) +if len(set(blocks)) != 1 or blocks[0] != first: + sys.exit("FAIL the two Resolve Go version steps differ") +EOF + +fail=0 +check() { # fixture want-exit want-version + local got=0 out="$work/out" + : > "$work/output" + GO_MOD="$root/testdata/go-version/$1.go.mod" GITHUB_OUTPUT="$work/output" bash -e "$work/step.sh" > "$out" 2>&1 || got=$? + local version + version=$(sed -n 's/^version=//p' "$work/output") + if [ "$got" = "$2" ] && [ "$version" = "$3" ]; then + echo "ok $1: exit $got, version '${version}'" + else + echo "FAIL $1: exit $got version '${version}', want exit $2 version '$3'"; cat "$out"; fail=1 + fi +} + +check patch 0 1.26 +check minor 0 1.26 +check toolchain-newer 0 1.27 +check toolchain-same 0 1.27 +check two-digit 0 1.10 +check no-go 1 "" +check missing 1 "" +exit $fail diff --git a/testdata/go-version/minor.go.mod b/testdata/go-version/minor.go.mod new file mode 100644 index 0000000..80ec86c --- /dev/null +++ b/testdata/go-version/minor.go.mod @@ -0,0 +1,3 @@ +module example.com/a + +go 1.26 diff --git a/testdata/go-version/no-go.go.mod b/testdata/go-version/no-go.go.mod new file mode 100644 index 0000000..b2aa6f2 --- /dev/null +++ b/testdata/go-version/no-go.go.mod @@ -0,0 +1 @@ +module example.com/a diff --git a/testdata/go-version/patch.go.mod b/testdata/go-version/patch.go.mod new file mode 100644 index 0000000..a5f0bc0 --- /dev/null +++ b/testdata/go-version/patch.go.mod @@ -0,0 +1,3 @@ +module example.com/a + +go 1.26.0 diff --git a/testdata/go-version/toolchain-newer.go.mod b/testdata/go-version/toolchain-newer.go.mod new file mode 100644 index 0000000..dbf5c4c --- /dev/null +++ b/testdata/go-version/toolchain-newer.go.mod @@ -0,0 +1,5 @@ +module example.com/a + +go 1.26.0 + +toolchain go1.27.3 diff --git a/testdata/go-version/toolchain-same.go.mod b/testdata/go-version/toolchain-same.go.mod new file mode 100644 index 0000000..5eb2807 --- /dev/null +++ b/testdata/go-version/toolchain-same.go.mod @@ -0,0 +1,5 @@ +module example.com/a + +go 1.27.1 + +toolchain go1.27.3 diff --git a/testdata/go-version/two-digit.go.mod b/testdata/go-version/two-digit.go.mod new file mode 100644 index 0000000..5ec0a8f --- /dev/null +++ b/testdata/go-version/two-digit.go.mod @@ -0,0 +1,5 @@ +module example.com/a + +go 1.9 + +toolchain go1.10.2