Skip to content

BUILD.md — SunSail Build & Harness Contract

Living build documentation. Grows with each sprint. Tier-1 authority remains docs/ARCHITECTURE.md; this file records the how of building and verifying.


Toolchain

  • Language floor: go 1.26 (go.mod go directive).
  • Pinned build version: toolchain go1.26.5. With GOTOOLCHAIN=auto (the default), the first go command in this module auto-downloads go1.26.5 if the host toolchain is older. The pin is bumped in later sprints on each Go release; the floor is not.
  • Zero CGo: every build sets CGO_ENABLED=0. This is what makes the binaries statically linked (no libc) and freely cross-compilable.

staticcheck gotcha

staticcheck refuses to analyze a module whose required Go version is newer than the Go release staticcheck itself was built with. A staticcheck built with an older Go (e.g. 1.25.x) fails on this go 1.26 module with:

module requires at least go1.26, but Staticcheck was built with go1.25.3

Fix — build staticcheck with this module's toolchain:

make install-tools      # go install honnef.co/go/tools/cmd/staticcheck@latest

Run from the repo root so the pinned toolchain go1.26.5 is used.


Make targets

Target What it does
make build Host-platform binary → out/sun. CGO_ENABLED=0 -trimpath -ldflags="-s -w -X main.version".
make check Pre-commit gate: go build ./... → go vet ./... → staticcheck ./... → go test ./....
make e2e go test -tags e2e ./test/e2e/... — builds the real binary and drives full flows in a tmpdir.
make differential go test -tags differential ./... — real-network Nile validation (placeholder until S6).
make proto Regenerates internal/rpc/pb/ from the vendored protos. Requires protoc + protoc-gen-go + protoc-gen-go-grpc on PATH (see proto/README.md for pinned versions). Only needed when proto/ changes — the generated *.pb.go are committed.
make release Cross-compiles the 6-binary static matrix → release/ + SHA256SUMS.
make clean Removes out/ and release/.
make install-tools Installs staticcheck built with the module toolchain.

Build tags

  • e2e — the end-to-end harness (test/e2e/harness_test.go). Excluded from the hermetic go test ./... so make check stays fast; an untagged test/e2e/doc.go keeps the package buildable under go build ./....
  • differential — real-network tests against Nile. Never part of go test ./... (hermeticity invariant, CLAUDE.md §3.12).

Static-binary guarantees & verification

make release produces linux|darwin|windows × amd64|arm64 (6 binaries). Guarantees:

  • linux/amd64 < 18 MB (currently ~15.9 MB — ARCHITECTURE Erratum E5: the ~2.4 MB of the S0 skeleton predated the gRPC + protobuf + vendored-proto stack that lands with the network surface).
  • No libc linkage — statically linked, a consequence of CGO_ENABLED=0.

Verifying "not a dynamic executable"

macOS has no ldd and cannot run a Linux binary, so use one of:

  • Always available (host):

    file release/sun-linux-amd64 # => "... statically linked ..."

  • Literal ldd (Linux container, Docker required):

    docker run --rm --platform linux/amd64 -v "$PWD/release":/r \ debian:stable-slim ldd /r/sun-linux-amd64 # => "not a dynamic executable" (ldd exits non-zero for static binaries — expected)

On a native Linux CI host, ldd release/sun-linux-amd64 is used directly.


Version stamping

The binary version is injected at link time:

-ldflags "-X main.version=<v>"

VERSION defaults to git describe --tags --always --dirty, falling back to dev before any tags/git exist. sun --version prints sun version <v>.


SAIL_HOME contract (ARCHITECTURE §2)

The data directory for managed binaries and caches resolves, cross-platform, as:

  1. $SAIL_HOME if set (non-empty); otherwise
  2. <os.UserHomeDir()>/.sail.

A literal ~ is never used. Managed binaries live at <SAIL_HOME>/bin/<name>-<version>[.exe] (.exe only on Windows). Implemented in internal/config/paths.go (Home, ExeSuffix, ManagedBinPath).


Continuous Integration

Three workflows live under .github/workflows/:

  • ci.yml — on push and pull_request to main: make check → make e2e, fully hermetic (no network, no secrets).
  • differential.yml — nightly (03:00 UTC): make differential against Nile plus make fuzz, in parallel jobs.
  • release.yml — on v* tags: make release (the 6-binary matrix + SHA256SUMS), attached to the GitHub Release.

The same make targets documented above run in CI. Full reference — triggers, permissions, the nightly secrets, and platform coverage — in docs/CI.md.