ci.yml 3.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. name: CI
  2. # Enforces the contribution rules documented in CLAUDE.md.
  3. # Formatting and the convention checker are scoped to the change's diff so that
  4. # grandfathered legacy code is not punished; build/test run module-wide, and
  5. # go vet runs module-wide but advisory-only (it never fails CI on legacy code).
  6. on:
  7. push:
  8. branches: ["**"]
  9. pull_request:
  10. permissions:
  11. contents: read
  12. jobs:
  13. verify:
  14. runs-on: ubuntu-latest
  15. defaults:
  16. run:
  17. working-directory: src
  18. steps:
  19. - name: Checkout (full history for diffing)
  20. uses: actions/checkout@v4
  21. with:
  22. fetch-depth: 0
  23. - name: Set up Go
  24. uses: actions/setup-go@v5
  25. with:
  26. go-version: "1.24"
  27. cache-dependency-path: src/go.sum
  28. - name: Resolve diff base
  29. id: base
  30. working-directory: ${{ github.workspace }}
  31. run: |
  32. if [ "${{ github.event_name }}" = "pull_request" ]; then
  33. base="${{ github.event.pull_request.base.sha }}"
  34. else
  35. base="${{ github.event.before }}"
  36. fi
  37. # Use the base only if it is a real commit present in this checkout.
  38. # Push events can report a github.event.before that was force-pushed
  39. # away or never fetched (git then errors "fatal: bad object"); fall
  40. # back to HEAD's parent, and finally to empty so the diff-scoped steps
  41. # below skip gracefully instead of crashing the job under `bash -e`.
  42. if ! git rev-parse --verify --quiet "${base}^{commit}" >/dev/null 2>&1; then
  43. base="$(git rev-parse --verify --quiet 'HEAD~1^{commit}' 2>/dev/null || true)"
  44. fi
  45. echo "sha=$base" >> "$GITHUB_OUTPUT"
  46. echo "Diff base: ${base:-<none; diff-scoped checks skipped>}"
  47. - name: Convention checker (rules 1-5, diff only)
  48. if: steps.base.outputs.sha != ''
  49. working-directory: ${{ github.workspace }}
  50. run: sh scripts/check-conventions.sh --diff "${{ steps.base.outputs.sha }}"
  51. - name: gofmt (changed Go files)
  52. if: steps.base.outputs.sha != ''
  53. working-directory: ${{ github.workspace }}
  54. run: |
  55. files=$(git diff --name-only --diff-filter=ACM "${{ steps.base.outputs.sha }}" -- '*.go' || true)
  56. [ -z "$files" ] && { echo "No Go files changed."; exit 0; }
  57. unformatted=$(gofmt -l $files)
  58. if [ -n "$unformatted" ]; then
  59. echo "These files are not gofmt-clean:"; echo "$unformatted"
  60. echo "Run: gofmt -w <file>"; exit 1
  61. fi
  62. - name: go vet (advisory — never blocks CI)
  63. # Module-wide vet surfaces pre-existing issues in grandfathered legacy
  64. # code that this project intentionally does not modify, so it is run for
  65. # visibility only and never fails the build. Enforcement of the
  66. # contribution rules on NEW code is handled by the diff-scoped
  67. # convention checker above.
  68. continue-on-error: true
  69. run: go vet ./...
  70. - name: go build (all packages, native)
  71. run: go build ./...
  72. - name: Cross-compile smoke test (portability, rule 5)
  73. # Mirrors the release targets in the Makefile: the shipped binary must
  74. # build for other OSes from a single, dependency-free codebase.
  75. run: |
  76. GOOS=windows GOARCH=amd64 go build -o /dev/null .
  77. GOOS=darwin GOARCH=arm64 go build -o /dev/null .
  78. GOOS=linux GOARCH=arm GOARM=6 go build -o /dev/null .
  79. - name: go test
  80. run: go test -count=1 ./...