ci.yml 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  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-6, 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 every release target built by the Jenkins pipeline / Makefile:
  74. # the shipped binary must build for all supported OS/arch pairs from a
  75. # single, dependency-free codebase. Kept in lock-step with those targets
  76. # so a platform-specific break (e.g. a missing build-tagged file for
  77. # freebsd) fails here in CI instead of only in the release build.
  78. run: |
  79. set -e
  80. # GOOS GOARCH [extra env...]
  81. targets="
  82. darwin/amd64
  83. darwin/arm64
  84. freebsd/amd64
  85. freebsd/386
  86. linux/amd64
  87. linux/386
  88. linux/arm/GOARM=6
  89. linux/arm/GOARM=7
  90. linux/arm64
  91. linux/riscv64
  92. linux/mipsle/GOMIPS=softfloat
  93. windows/amd64
  94. windows/386
  95. "
  96. for t in $targets; do
  97. os="${t%%/*}"; rest="${t#*/}"; arch="${rest%%/*}"; extra=""
  98. case "$rest" in */*) extra="${rest#*/}";; esac
  99. echo "==> building $os/$arch $extra"
  100. env GOOS="$os" GOARCH="$arch" $extra go build -o /dev/null .
  101. done
  102. - name: go test
  103. run: go test -count=1 ./...