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
./main.go:15:2: undefined: platformSpecificInit
# But platform_linux.go exists with the function definition!Or:
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 fileCommon 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:
linuxvsLinux,amd64vsx86_64 - Logic error in constraint:
//go:build linux && amd64excludes darwin builds - File name convention conflict:
file_linux.goexcluded when building on macOS - Custom tags not passed:
go build -tags=mytagnot 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
# 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=amd64Step 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:buildsyntax (Go 1.17+) and keep// +buildfor 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 -vto verify all expected files are compiled