Build Constraints & Platform-Specific Files
Build constraints tell the Go toolchain which source files belong in a given build.
You express them with //go:build lines, file suffixes, and custom tags passed via -tags.
The result is one module tree that compiles different implementations per OS, architecture, or feature flag.
Summary
Before Go 1.17, constraints used // +build comment lines.
Modern code should use //go:build as the first line of a file (optionally followed by blank line and package).
The toolchain evaluates constraints against GOOS, GOARCH, and custom tags.
Files without constraints are always included; platform-specific files should provide fallbacks or live behind matching tags only.
Recipe
Quick-reference recipe card - copy-paste ready.
//go:build linux && amd64
package sysfs
// linux_amd64 implementationgo build -tags=integration ./...
GOOS=windows GOARCH=arm64 go build ./...When to reach for this:
- Calling OS-specific APIs (
syscall,x/sys) without runtimeif runtime.GOOSsprawl. - Shipping arch-tuned code (SIMD, pointer size assumptions) in separate files.
- Gating integration tests, FIPS builds, or enterprise feature sets.
- Excluding cgo files on platforms where the C toolchain is unavailable.
Working Example
example.com/net/
listen.go
listen_unix.go
listen_windows.go
// listen.go - all platforms
package net
func DefaultListenConfig() ListenConfig {
return ListenConfig{}
}
type ListenConfig struct {
ReusePort bool
}// listen_unix.go
//go:build unix
package net
func (c ListenConfig) platformReuse() bool { return c.ReusePort }// listen_windows.go
//go:build windows
package net
func (c ListenConfig) platformReuse() bool { return false } // SO_REUSEPORT unlike Windows// demo/main.go
package main
import (
"fmt"
"example.com/net"
)
func main() {
cfg := net.DefaultListenConfig()
cfg.ReusePort = true
fmt.Println("reuse supported:", cfg.platformReuse())
}What this demonstrates:
- Shared API in neutral files; platform deltas in tagged files.
unixis a predefined tag covering Linux, BSD, macOS, and other Unix-likeGOOSvalues.- The same package name compiles different bodies per platform without import changes.
Deep Dive
How It Works
go buildcollects all.gofiles in the package directory.- For each file, the toolchain evaluates build constraints (explicit line or suffix-derived).
- Files with false constraints are excluded from the package for that build.
GOOS,GOARCH, and-tagsvalues form the evaluation environment.- The compiler proceeds with the remaining file set as one package.
Predefined Tags
| Term | Meaning |
|---|---|
GOOS value | linux, windows, darwin, freebsd, ... |
GOARCH value | amd64, arm64, wasm, ... |
unix | Unix-like platforms (see go tool dist list) |
gc / gccgo | Compiler toolchain |
cgo | cgo enabled for this build |
File Suffix Shorthand
| Suffix pattern | Implied constraint |
|---|---|
_linux.go | GOOS=linux |
_windows.go | GOOS=windows |
_amd64.go | GOARCH=amd64 |
_linux_amd64.go | linux AND amd64 |
Suffix rules combine with explicit //go:build lines when both are present.
Boolean Expressions
//go:build (linux || darwin) && amd64
//go:build !windows
//go:build integration && !short
Use parentheses for precedence.
Go Notes
go list -tags=integration -f '{{.GoFiles}}' ./...Inspect selected files before debugging "undefined symbol" build failures.
Gotchas
- Missing fallback implementation - tagging only
linuxleaves other GOOS without required functions. Fix: adddefault.goor!linuxfile with portable code. - Runtime GOOS checks instead of tags - compiles forbidden imports on other platforms. Fix: move imports behind build-tagged files.
- Drift between suffix and explicit tag -
_foo.goplus conflicting//go:buildconfuses readers. Fix: prefer one style per file. - Forgetting CI matrix - only building
linux/amd64hides broken Windows files. Fix: cross-compileGOOS=windowsin CI at minimum. - Custom tags undocumented - teammates run
go testwithout-tagsand skip suites silently. Fix: document tags in Makefile and README. - Legacy
// +buildonly - still works but gofmt rewrites to//go:buildin modern toolchains. Fix: migrate on touch.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
runtime.GOOS switch | Tiny behavioral differences, no special imports | File needs OS-only imports |
| Separate packages per platform | Large divergent implementations | Simple one-function deltas |
| WASM/browser build tags | JS-specific entrypoints | Server-only code |
| Build tags for features | Optional paid/enterprise modules | Simple config toggles better as runtime flags |
FAQs
What is the first line rule for //go:build?
The constraint line must appear before the package clause (only comments and blank lines may precede it).
Can I use build tags on non-Go files?
Tags apply to .go sources.
Use separate embed or asset trees with tagged Go files that reference them.
How do integration tags interact with -short?
Common pattern: //go:build integration && !short so default go test -short skips slow tests.
Does go test -tags affect main builds?
Tags apply per invocation.
go build and go test each need the same -tags when you expect identical file sets.
Can constraints reference cgo?
Yes.
//go:build cgo selects files only when cgo is enabled for the build.
What about ios and android?
Mobile targets use GOOS values like ios and android with their own tag sets.
Verify with go tool dist list for your Go version.
How do I debug excluded files?
Run go list -f '{{.GoFiles}} {{.IgnoredGoFiles}}' ./package to see ignored sources.
Are test files constrained too?
Yes.
_test.go files follow the same build constraint rules as production files.
Can one file have multiple build lines?
Only one //go:build constraint line (possibly with boolean operators).
Do not stack multiple separate constraint comments.
How do workspaces affect tags?
Tags apply per module build inside the workspace.
Each module's tagged files are evaluated independently.
Related
- Beyond the Spec: Embed, Build Modes, and Assembly - toolchain big picture
- Advanced Language Features Basics - tag and suffix examples
- Assembly in Go (*.s files) - arch-specific
.sfiles - CGO Basics - cgo gated behind tags on some teams
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).