skip to content

Your Go CLI ships prebuilt binaries — how do you pick the GOOS/GOARCH matrix and verify each artifact?

level: seniorimportance: should knowfreq 34%

answer

  1. the matrix is a support promise
  2. demand first, then the toolchain's tiering
  3. one builder, many targets
  4. a green build is not a green run
  5. inspect the header, then smoke-run it

basics

~20 s

Ship the pairs your users actually run — usually linux, darwin and windows on amd64 and arm64 — confirm each against go tool dist list, keep the default microarchitecture baseline, and smoke-run every artifact on the real target before publishing.

solid answer

~50 s

I start from evidence: which platforms users report from, what the install docs promise, and which pairs Go treats as first-class ports and therefore tests every release. That usually lands on linux, darwin and windows across amd64 and arm64, each checked against `go tool dist list` so a typo cannot become a silently missing download. All of them build from one CI machine, and every artifact carries its pair in the filename with `.exe` added for Windows. Then I verify, because a green build only proves the package compiles and links: `file` on each artifact must report the operating system and architecture I intended, which catches a matrix entry that quietly fell back to host defaults, and one real execution per architecture, on hardware or an emulator, catches what compilation cannot see. Microarchitecture baselines stay at their defaults so the binary runs on the oldest machine a stranger might own.

code

text · 7 lines
text
$ go tool dist list | grep -E '^(linux|darwin|windows)/(amd64|arm64)$'
darwin/amd64
darwin/arm64
linux/amd64
linux/arm64
windows/amd64
windows/arm64

go deeper

for a junior

Know that one machine can build every published platform by setting GOOS and GOARCH, and that the pair belongs in the artifact's filename so users pick the right download.

for a middle

Explain how the matrix is validated against go tool dist list, why the microarchitecture baseline stays at its default for public binaries, and what .exe naming implies for Windows entries.

for a senior

Demonstrate verification discipline: assert each artifact's reported operating system and architecture in CI, smoke-run one binary per architecture, and cross-build every supported pair so platform-specific files fail fast.

for a principal

Own the matrix as a support commitment — which ports you promise, the CI and emulator cost of verifying them, how first-class versus best-effort ports shape that promise, and the process for retiring an entry.

## Choosing the matrix A release matrix is a support promise, so it should come from evidence rather than from the length of `go tool dist list`. **Start with demand.** Where do users actually run the tool — developer laptops (macOS on arm64 and amd64, Windows on amd64), CI runners and servers (linux/amd64, increasingly linux/arm64), embedded or edge devices (32-bit arm)? Download counts, issue reports and the platforms your own infrastructure runs on are all better inputs than guesswork. **Then apply the toolchain's own tiering.** The Go project designates a small set of ports as *first class*: they are tested continuously, a broken build on one blocks a Go release, and official binary downloads exist for them. Linux, macOS and Windows on the mainstream architectures are in that group. Other ports in `go tool dist list` are real and usable, but they are best-effort — if something breaks on one, you are likely to be the person who finds it and files the bug. That is a fine trade for a platform your users need; it is not a reason to publish forty artifacts nobody downloads. **Validate every pair mechanically.** Keep the matrix in one place in the build configuration and check each entry against `go tool dist list` output rather than trusting a hand-written list. An invalid pair fails the build loudly, but a *valid but unintended* one — a typo that happens to name a real port — just produces a download nobody can use. ## Building the matrix Cross-building is the reason the matrix is cheap: one machine, one checkout, `GOOS` and `GOARCH` set per invocation, no per-platform runner and no emulation required to produce the artifacts. Set the pair explicitly for every entry, including the one that matches the build host, so nothing silently inherits host defaults. Name artifacts so users and installer scripts can pick correctly — the tool name plus `GOOS` and `GOARCH`, with `.exe` appended for Windows by the go command — and publish checksums alongside them. Leave microarchitecture baselines alone. A public binary built with a raised `GOAMD64` runs fine on your build machine and on modern CI, then dies with an illegal-instruction fault on some user's older laptop, with a stack trace pointing into the standard library. Tuned baselines belong to internal artifacts on hardware you know. ## Verifying what you built This is the part teams skip, and it is where the leaf's real failure lives: a binary that runs on the build machine and faults on the target. **Check the artifact's identity.** Run `file` (or the platform equivalent) on each produced binary and assert that it reports the operating system and architecture you asked for — an ELF for Linux naming aarch64, a Mach-O for macOS, a PE for Windows. This is a two-second check that catches the whole class of "one matrix entry didn't take": a shell quoting mistake, a variable that was not exported, a job that reused a cached artifact. Automate the assertion; do not eyeball it. **Then actually run it.** A successful cross-build proves the package compiles and links for the target and nothing more. It does not prove the platform-specific file you added behaves, that a path or permission assumption holds, or that the CPU baseline is satisfied. A minimal smoke test — start the binary, print its version, exit zero — on real hardware or an emulator for each architecture is enough to catch nearly all of it. Where a real runner does not exist for an architecture, an emulated container is a reasonable substitute; note in the release process which platforms are verified only by emulation. **Test where the code branches by platform.** Files selected by `GOOS` suffix are compiled only for their platform, so an error in one is discovered by the first build that targets it. Cross-building every supported pair in CI, not just the host, turns that latent breakage into a failed build minutes after the commit. ## Retiring entries A matrix only grows unless someone prunes it. Each entry costs build time, storage, release-note surface and an implied support promise. Drop a platform when downloads and issues both say nobody is using it, announce it in the release notes, and keep the source buildable for it even after you stop publishing a binary — users who need that port can still build from source, which is exactly the escape hatch Go's easy cross-compilation gives you.

  • What is a first-class port, and why does it matter to a release matrix?
    It is a platform the Go project tests continuously and publishes official downloads for; a broken build on one blocks a Go release. Linux, macOS and Windows on the mainstream architectures are in that group. Other supported ports work but are best-effort, so shipping one means accepting that you may be the first to hit a problem on it.
  • Why is a successful cross-build not enough evidence to publish?
    It proves the package compiles and links for that target — nothing about execution. A raised CPU baseline, a platform-specific file that compiles but misbehaves, a path or permission assumption, all survive a clean build. A smoke run per architecture on real hardware or an emulator closes most of that gap for very little effort.
  • How would you catch a matrix entry that silently produced a host-native binary?
    Assert the artifact's identity rather than trusting the build log: run `file` on each output in CI and fail the job unless the reported operating system and architecture match the matrix entry. Quoting mistakes, unexported variables and reused cache entries all produce a plausible-looking file with the wrong contents, and this check catches every one of them.
  • How do you decide to drop a platform from the matrix?
    Look at download counts and issue traffic for that pair, announce the removal in release notes ahead of the release, and keep the source buildable for it. Because cross-compiling is trivial in Go, a user on a niche port can still build from source, so retiring a published binary is a much smaller promise to break than removing support.

saying these in an interview costs you the question

  • Ships only linux/amd64 and tells arm64 users to build from source
  • Treats a green cross-build as proof the binary runs
  • Publishes every pair go tool dist list prints
  • Raises the CPU baseline on a public artifact for speed
  • Builds each target on a separate VM of that operating system
  • Never inspects the produced artifacts before publishing