Introduction
Go build constraints (build tags) control which source files are included in a build based on the target operating system (GOOS), architecture (GOARCH), or custom tags. When build constraints are incorrectly specified, the compiler either includes files for the wrong platform (causing compilation errors with platform-specific APIs) or excludes all implementations of a function (causing "undefined" errors). This is common in cross-platform applications that use OS-specific system calls, file paths, or signal handling.
Symptoms
Compilation error due to wrong platform code being included:
# github.com/myapp/internal/sys
sys/sys_unix.go:15:23: undefined: syscall.SIGUSR1
sys/sys_unix.go:18:12: undefined: syscall.ForkExecOr missing implementation:
# github.com/myapp/internal/sys
sys/process.go:10:9: undefined: getProcessInfoOr the old syntax warning:
sys_unix.go:1:1: +build line is no longer used in Go 1.17+, use //go:build insteadCommon Causes
- Wrong GOOS in build constraint: Using
//go:build windowswhen the file contains Linux-specific code - Missing build constraint on platform-specific files: File without constraint compiles on all platforms
- Old +build syntax not understood by newer Go: Go 1.17+ requires
//go:buildsyntax - File naming convention mismatch:
sys_linux.goautomatically getslinuxconstraint, conflicting with manual//go:build - Cross-compilation without all platform files: Building for
GOOS=windowsbut nosys_windows.goexists - Conflicting constraints: Two files both claim the same GOOS, causing duplicate symbol errors
Step-by-Step Fix
Step 1: Use correct //go:build syntax
```go // sys_linux.go - Linux-specific implementation //go:build linux
package sys
import ( "os" "syscall" )
func GetProcessInfo(pid int) (*ProcessInfo, error) { stat, err := os.ReadFile(fmt.Sprintf("/proc/%d/stat", pid)) if err != nil { return nil, err } // Parse /proc/[pid]/stat return parseProcStat(stat) } ```
```go // sys_darwin.go - macOS-specific implementation //go:build darwin
package sys
func GetProcessInfo(pid int) (*ProcessInfo, error) { // Use sysctl on macOS return getProcessInfoViaSysctl(pid) } ```
```go // sys_windows.go - Windows-specific implementation //go:build windows
package sys
func GetProcessInfo(pid int) (*ProcessInfo, error) { // Use Windows API return getProcessInfoViaWindowsAPI(pid) } ```
Step 2: Use file naming convention (preferred)
Go automatically applies build constraints based on file naming:
sys/
├── process.go # Shared code, no constraints
├── sys_linux.go # Auto-constraint: //go:build linux
├── sys_darwin.go # Auto-constraint: //go:build darwin
├── sys_windows.go # Auto-constraint: //go:build windows
└── sys_other.go # Auto-constraint: //go:build !linux && !darwin && !windowsNo explicit //go:build directive is needed when using the naming convention.
Step 3: Combine OS and architecture constraints
```go // sys_linux_amd64.go //go:build linux && amd64
package sys
func OptimizedFunction() { // Uses AVX instructions available on amd64 } ```
```go // sys_linux_arm64.go //go:build linux && arm64
package sys
func OptimizedFunction() { // Uses NEON instructions available on arm64 } ```
Step 4: Cross-compile with all platforms
```bash # Build for all platforms GOOS=linux GOARCH=amd64 go build -o myapp-linux-amd64 . GOOS=linux GOARCH=arm64 go build -o myapp-linux-arm64 . GOOS=darwin GOARCH=amd64 go build -o myapp-darwin-amd64 . GOOS=darwin GOARCH=arm64 go build -o myapp-darwin-arm64 . GOOS=windows GOARCH=amd64 go build -o myapp-windows-amd64.exe .
# Verify each build GOOS=linux GOARCH=amd64 go build -v . 2>&1 | grep "sys_linux" ```
Prevention
- Prefer file naming convention (
name_linux.go) over explicit//go:builddirectives - Always provide a fallback implementation in
name_other.gowith//go:build !linux && !darwin && !windows - Cross-compile for all target platforms in CI to catch build constraint errors early
- Use
go tool dist listto see all supported GOOS/GOARCH combinations - Add a CI step that builds for every supported platform on every PR
- Never mix old
+buildand new//go:buildsyntax in the same file