|
| 1 | +# DO NOT EDIT MANUALLY: content-pinned by limen, reset by `limen fix`. |
| 2 | +# The shared CI lanes every repository runs, called from its own ci.yaml |
| 3 | +# (`uses: ./.github/workflows/limen-verify.yaml`). A change here reaches every |
| 4 | +# repository with its limen bump; the project's own jobs live in its ci.yaml. |
| 5 | +# |
| 6 | +# Minimal glue: GitHub's own actions pinned by SHA, aqua at .aqua/aqua.yaml's |
| 7 | +# pins, `just` running the recipes a laptop runs. No third-party actions, |
| 8 | +# hence no egress-filtering action either. |
| 9 | +name: limen-verify |
| 10 | + |
| 11 | +on: |
| 12 | + workflow_call: |
| 13 | + inputs: |
| 14 | + os: |
| 15 | + # Pinned images, never -latest. macOS is not redundant: its /bin/bash |
| 16 | + # is 3.2, the floor the recipes target. Both windows legs run under |
| 17 | + # git-bash. These names are NOT the ruleset's required contexts (the |
| 18 | + # caller's `gate` is), so a project may trim or extend the list. |
| 19 | + description: The verify matrix's runners, as a JSON list. |
| 20 | + type: string |
| 21 | + default: '["ubuntu-24.04", "ubuntu-24.04-arm", "macos-15", "windows-2025", "windows-11-arm"]' |
| 22 | + |
| 23 | +# No default token permissions: each job states what it needs, within what |
| 24 | +# the caller grants (contents: read). |
| 25 | +permissions: {} |
| 26 | + |
| 27 | +# Explicit bash everywhere: windows defaults to PowerShell and never sees |
| 28 | +# git-bash's environment; explicit bash also brings -eo pipefail. |
| 29 | +defaults: |
| 30 | + run: |
| 31 | + shell: bash |
| 32 | + |
| 33 | +jobs: |
| 34 | + verify: |
| 35 | + strategy: |
| 36 | + fail-fast: false |
| 37 | + matrix: |
| 38 | + os: ${{ fromJSON(inputs.os) }} |
| 39 | + runs-on: ${{ matrix.os }} |
| 40 | + # Generous for the windows legs, which are markedly slower. |
| 41 | + timeout-minutes: 45 |
| 42 | + permissions: |
| 43 | + contents: read |
| 44 | + steps: |
| 45 | + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
| 46 | + with: |
| 47 | + # The token is not left behind in .git/config: nothing in this |
| 48 | + # workflow talks to GitHub after checkout. |
| 49 | + persist-credentials: false |
| 50 | + # Full history and refs: `just do lint commits` validates the commit |
| 51 | + # range against the PR's base branch, which a shallow clone lacks. |
| 52 | + fetch-depth: 0 |
| 53 | + |
| 54 | + # Windows keeps every cache below in one disk image, cached as a single |
| 55 | + # file: restored as files, they cost minutes of small-file writes there. |
| 56 | + # Before setup-aqua, which takes the AQUA_ROOT_DIR it sets; the hash is |
| 57 | + # taken here because hashFiles cannot walk into the attached image. |
| 58 | + - id: image |
| 59 | + if: runner.os == 'Windows' |
| 60 | + uses: ./.github/actions/windows-cache-image |
| 61 | + with: |
| 62 | + mode: attach |
| 63 | + prefix: win-image-${{ runner.os }}-${{ runner.arch }}- |
| 64 | + hash: ${{ hashFiles('.aqua/aqua.yaml', '.aqua/aqua-checksums.json', '.limen/aqua.yaml', '.limen/aqua-registry.yaml', '**/go.sum') }} |
| 65 | + |
| 66 | + - name: Install aqua (pinned, checksum-verified) |
| 67 | + uses: ./.github/actions/setup-aqua |
| 68 | + |
| 69 | + # aqua's package store, keyed on the exact pins and per architecture |
| 70 | + # (the store holds native binaries). A warm store turns every lazy first |
| 71 | + # use below into a link; the fallback key keeps the tools a pin bump did |
| 72 | + # not touch, and the changed one downloads and verifies as before. |
| 73 | + - if: runner.os != 'Windows' |
| 74 | + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 |
| 75 | + with: |
| 76 | + path: ${{ env.AQUA_ROOT_DIR }}/pkgs |
| 77 | + key: aqua-pkgs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.aqua/aqua.yaml', '.aqua/aqua-checksums.json', '.limen/aqua-registry.yaml') }} |
| 78 | + restore-keys: | |
| 79 | + aqua-pkgs-${{ runner.os }}-${{ runner.arch }}- |
| 80 | +
|
| 81 | + - name: Install pinned tools |
| 82 | + # Link-only: tools download lazily on first use, checksum-verified; a |
| 83 | + # job pays only for what its recipes run. |
| 84 | + run: aqua install --only-link |
| 85 | + |
| 86 | + # The Go build and module caches, plus the linter's (build/cache/, see |
| 87 | + # main.just). The per-platform analysis legs compile the whole module |
| 88 | + # graph five times, cold, on every run without this. Paths come from the |
| 89 | + # pinned go itself — they differ per OS — and the key carries its version |
| 90 | + # and every go.sum; a stale restore is harmless (the build cache is |
| 91 | + # content-addressed, the module cache is checksum-verified), so the |
| 92 | + # fallback key is always worth taking. |
| 93 | + - id: gocache |
| 94 | + if: runner.os != 'Windows' |
| 95 | + run: | |
| 96 | + echo "build=$(go env GOCACHE)" >> "$GITHUB_OUTPUT" |
| 97 | + echo "mod=$(go env GOMODCACHE)" >> "$GITHUB_OUTPUT" |
| 98 | + echo "version=$(go env GOVERSION)" >> "$GITHUB_OUTPUT" |
| 99 | + - if: runner.os != 'Windows' |
| 100 | + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 |
| 101 | + with: |
| 102 | + path: | |
| 103 | + ${{ steps.gocache.outputs.build }} |
| 104 | + ${{ steps.gocache.outputs.mod }} |
| 105 | + build/cache |
| 106 | + key: go-${{ runner.os }}-${{ runner.arch }}-${{ steps.gocache.outputs.version }}-${{ hashFiles('**/go.sum') }} |
| 107 | + restore-keys: | |
| 108 | + go-${{ runner.os }}-${{ runner.arch }}-${{ steps.gocache.outputs.version }}- |
| 109 | + go-${{ runner.os }}-${{ runner.arch }}- |
| 110 | +
|
| 111 | + - name: Lint |
| 112 | + run: just lint |
| 113 | + |
| 114 | + - name: Test |
| 115 | + run: just test |
| 116 | + |
| 117 | + # On a miss, the image this run filled becomes the key's; a hit needs no |
| 118 | + # save, since a cache key is never overwritten. |
| 119 | + - if: runner.os == 'Windows' && steps.image.outputs.cache-hit != 'true' |
| 120 | + uses: ./.github/actions/windows-cache-image |
| 121 | + with: |
| 122 | + mode: save |
| 123 | + key: ${{ steps.image.outputs.key }} |
| 124 | + |
| 125 | + # One linux leg, not the matrix: fuzzing explores the same corpus wherever |
| 126 | + # it runs, so more legs cost CPU and add nothing. The recipe passes on a |
| 127 | + # tree with no targets or no Go module, so this job is safe in every |
| 128 | + # repository. |
| 129 | + fuzz: |
| 130 | + runs-on: ubuntu-24.04 |
| 131 | + timeout-minutes: 30 |
| 132 | + permissions: |
| 133 | + contents: read |
| 134 | + steps: |
| 135 | + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
| 136 | + with: |
| 137 | + persist-credentials: false |
| 138 | + |
| 139 | + - name: Install aqua (pinned, checksum-verified) |
| 140 | + uses: ./.github/actions/setup-aqua |
| 141 | + |
| 142 | + - name: Install pinned tools |
| 143 | + run: aqua install --only-link |
| 144 | + |
| 145 | + # The cached corpus is what makes fuzzing cumulative across runs. Keyed |
| 146 | + # on the fuzz sources so a changed target restarts its corpus; |
| 147 | + # restore-keys keep the rest. |
| 148 | + - id: fuzzdir |
| 149 | + run: echo "dir=$(go env GOCACHE)/fuzz" >> "$GITHUB_OUTPUT" |
| 150 | + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 |
| 151 | + with: |
| 152 | + path: ${{ steps.fuzzdir.outputs.dir }} |
| 153 | + key: fuzz-corpus-${{ runner.os }}-${{ hashFiles('**/*_fuzz_test.go', '**/fuzz_test.go') }} |
| 154 | + restore-keys: | |
| 155 | + fuzz-corpus-${{ runner.os }}- |
| 156 | +
|
| 157 | + - name: Fuzz |
| 158 | + run: just do test go fuzz |
| 159 | + |
| 160 | + # A crasher is a bug with a reproducer attached; the log names the |
| 161 | + # target, the input file is what reproduces it locally. Uploaded only |
| 162 | + # on failure, and only if any was written. |
| 163 | + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 |
| 164 | + if: failure() |
| 165 | + with: |
| 166 | + name: fuzz-crashers |
| 167 | + path: '**/testdata/fuzz/' |
| 168 | + if-no-files-found: ignore |
| 169 | + |
| 170 | + # Every pin resolves. The verify legs link only, and a link verifies |
| 171 | + # nothing: an asset that is missing from a release, or that fails its |
| 172 | + # checksum, would merge green and fail at first use. One linux leg pays for |
| 173 | + # the real install; aqua's package store is cached on the exact pins, so a |
| 174 | + # warm key costs seconds and a changed pin is always a real install. (The |
| 175 | + # Go-built tools are tools/go.mod directives, built by the verify legs.) |
| 176 | + tools: |
| 177 | + runs-on: ubuntu-24.04 |
| 178 | + timeout-minutes: 30 |
| 179 | + permissions: |
| 180 | + contents: read |
| 181 | + steps: |
| 182 | + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 |
| 183 | + with: |
| 184 | + persist-credentials: false |
| 185 | + |
| 186 | + - name: Install aqua (pinned, checksum-verified) |
| 187 | + uses: ./.github/actions/setup-aqua |
| 188 | + |
| 189 | + - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 |
| 190 | + with: |
| 191 | + path: ${{ env.AQUA_ROOT_DIR }}/pkgs |
| 192 | + # The same key as the verify legs', so this job and the linux leg |
| 193 | + # share one store — and no fallback key here: a changed pin must be |
| 194 | + # a real install, which is what this job exists to prove. |
| 195 | + key: aqua-pkgs-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('.aqua/aqua.yaml', '.aqua/aqua-checksums.json', '.limen/aqua-registry.yaml') }} |
| 196 | + |
| 197 | + - name: Install every pinned tool |
| 198 | + run: aqua install |
0 commit comments