Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 174 additions & 0 deletions .github/workflows/ci-go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
name: CI (Go)

# The toolchain-level check for a Go module: tidy modules, formatting, lint, tests
# with the race detector, a vulnerability scan and a build. Repos call it from a thin
# wrapper, so a fix here lands everywhere at once.
#
# go.mod is the source of truth for the Go version. golangci-lint and govulncheck are
# run with `go run <pkg>@<version>` rather than downloaded as binaries: a linter built
# with an older Go than the code it reads refuses to run, and building it with the
# module's own toolchain rules that out. The Go checksum database verifies what is
# fetched, and the same command works on a developer's machine, so local and CI
# results match.
#
# Nothing here reads a secret, so it is safe on pull requests from forks.
#
# jobs:
# go:
# permissions:
# contents: read
# uses: studiobimo/.github/.github/workflows/ci-go.yml@v1

on:
workflow_call:
inputs:
working-directory:
description: Directory holding go.mod.
type: string
required: false
default: .
go-version-file:
description: File that names the Go version, relative to working-directory.
type: string
required: false
default: go.mod
golangci-lint-version-file:
description: >-
File holding the golangci-lint version (for example v2.14.0), relative to
working-directory, so local runs and CI share one pin. Empty to disable.
type: string
required: false
default: .golangci-lint-version
golangci-lint-version:
description: golangci-lint version, used when golangci-lint-version-file does not exist.
type: string
required: false
default: v2.14.0
govulncheck-version:
description: govulncheck version. Empty skips the vulnerability scan.
type: string
required: false
default: v1.8.0
packages:
description: Package pattern to lint, test and build.
type: string
required: false
default: ./...
test-args:
description: Extra arguments for `go test`, space separated.
type: string
required: false
default: -race
runs-on:
description: Runner label.
type: string
required: false
default: ubuntu-latest
timeout-minutes:
description: Job timeout. The linter compiles from source on a cold cache.
type: number
required: false
default: 20

permissions: {}

