Introduction

Go build constraints (build tags) control which files are included in a build based on OS, architecture, and custom tags. The new //go:build syntax (Go 1.17+) replaced the old // +build syntax, and files with incorrect constraint placement, wrong tag names, or missing constraints for platform-specific code are silently excluded from the build. This causes undefined: function errors at compile time that are confusing because the file clearly exists -- it is just not being compiled for the current target.

Symptoms

bash
./main.go:15:2: undefined: platformSpecificInit
# But platform_linux.go exists with the function definition!

Or:

bash
package myapp imports myapp/internal/config from implicitly required module; to add missing requirements, run:
    go get myapp@v0.0.0
# Actually caused by build constraint excluding the config file

Common Causes

  • Old // +build syntax without //go:build: Go 1.17+ prefers new syntax
  • Constraint on wrong line: Build constraint must be before package clause with blank line
  • Wrong tag name: linux vs Linux, amd64 vs x86_64
  • Logic error in constraint: //go:build linux && amd64 excludes darwin builds
  • File name convention conflict: file_linux.go excluded when building on macOS
  • Custom tags not passed: go build -tags=mytag not used in build command

Step-by-Step Fix

Step 1: Use correct //go:build syntax

```go // CORRECT: New syntax (Go 1.17+) //go:build linux || darwin // +build linux darwin // Old syntax for backwards compatibility

package platform

func platformSpecificInit() { // Linux and macOS implementation } ```

```go // File: config_production.go //go:build production

package config

func getDatabaseURL() string { return os.Getenv("DATABASE_URL") } ```

```go // File: config_development.go //go:build !production

package config

func getDatabaseURL() string { return "postgres://localhost/dev" } ```

Step 2: Use file name conventions for OS/arch

bash
# These files are automatically selected by Go based on target:
config_linux.go      // Only included when GOOS=linux
config_darwin.go     // Only included when GOOS=darwin
config_windows.go    // Only included when GOOS=windows
config_amd64.go      // Only included when GOARCH=amd64
config_arm64.go      // Only included when GOARCH=arm64
config_linux_amd64.go // Only included when GOOS=linux AND GOARCH=amd64

Step 3: Debug which files are included in build

```bash # Show which files are compiled go build -v ./...

# Show why files are excluded go list -f '{{.GoFiles}}' ./...

# Show files excluded by build constraints go list -f '{{.IgnoredGoFiles}}' ./...

# Build with custom tags go build -tags production ./...

# Check all tags go list -tags production -f '{{.GoFiles}}' ./... ```

Prevention

  • Use //go:build syntax (Go 1.17+) and keep // +build for backwards compatibility
  • Place build constraints as the first lines of the file, before package clause
  • Use file naming conventions (_linux.go, _windows.go) when possible
  • Run go list -f '{{.IgnoredGoFiles}}' to check which files are excluded
  • Add cross-compilation tests in CI: GOOS=linux GOARCH=amd64 go build ./...
  • Document build tags in the package doc comment
  • Use go build -v to verify all expected files are compiled