Skip to content

Week 1 - The Toolchain and the Build Pipeline

1.1 Conceptual Core

  • The Go toolchain is a single binary that bundles the compiler, linker, formatter, dependency manager, test runner, race detector, profiler, and tracer. Every other ecosystem you've used distributes these as separate tools; Go's choice to integrate them is itself a design statement.
  • go build is not just a compiler invocation. It is a dependency graph walker that:
  • Resolves the module graph (go.mod + go.sum).
  • Computes the build action graph (run with go build -x or go build -n to inspect).
  • Compiles each package to an archive (.a) cached in $GOCACHE (default $HOME/.cache/go-build).
  • Links into a final binary or .so/.a.
  • The build cache is content-addressed. Identical inputs → identical outputs → cache hit. This is what makes go build feel instantaneous on second invocations.

1.2 Mechanical Detail

  • Module mode is the only mode. GOPATH mode is dead-do not start a project under $GOPATH/src in 2026.
  • go.mod directives: module, go, toolchain, require, replace, exclude, retract. Memorize all of them.
  • Minimum Version Selection (MVS): Go's resolver picks the minimum version of each dependency that satisfies all require directives. This is the opposite of npm/pip "latest compatible." Read Russ Cox's MVS paper.
  • go.sum is a content-addressed integrity ledger, not a lock file. It records hashes of every module version ever depended on, including transitively-dropped versions. Never edit by hand.
  • The vendor/ directory: dead in OSS, alive in air-gapped enterprise. Use go mod vendor only when offline builds are mandatory.
  • Useful introspection commands:
  • `go env - every environment variable the toolchain consults.
  • `go list -m all - every module in the build.
  • `go list -deps -json ./... - the package graph as JSON.
  • `go version -m - the modules embedded in a built binary (BuildInfo).

1.3 Lab-"Hello World, Audited"

  1. Create hello-audited. Set go 1.22 and a toolchain go1.22.x directive.
  2. Build with go build -trimpath -ldflags="-s -w -X main.version=v0.1.0". Run go version -m ./hello-audited.
  3. Strip with strip and compare. Cross-compile to linux/arm64, darwin/arm64, windows/amd64 with GOOS=... GOARCH=... go build.
  4. Document the size delta from each flag in NOTES.md. - s -wtypically saves ~30%; - trimpath is a reproducibility flag (no local paths in the binary), not a size flag.
  5. Inspect the binary with go tool nm and go tool objdump. Identify the runtime symbols (runtime.main, runtime.gcStart, runtime.schedule).

1.4 Idiomatic & golangci-lint Drill

  • Install golangci-lint. Enable a strict config: errcheck, govet, staticcheck, gosimple, ineffassign, revive, gocritic, gosec, bodyclose, nilerr, prealloc, unconvert. Run on a small repo. Read each finding's URL and understand the rationale.

1.5 Production Hardening Slice

  • Add a Makefile (or Taskfile.yml) target that runs gofmt -l -d, go vet ./..., golangci-lint run, go test -race -count=1 ./..., go build -trimpath. This is the baseline CI invocation; every subsequent week's hardening slice extends it.
  • Adopt go-licenses to scan dependency licenses. Commit the report.

Comments