Documentation
¶
Overview ¶
Package cigate is the local CI gate: the one definition of what "CI passes" means for a forge project, runnable on a laptop and from a workflow.
A scaffolded workflow is a thin wrapper that prepares the environment (checkout, toolchains, caches) and calls `task ci:checks`, `task test` and `task test:integration`. `task ci` runs all three locally, so "CI did not run" never has to mean "unverified". This package is what makes the first and last of those honest, with four rules:
- Every check runs, even after one fails, so one pass shows every failure. The verdict is a summary table, and the gate exits non-zero if any row failed.
- A check that could not run (a missing tool or prerequisite) is SKIPPED, never PASS, and fails the gate unless CI_SKIP names it. A silent skip is false confidence. Rows a project's own configuration or kind makes moot are N/A, which is not a skip and does not fail the gate.
- Tools run at the versions CI pins (tools.go), not whatever is on PATH.
- Under GitHub Actions the table is appended to $GITHUB_STEP_SUMMARY.
Index ¶
Constants ¶
const SkipEnv = "CI_SKIP"
SkipEnv names the checks a caller has decided not to run, comma-separated.
Variables ¶
This section is empty.
Functions ¶
func HasIntegrationTests ¶
HasIntegrationTests reports whether any Go test file under root carries the `integration` build tag. The scaffolded workflow asks the same question in its `changes` job (git grep for the same constraint) to decide whether the integration-test job runs at all.
func ParseSkips ¶
ParseSkips splits a CI_SKIP value into check names.
func UnknownSkips ¶
UnknownSkips returns the names in skips that no check carries. A typo in CI_SKIP would otherwise quietly skip nothing, or — worse — be read as the caller having accounted for a check they did not.
Types ¶
type Check ¶
type Check struct {
Name string
Lane Lane
// NotApplicable, when set, reports the row as N/A with this reason and
// runs nothing.
NotApplicable string
// Done returns a non-empty note when the check's work is already done
// (dependencies installed); the row is PASS and nothing runs.
Done func() string
// Prereq returns a non-empty reason when the check cannot run here. The
// row is then SKIPPED, which fails the gate.
Prereq func() string
// Tools are pinned tools the command needs. Each is resolved to its
// pinned version before the command runs, and its directory is put first
// on the command's PATH.
Tools []Tool
Argv []string
// Dir is project-relative; empty is the project root.
Dir string
Env []string
// OnFail runs after the command failed, to undo what it left behind.
OnFail func(out io.Writer)
}
Check is one row of the gate.
type ExecFunc ¶
type ExecFunc func(ctx context.Context, dir string, env []string, argv []string, out io.Writer) error
ExecFunc runs argv in dir with env, writing combined output to out.
type Lane ¶
type Lane string
Lane is a group of checks a CI job runs together.
const ( // LaneChecks is the static and build lane: the `checks` CI job. LaneChecks Lane = "checks" // LaneTest is the unit-test lane: `task test`, the `test` CI job. LaneTest Lane = "test" // LaneIntegration is `task test:integration`, the `integration-test` job. LaneIntegration Lane = "integration" )
type Options ¶
type Options struct {
// Out receives the checks' own output and the summary. Defaults to
// os.Stdout.
Out io.Writer
// Skip names the checks the caller chose not to run.
Skip []string
// Resolver pins tools. Nil means checks that declare Tools cannot run.
Resolver *Resolver
// Exec runs one command. Nil uses os/exec; tests substitute.
Exec ExecFunc
// Root is the project root.
Root string
// Groups wraps each check's output in GitHub Actions ::group:: lines.
Groups bool
// Env is the base environment; nil is os.Environ().
Env []string
}
Options configure a Run.
type PlanInput ¶
type PlanInput struct {
// Root is the project root.
Root string
// CI is the data the scaffolded ci.yml renders with: which lints, scanners
// and jobs the project has. The gate reads the same struct so a toggle
// means the same thing in both.
CI templates.CIWorkflowData
// Cfg supplies the project's extra checks and its derived features.
Cfg *config.ProjectConfig
// Forge is the argv that re-enters this forge binary.
Forge []string
}
PlanInput is everything the plan is derived from.
type Report ¶
Report is every row of one gate run.
func Run ¶
Run executes checks in order and returns the report. It never stops early: a failing check is a row, not an abort.
func (Report) Failed ¶
Failed reports whether the gate is red: any FAIL, or any SKIPPED the caller did not allow.
func (Report) RenderMarkdown ¶
RenderMarkdown is the same table for $GITHUB_STEP_SUMMARY.
func (Report) RenderText ¶
RenderText writes the summary table.
type Resolver ¶
type Resolver struct {
// CacheRoot is where pinned tools are installed.
CacheRoot string
// ProjectDir selects the Go toolchain tools are built with: the one `go`
// picks for the project, so a linter is never built with a Go older than
// the module it must lint.
ProjectDir string
// Note receives one line per install, so a first-run build is explained.
Note io.Writer
LookPath func(string) (string, error)
// Probe reports the version of the binary at path, or "".
Probe func(path string, t Tool) string
// Install builds t into dir.
Install func(ctx context.Context, t Tool, dir, toolchain string) error
// Toolchain reports the Go release `go` selects for ProjectDir.
Toolchain func(ctx context.Context) string
// ModuleGo is the project's go directive ("1.27.0"), "" when unknown.
ModuleGo string
// BuiltWith reports the Go release a binary was built with ("go1.27.2").
BuiltWith func(path string) string
// contains filtered or unexported fields
}
Resolver turns a Tool into a directory to put first on PATH.
func NewResolver ¶
NewResolver is the production resolver.
type Result ¶
type Result struct {
Name string
Lane Lane
Status Status
Duration time.Duration
Note string
// Allowed marks a SKIPPED row the caller asked for (CI_SKIP), which does
// not fail the gate.
Allowed bool
// Tail is the end of a failed command's output, for the job summary.
Tail string
}
Result is the outcome of one Check.
type Status ¶
type Status string
Status is one row's verdict.
const ( // Pass is a check that ran and succeeded. Pass Status = "PASS" // Fail is a check that ran and failed. Fail Status = "FAIL" // Skip is a check that did not run. It fails the gate unless Allowed. Skip Status = "SKIPPED" // NA is a check this project has nothing for. It is not a skip. NA Status = "N/A" )
type Tally ¶
type Tally struct{ Passed, Failed, Skipped, Allowed, NA int }
Tally is the rows of a report counted by outcome. Skipped counts only the SKIPPED rows that fail the gate; Allowed counts the ones CI_SKIP named.
type Tool ¶
type Tool struct {
Binary string
// Module is the `go install` package path.
Module string
// BuildModule is the module path the binary's build info records, when
// that differs from Module's directory (golangci-lint's /cmd/ suffix).
BuildModule string
// Version is the pin, with the leading "v".
Version string
// MatchGo requires the binary to have been built with a Go at least as
// new as the project's go directive: golangci-lint cannot analyze Go newer
// than the Go it was built with, so a PATH copy at the right VERSION but
// built with an older Go is the wrong binary.
MatchGo bool
// VersionArgs and VersionRE read the version from the binary's own output
// when its build info does not carry one (release builds record
// "(devel)").
VersionArgs []string
VersionRE *regexp.Regexp
}
Tool is a binary the gate runs at a pinned version. CI installs these at the same versions (the scaffolded ci.yml renders the same templates constants), so a developer's `task ci` and the workflow judge the same code with the same linters. A tool on PATH at another version is not used: the pin is installed into forge's tool cache and put ahead of it for that command only. The developer's GOBIN and shell PATH are never touched.