100 lines
4.7 KiB
Markdown
100 lines
4.7 KiB
Markdown
# CI Workflow Design (Plan 4d)
|
||
|
||
**Date:** 2026-04-14
|
||
**Scope:** Plan 4d of the Archduino Go rewrite — validate Go code quality, build health, and release config on every change.
|
||
|
||
## Goal
|
||
|
||
A single Gitea Actions workflow that gives fast, reliable feedback on every push and pull request. It runs unit tests with the race detector, static checks, a build sanity pass, shell-script linting, and validates the goreleaser config — all inside an Arch Linux container for environmental fidelity with the target platform.
|
||
|
||
## Non-goals
|
||
|
||
- No integration tests that invoke a real `archduino install` against live pacman/AUR mirrors. That requires a privileged container and mirror access; it lives in a future plan if ever needed.
|
||
- No `golangci-lint`. The `go vet` + `gofmt` baseline is enough for this codebase; revisit only if linting gaps surface.
|
||
- No coverage reporting or codecov upload.
|
||
- No scheduled/nightly runs. Trigger is push + PR only.
|
||
|
||
## Trigger
|
||
|
||
```yaml
|
||
on:
|
||
push:
|
||
pull_request:
|
||
```
|
||
|
||
Fires on every push to every branch and every pull request. Matches a single-developer workflow where noisy branches aren't a real concern; immediate feedback is worth the runner minutes.
|
||
|
||
## Runner
|
||
|
||
- `runs-on: ubuntu-latest` — same runner label used by the Plan 4c release workflow.
|
||
- `container: archlinux:latest` — tests execute inside a fresh Arch userspace each run, matching Archduino's sole supported OS.
|
||
|
||
**Runner infrastructure:** depends on the same Gitea Actions runner registered for Plan 4c. No new infra introduced by this plan. If the runner is offline, CI queues rather than failing; this is acceptable behavior.
|
||
|
||
## Job steps (single job, sequential)
|
||
|
||
1. **Install container prereqs.** Before `actions/checkout` can run, the Arch image needs git; the later steps also need `base-devel` (for the C toolchain Go occasionally pulls in for cgo-adjacent deps) and `shellcheck`. Run once up front:
|
||
```
|
||
pacman -Sy --needed --noconfirm base-devel git shellcheck
|
||
```
|
||
Note: `go` is intentionally NOT installed here — `actions/setup-go` installs the pinned version.
|
||
|
||
2. **Checkout code.** `actions/checkout@v4` with `fetch-depth: 1` (CI doesn't need history).
|
||
|
||
3. **Set up Go.** `actions/setup-go@v5` with:
|
||
- `go-version-file: go.mod` (pinned, deterministic)
|
||
- `cache: true` (built-in cache of `$GOMODCACHE` + `$GOCACHE`, keyed on `go.sum`)
|
||
|
||
4. **`go vet ./...`** — catches obvious correctness issues the compiler misses.
|
||
|
||
5. **`gofmt` check:**
|
||
```
|
||
diff=$(gofmt -l .)
|
||
if [ -n "$diff" ]; then
|
||
echo "files need gofmt:"; echo "$diff"; exit 1
|
||
fi
|
||
```
|
||
Fails the build if any Go file isn't canonically formatted.
|
||
|
||
6. **`go build ./...`** — compiles every package. Catches deadcode paths and untested packages that `go test` would otherwise skip.
|
||
|
||
7. **`go test -race ./...`** — full unit test suite with the race detector. The engine (DAG scheduler) and `privilege.Acquire` (sudo keepalive goroutine) are the concurrency hotspots this covers.
|
||
|
||
8. **`shellcheck bootstrap.sh`** — lints the user-facing shell installer. `install.sh.legacy` is NOT linted; it's archived code.
|
||
|
||
9. **Install goreleaser + `goreleaser check`:**
|
||
```
|
||
go install github.com/goreleaser/goreleaser/v2@latest
|
||
"$(go env GOPATH)/bin/goreleaser" check
|
||
```
|
||
Validates `.goreleaser.yaml` on every change. Cheap insurance against config drift.
|
||
|
||
## Runtime expectations
|
||
|
||
- Arch image pull: ~15–30s (cached on the runner after first run).
|
||
- `pacman -Sy … base-devel git shellcheck`: ~10–20s.
|
||
- `actions/setup-go` + cache restore: ~5–10s warm, ~30s cold.
|
||
- `go build ./...` + `go test -race ./...`: ~30–60s given 37 test files with mostly mocked I/O.
|
||
- `go vet`, `gofmt`, `shellcheck`, `goreleaser check`: <5s combined.
|
||
- **Total per run:** ~1–2 min warm cache, ~3 min cold.
|
||
|
||
## Error handling
|
||
|
||
- Sequential steps; the first failure fails the workflow. No `continue-on-error` anywhere.
|
||
- `gofmt` check uses an explicit guard (not just `-l`) so a non-empty output is a clear failure.
|
||
- `setup-go` cache misses silently fall back to a clean install — no workflow-breaking behavior.
|
||
|
||
## Caching
|
||
|
||
`actions/setup-go@v5` handles Go build/module caching natively via `cache: true`. No extra cache step. The `pacman` packages are re-installed every run; caching them is not worth the complexity for ~15s of savings on a 90s run.
|
||
|
||
## Files touched
|
||
|
||
- Create: `.gitea/workflows/ci.yml`
|
||
|
||
No other repo changes; the existing tests, `go.mod`, `.goreleaser.yaml`, and `bootstrap.sh` all stay as-is.
|
||
|
||
## Interaction with Plan 4c
|
||
|
||
The release workflow (`.gitea/workflows/release.yml`) is unchanged. Both workflows share the Gitea Actions runner but never run together: CI fires on push/PR, release fires on tag push. Independent files, independent triggers, zero coupling.
|