Day 052 — Build Tags & Cross-Compilation¶
Month 2 · Week 4 · ⬅ Day 051 · Day 053 ➡ · Journal index
🎯 Learning Objective¶
Compile platform- and feature-specific variants of the same package using //go:build constraints and filename suffixes, and cross-compile a binary for another OS/arch with GOOS/GOARCH.
📚 Topics¶
//go:buildconstraint expressions (&&,||,!, parens)- Implicit filename-suffix constraints (
_windows.go,_amd64.go,_test.go) - Custom tags via
-tags; cross-compilation withGOOS/GOARCH
📖 Reading / Sources¶
-
go help buildconstraint -
go/build— constraint syntax - Go 1.17 release notes —
//go:buildlines -
go tool dist list— valid GOOS/GOARCH pairs
📝 Notes¶
- A build constraint decides whether a file is compiled at all. Modern form is a
//go:buildline; it must sit at the top of the file, followed by a blank line before thepackageclause. The old// +buildform is deprecated (kept only for pre-1.17 toolchains). → links to [[tooling]]. - The constraint is a boolean expression over tags:
//go:build (linux || darwin) && !cgo. Tags include everyGOOS, everyGOARCH,cgo, the Go version (go1.22), and any custom identifier you pass with-tags foo. - Filename suffixes are implicit constraints: a file ending
_windows.gocompiles only forGOOS=windows;_amd64.goonly forGOARCH=amd64;_windows_amd64.gofor both._test.gois the suffix that makes a file test-only. You don't need a//go:buildline for the suffix to apply, but adding one lets you combine the OS with extra tags. - Per-platform files providing the same symbol must be mutually exclusive and exhaustive, or you get "X redeclared in this block" (overlap) or "undefined: X" (a gap). Include a catch-all like
//go:build !linux && !darwin && !windows. - Build tags resolve the variant at compile time — there is no
if runtime.GOOS == ...branch and no dead code for other platforms in the binary. Contrast withruntime.GOOS, which is a runtime value. - Cross-compilation is built in: set
GOOS/GOARCHandgo buildemits a binary for that target without a C toolchain as long asCGO_ENABLED=0(the default when cross-compiling).GOOS=windows GOARCH=amd64 go buildproduces a.exefrom a Linux box. runtime.GOOS/runtime.GOARCHreport the target baked in at build time, so a cross-built binary prints its target, not the build host. List all targets withgo tool dist list.
💻 Code Examples¶
//go:build linux || darwin
// greeting_unix.go — compiled ONLY for the unix-like targets. A sibling
// greeting_windows.go and a catch-all greeting_other.go define the same
// platformGreeting() for the other targets; exactly one is linked in.
package main
func platformGreeting() string { return "Hello from a Unix-like build" }
Full example:
examples/month-02/build-tags/· Run host build:go run ./examples/month-02/build-tags· Feature tag:go run -tags debug ./examples/month-02/build-tags· Cross-compile:GOOS=windows GOARCH=amd64 go build -o /tmp/bt.exe ./examples/month-02/build-tags
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Add a debug_on.go/debug_off.go pair gated on -tags debug |
✅ | examples/month-02/build-tags |
| Cross-compile the URL shortener for windows/amd64 and darwin/arm64 | ✅ | examples/month-02/urlshortener |
🐛 Mistakes Made¶
- Forgot the blank line between
//go:build linuxandpackage main→ the line was parsed as an ordinary comment and the constraint silently did nothing. - Made the per-OS constraints overlap (
linuxin two files) →platformGreeting redeclared. Fixed by making them mutually exclusive.
❓ Open Questions¶
- When do I actually need
cgo(so cross-compilation needs a cross C toolchain) vs pure-Go? (Pure Go cross-compiles freely; cgo pulls in a C compiler per target — avoid unless a dependency forces it.)
🧠 Active Recall (answer without looking)¶
- Q: What two formatting rules make a
//go:buildline actually take effect?A
It must be near the top of the file before the package clause, and it must be followed by a blank line separating it from package. Otherwise it's just a comment.
2. Q: Does runtime.GOOS tell you the build host or the build target? A
The target — it's the GOOS the binary was compiled for, fixed at build time. A binary cross-built with GOOS=windows reports windows even if built on Linux.
🪶 Feynman Reflection¶
Build tags are an on/off switch on each source file, evaluated before compilation. The switch reads the target OS/arch and any -tags you pass, so you can keep windows-only and linux-only code in separate files and let the toolchain pick the right one. Because the choice happens at compile time, cross-compiling is just changing two environment variables — no other-platform code rides along in the binary.
🕳️ Knowledge Gaps¶
embed+ build tags for asset variants; andpuregopatterns to dodge cgo — note for later.
✅ Summary¶
I can gate files with //go:build expressions and filename suffixes, keep per-platform symbols mutually exclusive, pass custom -tags, and cross-compile with GOOS/GOARCH.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 053: profile a running program with
runtime/pprofandnet/http/pprof, then read the profile withgo tool pprof.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: docs(journal): build tags and cross-compilation (day 052)