Go #102
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Go | |
| on: | |
| push: | |
| branches: [ "main" ] | |
| tags: [ "v*" ] | |
| pull_request: | |
| branches: [ "main" ] | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Cache Go build artifacts | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| - name: Verify dependencies | |
| run: go mod verify | |
| - name: Build | |
| run: go build -v -buildvcs=false ./... | |
| - name: Test | |
| run: go test -v -race ./... | |
| - name: Run golangci-lint | |
| uses: golangci/golangci-lint-action@v9 | |
| with: | |
| version: latest | |
| # Single job that owns release creation. It runs once (not a matrix), so | |
| # exactly one job ever creates the release for the tag. The upload-assets | |
| # matrix below then only adds files to this already-existing release and can | |
| # never race to create it (the race that made action-gh-release v3 fail with | |
| # "already_exists (tag_name)"). | |
| # | |
| # The release is created as a draft so it is not publicly visible while the | |
| # upload matrix is still attaching assets. The publish-release job flips it to | |
| # published only after all assets are present, so the public never sees a | |
| # partial or empty release. | |
| create-release: | |
| needs: build | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Create draft release for tag | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| draft: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| upload-assets: | |
| needs: create-release | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| permissions: | |
| contents: write | |
| strategy: | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| goos: linux | |
| goarch: amd64 | |
| artifact_name: subenum | |
| asset_name: subenum-linux-amd64 | |
| - os: windows-latest | |
| goos: windows | |
| goarch: amd64 | |
| artifact_name: subenum.exe | |
| asset_name: subenum-windows-amd64.exe | |
| - os: macos-latest | |
| goos: darwin | |
| goarch: arm64 | |
| artifact_name: subenum | |
| asset_name: subenum-macos-arm64 | |
| - os: ubuntu-latest | |
| goos: darwin | |
| goarch: amd64 | |
| artifact_name: subenum | |
| asset_name: subenum-macos-amd64 | |
| - os: ubuntu-latest | |
| goos: linux | |
| goarch: arm64 | |
| artifact_name: subenum | |
| asset_name: subenum-linux-arm64 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Set up Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Cache Go build artifacts | |
| uses: actions/cache@v5 | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.mod') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go- | |
| # TEMPORARY failure injection for rc5 cleanup validation. REMOVE before merge. | |
| - name: Inject failure for cleanup validation | |
| if: matrix.goos == 'windows' | |
| shell: bash | |
| run: | | |
| echo "Intentional failure to validate cleanup-failed-release." | |
| exit 1 | |
| - name: Build | |
| env: | |
| GOOS: ${{ matrix.goos }} | |
| GOARCH: ${{ matrix.goarch }} | |
| run: go build -v -buildvcs=false -o ${{ matrix.artifact_name }} | |
| - name: Rename for release | |
| shell: bash | |
| run: | | |
| if [ "${{ matrix.artifact_name }}" != "${{ matrix.asset_name }}" ]; then | |
| cp ${{ matrix.artifact_name }} ${{ matrix.asset_name }} | |
| fi | |
| - name: Generate checksum | |
| shell: bash | |
| run: sha256sum ${{ matrix.asset_name }} > ${{ matrix.asset_name }}.sha256 | |
| # The release already exists (created by create-release), so each matrix leg | |
| # only adds its binary and checksum. No matrix job creates the release, which | |
| # avoids the concurrent-create race entirely. draft: true is set explicitly | |
| # so these update calls keep the release in draft (the action's draft input | |
| # defaults to false and would otherwise publish it early). | |
| - name: Upload assets to draft release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| draft: true | |
| files: | | |
| ${{ matrix.asset_name }} | |
| ${{ matrix.asset_name }}.sha256 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Single job that publishes the release once, only after the full asset set is | |
| # attached. Flipping draft to false here is the moment the release becomes | |
| # public, so there is no window where a partial or empty release is visible. | |
| publish-release: | |
| needs: upload-assets | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Publish release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| draft: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Failure-path cleanup. If the upload matrix fails partway, publish-release is | |
| # skipped and the draft release created above would otherwise linger | |
| # invisibly until someone removes it by hand. This job runs only when a | |
| # tag-triggered release run failed. It deletes the draft release for this tag | |
| # and the tag ref so a partial release leaves nothing behind. It only ever | |
| # deletes a draft (never a published release) and is a no-op when there is | |
| # nothing to clean, so it is safe and idempotent. | |
| cleanup-failed-release: | |
| needs: [create-release, upload-assets] | |
| if: ${{ failure() && startsWith(github.ref, 'refs/tags/v') }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Remove draft release and tag for failed run | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ github.ref_name }} | |
| REPO: ${{ github.repository }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| rel=$(gh api "repos/${REPO}/releases" --jq "([.[] | select(.tag_name==\"${TAG}\")][0]) // empty") | |
| if [ -z "${rel}" ]; then | |
| echo "No release found for ${TAG}; nothing to clean." | |
| exit 0 | |
| fi | |
| id=$(printf '%s' "${rel}" | jq -r '.id') | |
| draft=$(printf '%s' "${rel}" | jq -r '.draft') | |
| if [ "${draft}" != "true" ]; then | |
| echo "Release ${id} for ${TAG} is published (draft=${draft}); leaving it intact." | |
| exit 0 | |
| fi | |
| echo "Deleting draft release ${id} for ${TAG}." | |
| gh api -X DELETE "repos/${REPO}/releases/${id}" | |
| echo "Deleting tag ref ${TAG}." | |
| gh api -X DELETE "repos/${REPO}/git/refs/tags/${TAG}" || echo "Tag ref already removed." | |
| echo "Cleanup complete for ${TAG}." |