diff --git a/.gitea/workflows/race.yml b/.gitea/workflows/race.yml index d853ae0..b6dbaaa 100644 --- a/.gitea/workflows/race.yml +++ b/.gitea/workflows/race.yml @@ -1,8 +1,10 @@ -# Race, Go. Dispatched by hand, and run as part of the release gates. +# Race, Go. Dispatched by hand, and never a gate on a push or a tag: the release tag is +# cut only after `just gates` has already raced the tree, so this workflow is the +# explicit second opinion, not a step of the release. # # The race detector roughly doubles both time and memory, which the shared runner box # cannot afford on every push. Locally it belongs to `just gates`, which runs it once per -# task; here it is an explicit decision rather than a routine. +# task; here it is a decision rather than a routine. # # Every step is one command, so the step that fails is the gate that failed. name: Race @@ -11,7 +13,6 @@ on: workflow_dispatch: env: - GOAMD64: v3 # One core: parallelism buys no speed here and costs memory the box does not have. GOFLAGS: -p=1 GOMAXPROCS: "2" @@ -19,7 +20,7 @@ env: jobs: race: runs-on: fedora - timeout-minutes: 45 + timeout-minutes: 20 steps: - uses: actions/checkout@v7 @@ -33,4 +34,4 @@ jobs: run: dnf install -y gcc - name: Race - run: go test -race -count=1 -timeout 30m ./... + run: go test -race -count=1 -timeout 10m ./... diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index c1ddeeb..2a2e7aa 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,13 +1,20 @@ # Release, Go binaries. Runs on version tags (v1.2.3) pushed to main. # +# The module sits at the repository root: the toolchain records a version only for a root +# module, measured on go1.27.1, so a build of a module in a subdirectory reports (devel) +# even at its own /vX.Y.Z tag and this workflow's smoke test can never pass for +# it. A Go repository is one module at the root. +# # The version contract these steps implement is in the `release` skill, and its point is # that nothing is injected: the toolchain records the tag into the binary's build # information, so the build simply has to happen at the tag, which the trigger guarantees. # -# The gates run in their own job, once, before the matrix. Putting them inside the matrix -# would run the whole suite and the race detector once per target on the box that also hosts -# the forge. Each job validates the tag for itself rather than passing a value between jobs, -# so no workflow feature has to be trusted for the version to reach the file name. +# The gates run in their own job, once, before the matrix, minus the race detector: race +# never runs on a push path or a tag, and the local gate raced this tree before the tag +# was cut. Putting the gates inside the matrix would run the whole suite once per target +# on the box that also hosts the forge. Each job validates the tag for itself rather than +# passing a value between jobs, so no workflow feature has to be trusted for the version +# to reach the file name. name: Release on: @@ -15,7 +22,6 @@ on: tags: ["v*"] env: - GOAMD64: v3 # The box is shared with the forge, so parallelism is bounded on purpose. The gates job # needs it most; the build jobs inherit it for their parallel compilation. GOFLAGS: -p=1 @@ -24,7 +30,7 @@ env: jobs: gates: runs-on: fedora - timeout-minutes: 25 + timeout-minutes: 10 steps: - uses: actions/checkout@v7 @@ -33,10 +39,10 @@ jobs: go-version-file: go.mod cache: true - - name: Install Perl and gcc - # Perl for the steps below, gcc for the race detector. Both are no-ops where the - # package is already present. - run: dnf install -y perl gcc + - name: Install Perl + # Perl for the steps below. The install is a no-op where the package + # is already present. + run: dnf install -y perl - name: Validate the tag env: @@ -70,7 +76,7 @@ jobs: - name: Tests # The same command as in test.yml, so the floor is the same number everywhere. - run: go test -count=1 -timeout 30m -coverprofile=coverage.out -coverpkg=./arch/...,./asm/...,./ast/...,./disasm/...,./format/...,./lexer/...,./lint/...,./lsp/...,./parser/...,./token/...,./verify/... ./... + run: go test -count=1 -timeout 10m -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/... - name: Coverage floor run: | @@ -84,9 +90,6 @@ jobs: exit($total < 80 ? 1 : 0); ' - - name: Race - run: go test -race -count=1 -timeout 30m ./... - build: runs-on: fedora timeout-minutes: 25 @@ -94,9 +97,12 @@ jobs: strategy: fail-fast: false matrix: - # Portable targets: amd64 (v3 baseline), arm64, loong64 and riscv64 on Linux. - # No 32-bit, no wasm, no macOS, no Windows. FreeBSD waits until verify/jit.go - # ports off syscall.Mprotect, which the freebsd build does not carry. + # Portable targets: amd64, arm64, loong64 and riscv64 on Linux, at the toolchain + # default level. No 32-bit, no wasm, no macOS, no Windows. FreeBSD stays out until + # verify/jit.go ports off syscall.Mprotect: the Go syscall package defines no + # Mprotect for freebsd, and verify/jit.go:50 calls it to drop the write bit from + # the JIT mapping, so every freebsd target fails to build with "undefined: + # syscall.Mprotect" (verified for amd64, arm64 and riscv64 on go1.27.1). include: - goos: linux goarch: amd64 @@ -182,6 +188,11 @@ jobs: timeout-minutes: 15 needs: build permissions: + # contents: read is required for the checkout: a job that declares any + # permissions gets a token scoped to exactly those, and releases: write + # alone leaves the fetch with no read access, which Gitea answers with + # a 404 "Repository not found". Verified on the instance 2026-09-16. + contents: read releases: write steps: - uses: actions/checkout@v7 diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index 21e7d03..10e5cc4 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -21,16 +21,22 @@ on: branches: [development] env: - # Portable baseline, x86-64-v3 minimum. Other GOARCH values ignore it. - GOAMD64: v3 # One core: parallelism buys no speed here and costs memory the box does not have. GOFLAGS: -p=1 GOMAXPROCS: "2" +# A superseded run of the same ref is cancelled instead of queueing behind one that +# no longer matters. Verified on Gitea 1.27.1 on 2026-09-17: a queued run whose ref +# moved on is cancelled before it ever reaches the runner, while a run already +# dispatched there runs to completion. +concurrency: + group: ${{ gitea.workflow }}-${{ gitea.ref }} + cancel-in-progress: true + jobs: test: runs-on: fedora - timeout-minutes: 20 + timeout-minutes: 10 steps: - uses: actions/checkout@v7 @@ -45,6 +51,11 @@ jobs: # no-op where it is already present; drop this step once verified on the box. run: dnf install -y perl + # The steps follow the `gates` order of the justfile contract: build, format, + # vet, test. The vet gate is go vet and go fix -diff, two steps here. + - name: Build + run: go build ./... + - name: Format run: | perl -e ' @@ -62,14 +73,14 @@ jobs: # Exits non-zero when it has something to rewrite, so it needs no output capture. run: go fix -diff ./... - - name: Build - run: go build ./... - - name: Tests - # The sweep is `packages` in the project's justfile and the floor is computed over - # the same product packages as the justfile's `coverpkg`, so the number is the one - # `just test` reports locally. - run: go test -count=1 -timeout 30m -coverprofile=coverage.out -coverpkg=./arch/...,./asm/...,./ast/...,./disasm/...,./format/...,./lexer/...,./lint/...,./lsp/...,./parser/...,./token/...,./verify/... ./... + # The suite must be fast: a push pipeline that cannot finish in a few minutes moves + # its heavy part behind a dispatch. The inner timeout matches the job's, so a + # hanging test reports its own goroutine dump rather than a silent job kill. + # The pattern is `packages` in the project's justfile: the logic packages, since a + # thin cmd/ would drag the total under the floor. release.yml runs the same + # command, so the floor is the same number everywhere. + run: go test -count=1 -timeout 10m -coverprofile=coverage.out ./arch/... ./asm/... ./ast/... ./disasm/... ./format/... ./lexer/... ./lint/... ./lsp/... ./parser/... ./token/... ./verify/... - name: Coverage floor run: |