jobs:
check:
name: Go
runs-on: ${{ inputs.runs-on }}
timeout-minutes: ${{ inputs.timeout-minutes }}
permissions:
contents: read
defaults:
run:
working-directory: ${{ inputs.working-directory }}
env:
PACKAGES: ${{ inputs.packages }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: ${{ inputs.working-directory }}/${{ inputs.go-version-file }}
cache-dependency-path: |
${{ inputs.working-directory }}/go.sum
${{ inputs.working-directory }}/go.mod

# One file is the source of truth for the linter version, shared with the
# repo's Makefile. The input is the fallback.
- name: Resolve the golangci-lint version
id: lint
env:
VERSION_FILE: ${{ inputs.golangci-lint-version-file }}
FALLBACK: ${{ inputs.golangci-lint-version }}
run: |
if [ -n "${VERSION_FILE}" ] && [ -f "${VERSION_FILE}" ]; then
version="$(tr -d '[:space:]' < "${VERSION_FILE}")"
echo "Using ${VERSION_FILE}: ${version}"
else
version="${FALLBACK}"
echo "Using the golangci-lint-version input: ${version}"
fi
echo "run=go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@${version}" >> "${GITHUB_OUTPUT}"

- name: Modules are verified and tidy
run: |
go mod verify
go mod tidy -diff

- name: Format
env:
GOLANGCI_LINT: ${{ steps.lint.outputs.run }}
run: |
# shellcheck disable=SC2086 # a command line and a package pattern, split deliberately
${GOLANGCI_LINT} fmt --diff ${PACKAGES}

- name: Lint
env:
GOLANGCI_LINT: ${{ steps.lint.outputs.run }}
run: |
# shellcheck disable=SC2086 # a command line and a package pattern, split deliberately
${GOLANGCI_LINT} run ${PACKAGES}

- name: Test
env:
TEST_ARGS: ${{ inputs.test-args }}
run: |
read -ra args <<< "${TEST_ARGS}"
go test "${args[@]}" -covermode=atomic -coverprofile="${RUNNER_TEMP}/coverage.out" "${PACKAGES}"

# Always: a failed run is exactly when the numbers are worth reading.
- name: Summarise coverage
if: always()
run: |
profile="${RUNNER_TEMP}/coverage.out"
if [ ! -s "${profile}" ]; then
echo "No coverage profile was written." >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
{
echo "### Go test coverage"
echo
echo '```text'
go tool cover -func="${profile}" | tail -n 1
echo '```'
echo
echo "<details><summary>Per function</summary>"
echo
echo '```text'
go tool cover -func="${profile}"
echo '```'
echo
echo "</details>"
} >> "${GITHUB_STEP_SUMMARY}"

- name: Vulnerability scan
if: inputs.govulncheck-version != ''
env:
GOVULNCHECK_VERSION: ${{ inputs.govulncheck-version }}
run: go run "golang.org/x/vuln/cmd/govulncheck@${GOVULNCHECK_VERSION}" "${PACKAGES}"

- name: Build
run: go build "${PACKAGES}"
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ everywhere at once.

## Reusable workflows

| Workflow | What it does |
| ----------------------------------------- | -------------------------------------------------------------------------------- |
| `.github/workflows/ci-pr.yml` | Conventional PR title and branch name, PR size limit, Conventional Commits |
| `.github/workflows/ci-java-gradle.yml` | Java toolchain, Gradle build, test reports and artifacts |
| `.github/workflows/ci-workflows.yml` | actionlint, zizmor and shellcheck over a repo's own workflows |
| `.github/workflows/ci-pre-commit.yml` | A repo's own pre-commit hooks, over the whole tree |
| `.github/workflows/ci-gitleaks.yml` | Secret scan of what a pull request adds |
| `.github/workflows/ci-template-drift.yml` | Compares a repo with the project template and tracks the difference in one issue |
| `.github/workflows/release-please.yml` | Grooms a release pull request from Conventional Commits, then tags and releases |
| `.github/workflows/publish-mod.yml` | Builds, attests and publishes a Minecraft mod to Modrinth, CurseForge and GitHub |
| Workflow | What it does |
| ----------------------------------------- | ---------------------------------------------------------------------------------- |
| `.github/workflows/ci-pr.yml` | Conventional PR title and branch name, PR size limit, Conventional Commits |
| `.github/workflows/ci-java-gradle.yml` | Java toolchain, Gradle build, test reports and artifacts |
| `.github/workflows/ci-go.yml` | Go modules, formatting, golangci-lint, race tests, coverage, govulncheck and build |
| `.github/workflows/ci-workflows.yml` | actionlint, zizmor and shellcheck over a repo's own workflows |
| `.github/workflows/ci-pre-commit.yml` | A repo's own pre-commit hooks, over the whole tree |
| `.github/workflows/ci-gitleaks.yml` | Secret scan of what a pull request adds |
| `.github/workflows/ci-template-drift.yml` | Compares a repo with the project template and tracks the difference in one issue |
| `.github/workflows/release-please.yml` | Grooms a release pull request from Conventional Commits, then tags and releases |
| `.github/workflows/publish-mod.yml` | Builds, attests and publishes a Minecraft mod to Modrinth, CurseForge and GitHub |

A repo with a `.pre-commit-config.yaml` should call `ci-pre-commit.yml` **instead of**
`ci-workflows.yml` and `ci-gitleaks.yml`, not alongside them. Those two download actionlint,
Expand Down
8 changes: 8 additions & 0 deletions workflow-templates/ci-go.properties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "CI — Go",
"description": "Runs the centralized ci-go, ci-workflows and ci-gitleaks reusables on every pull request. Trim the jobs this repo does not need.",
"creator": "studiobimo",
"iconName": "checklist",
"categories": ["Continuous integration", "Go"],
"filePatterns": ["go.mod$"]
}
42 changes: 42 additions & 0 deletions workflow-templates/ci-go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI

# Starter: the per-pull-request checks for a Go repo, each a thin caller of a
# centralized reusable. Copied into your repo and owned by it from then on -- edit freely.
#
# ci-go reads the Go version from go.mod and the golangci-lint version from
# .golangci-lint-version, so both stay pinned in the repo, next to the code.
#
# This is deliberately not path-filtered: a skipped job reports no status, and a
# required check that never reports blocks the pull request forever.
#
# A repo with a .pre-commit-config.yaml should call ci-pre-commit instead of the
# `workflows` and `secrets-scan` jobs, and pass `skip:` for any Go hooks, which the
# `go` job already covers.

on:
push:
branches: [$default-branch]
pull_request:
merge_group:

permissions: {}

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
go:
permissions:
contents: read
uses: studiobimo/.github/.github/workflows/ci-go.yml@v1

workflows:
permissions:
contents: read
uses: studiobimo/.github/.github/workflows/ci-workflows.yml@v1

secrets-scan:
permissions:
contents: read
uses: studiobimo/.github/.github/workflows/ci-gitleaks.yml@v1
Loading