Why Build Tags Matter
When building a universal remote control that works with different TV brands—Sony, Samsung, LG—instead of buying a separate remote for each brand, you get one smart remote that automatically knows which buttons to show for each TV. Build tags are like that smart remote for your Go code—they automatically select the right implementation based on the target platform.
Real-World Impact:
Docker - 500K+ lines, runs on Windows/Linux/macOS:
file_linux.go,file_windows.go,file_darwin.go- Platform-specific syscalls- Zero runtime platform detection—compiler chooses correct files
- Result: Single binary, optimal performance per platform
Kubernetes - Compiles for ARM, x86, PowerPC:
- CPU-specific optimizations selected at compile time
//go:build arm64for ARM-specific crypto acceleration- 40% performance gain on ARM vs generic code
Discord - Cross-platform voice communication:
- Build tags handle platform-specific audio APIs
- Windows: DirectSound, WASAPI
- macOS: CoreAudio, AVFoundation
- Linux: ALSA, PulseAudio
- Result: Native performance on each platform
Build tags enable Go's "write once, compile anywhere" philosophy—the same codebase compiles to 20+ platforms without #ifdef hell. Understanding build tags separates simple projects from production-grade cross-platform systems.
Learning Objectives
By the end of this article, you will master:
- Build Tag Syntax: Write effective build constraints using modern
//go:buildsyntax - Platform Detection: Target specific operating systems, architectures, and compiler features
- Conditional Compilation: Create different implementations for different environments
- Feature Flags: Implement compile-time feature toggles and A/B testing
- Production Patterns: Apply build tags in real-world scenarios like CI/CD and multi-environment deployments
Core Concepts - Understanding Build Tags
What are Build Tags?
Build tags are special comments at the top of Go files that tell the compiler when to include or exclude that file during compilation. They act as conditional compilation directives, allowing your single codebase to adapt to different target environments.
Real-world Analogy: Think of build tags like a smart construction crew that automatically uses different building techniques based on the location. If they're building in earthquake-prone California, they use reinforced foundations. If they're building in snowy Minnesota, they use insulated walls and heated floors. The building plan adapts to the environment—build tags do the same for your code.
The Modern Build Tag Revolution
Go 1.17 introduced the modern //go:build syntax, replacing the legacy // +build format. The new syntax provides:
- Better readability with standard boolean operators
- Parentheses for complex expressions
- Clearer logical flow with AND, OR, and NOT operations
Types of Build Constraints
Platform Tags - Target specific operating systems and architectures:
1//go:build linux && amd64 // 64-bit Linux
2//go:build windows // Windows
3//go:build darwin && arm64 // Apple Silicon Macs
Feature Tags - Enable or disable code features:
1//go:build production // Production-only optimizations
2//go:build debug // Debug logging and checks
3//go:build experimental // Experimental features
Compiler Tags - Target specific Go compilers:
1//go:build gc // Standard Go compiler
2//go:build gccgo // GCC Go compiler
3//go:build !cgo // Disable CGo
Why Build Tags Over Runtime Checks?
The Problem with Runtime Checks:
1// ❌ WRONG: Runtime platform detection
2func GetHomePath() string {
3 if runtime.GOOS == "windows" {
4 return os.Getenv("USERPROFILE") + "\\AppData"
5 }
6 return os.Getenv("HOME")
7}
The Build Tag Solution:
1// ✅ BETTER: Compile-time selection
2// file_windows.go
3//go:build windows
4func GetHomePath() string {
5 return os.Getenv("USERPROFILE") + "\\AppData"
6}
7
8// file_unix.go
9//go:build !windows
10func GetHomePath() string {
11 return os.Getenv("HOME")
12}
Benefits:
- Zero runtime overhead - Platform check happens at compile time
- Dead code elimination - Unused platform code never reaches binary
- Type safety - Compiler only sees code that actually exists
- Smaller binaries - Only relevant code included
Build Tag Syntax Rules
Modern Syntax (//go:build):
1//go:build linux && amd64 // AND operation
2//go:build linux || darwin // OR operation
3//go:build !windows // NOT operation
4//go:build (linux || darwin) && amd64 // Complex expression
5
6package main
Critical Rules:
- Build tags must appear before the package declaration
- A blank line must separate build tags from package declaration
- Use standard boolean operators:
&&(AND),||(OR),!(NOT) - Parentheses group complex expressions
- Multiple build tag lines are combined with AND logic
File Naming Conventions
Go recognizes special file naming patterns without explicit build tags:
1# Operating system suffixes
2file_linux.go # Only Linux
3file_windows.go # Only Windows
4file_darwin.go # Only macOS
5file_freebsd.go # Only FreeBSD
6
7# Architecture suffixes
8file_amd64.go # Only AMD64/x86-64
9file_arm64.go # Only ARM64
10file_386.go # Only 32-bit x86
11file_arm.go # Only 32-bit ARM
12
13# Combined suffixes
14file_linux_amd64.go # 64-bit Linux only
15file_windows_arm64.go # ARM64 Windows only
16file_darwin_amd64.go # Intel Macs only
Example:
1// network_linux_amd64.go - No explicit build tag needed!
2package network
3
4func OptimizedRead(fd int, buf []byte) (int, error) {
5 // Linux AMD64-specific optimizations using syscalls
6 return syscall.Read(fd, buf)
7}
Practical Examples - Hands-On Build Tags
Example 1: Platform-Specific File Operations
Let's build a cross-platform file operations package that handles path differences between Windows and Unix-like systems:
1// fileops/file.go - Common interface
2package fileops
3
4// FileSystem provides platform-agnostic file operations
5type FileSystem interface {
6 HomePath() string
7 TempPath() string
8 Separator() string
9 Join(parts ...string) string
10}
11
12// GetFileSystem returns platform-specific implementation
13func GetFileSystem() FileSystem {
14 return newFileSystem()
15}
1// fileops/file_windows.go - Windows implementation
2//go:build windows
3
4package fileops
5
6import (
7 "os"
8 "path/filepath"
9)
10
11type windowsFS struct{}
12
13func newFileSystem() FileSystem {
14 return &windowsFS{}
15}
16
17func (w *windowsFS) HomePath() string {
18 // Windows: C:\Users\Username
19 return os.Getenv("USERPROFILE")
20}
21
22func (w *windowsFS) TempPath() string {
23 // Windows: C:\Users\Username\AppData\Local\Temp
24 return os.Getenv("TEMP")
25}
26
27func (w *windowsFS) Separator() string {
28 return "\\"
29}
30
31func (w *windowsFS) Join(parts ...string) string {
32 return filepath.Join(parts...)
33}
1// fileops/file_unix.go - Unix-like implementation
2//go:build !windows
3
4package fileops
5
6import (
7 "os"
8 "path/filepath"
9)
10
11type unixFS struct{}
12
13func newFileSystem() FileSystem {
14 return &unixFS{}
15}
16
17func (u *unixFS) HomePath() string {
18 // Unix/Linux/macOS: /home/username
19 return os.Getenv("HOME")
20}
21
22func (u *unixFS) TempPath() string {
23 // Unix/Linux/macOS: /tmp
24 return "/tmp"
25}
26
27func (u *unixFS) Separator() string {
28 return "/"
29}
30
31func (u *unixFS) Join(parts ...string) string {
32 return filepath.Join(parts...)
33}
Usage:
1package main
2
3import (
4 "fmt"
5 "yourproject/fileops"
6)
7
8func main() {
9 fs := fileops.GetFileSystem()
10
11 fmt.Printf("Home: %s\n", fs.HomePath())
12 fmt.Printf("Temp: %s\n", fs.TempPath())
13 fmt.Printf("Separator: %s\n", fs.Separator())
14
15 // Cross-platform path joining
16 fullPath := fs.Join(fs.HomePath(), "Documents", "myfile.txt")
17 fmt.Printf("Full path: %s\n", fullPath)
18}
19
20// run
Example 2: Architecture-Specific Optimizations
Let's implement CPU-specific optimizations for cryptographic operations:
1// crypto/hasher.go - Common interface
2package crypto
3
4// Hasher provides fast hashing with platform optimizations
5type Hasher interface {
6 Hash(data []byte) []byte
7 HashString(s string) []byte
8}
9
10// NewHasher returns optimized hasher for current platform
11func NewHasher() Hasher {
12 return newHasher()
13}
1// crypto/hasher_amd64.go - x86-64 optimizations
2//go:build amd64
3
4package crypto
5
6type amd64Hasher struct{}
7
8func newHasher() Hasher {
9 return &amd64Hasher{}
10}
11
12func (h *amd64Hasher) Hash(data []byte) []byte {
13 // Use SIMD instructions available on AMD64
14 return hashOptimized(data)
15}
16
17func (h *amd64Hasher) HashString(s string) []byte {
18 return h.Hash([]byte(s))
19}
20
21// hashOptimized uses AVX2 instructions for speed
22func hashOptimized(data []byte) []byte {
23 var hash uint64 = 14695981039346656037 // FNV-1a offset basis
24
25 // Process in larger chunks on 64-bit platforms
26 for i := 0; i < len(data); i++ {
27 hash ^= uint64(data[i])
28 hash *= 1099511628211 // FNV-1a prime
29 }
30
31 result := make([]byte, 8)
32 for i := 0; i < 8; i++ {
33 result[i] = byte(hash >> (i * 8))
34 }
35 return result
36}
1// crypto/hasher_generic.go - Generic implementation
2//go:build !amd64
3
4package crypto
5
6type genericHasher struct{}
7
8func newHasher() Hasher {
9 return &genericHasher{}
10}
11
12func (h *genericHasher) Hash(data []byte) []byte {
13 return hashGeneric(data)
14}
15
16func (h *genericHasher) HashString(s string) []byte {
17 return h.Hash([]byte(s))
18}
19
20// hashGeneric provides a platform-independent hash implementation
21func hashGeneric(data []byte) []byte {
22 var hash uint32 = 2166136261 // FNV-1a 32-bit offset basis
23
24 for _, b := range data {
25 hash ^= uint32(b)
26 hash *= 16777619 // FNV-1a 32-bit prime
27 }
28
29 result := make([]byte, 4)
30 for i := 0; i < 4; i++ {
31 result[i] = byte(hash >> (i * 8))
32 }
33 return result
34}
Example 3: Feature Flags for A/B Testing
Build tags enable powerful A/B testing and gradual feature rollouts:
1// auth/auth.go - Main authentication interface
2package auth
3
4// Authenticator provides user authentication
5type Authenticator interface {
6 Authenticate(username, password string) (bool, error)
7 CreateUser(username, password string) error
8 ValidateToken(token string) (bool, string, error)
9}
10
11// NewAuthenticator returns configured authenticator
12func NewAuthenticator() Authenticator {
13 return newAuthenticator()
14}
1// auth/auth_v1.go - Version 1 implementation
2//go:build !auth_v2
3
4package auth
5
6import (
7 "errors"
8 "fmt"
9)
10
11type v1Auth struct {
12 users map[string]string // username -> password hash
13}
14
15func newAuthenticator() Authenticator {
16 return &v1Auth{
17 users: make(map[string]string),
18 }
19}
20
21func (a *v1Auth) Authenticate(username, password string) (bool, error) {
22 storedHash, exists := a.users[username]
23 if !exists {
24 return false, errors.New("user not found")
25 }
26
27 // Simple hash comparison
28 inputHash := fmt.Sprintf("%x", password) // Simplified for example
29 if inputHash == storedHash {
30 return true, nil
31 }
32
33 return false, errors.New("invalid password")
34}
35
36func (a *v1Auth) CreateUser(username, password string) error {
37 if _, exists := a.users[username]; exists {
38 return errors.New("user already exists")
39 }
40
41 a.users[username] = fmt.Sprintf("%x", password) // Simplified for example
42 return nil
43}
44
45func (a *v1Auth) ValidateToken(token string) (bool, string, error) {
46 // V1 doesn't support tokens
47 return false, "", errors.New("tokens not supported in v1")
48}
1// auth/auth_v2.go - Version 2 implementation with tokens
2//go:build auth_v2
3
4package auth
5
6import (
7 "crypto/rand"
8 "encoding/base64"
9 "errors"
10 "fmt"
11 "time"
12)
13
14type v2Auth struct {
15 users map[string]string // username -> password hash
16 tokens map[string]tokenInfo
17}
18
19type tokenInfo struct {
20 username string
21 expires time.Time
22}
23
24func newAuthenticator() Authenticator {
25 return &v2Auth{
26 users: make(map[string]string),
27 tokens: make(map[string]tokenInfo),
28 }
29}
30
31func (a *v2Auth) Authenticate(username, password string) (bool, error) {
32 storedHash, exists := a.users[username]
33 if !exists {
34 return false, errors.New("user not found")
35 }
36
37 inputHash := fmt.Sprintf("%x", password) // Simplified for example
38 if inputHash == storedHash {
39 return true, nil
40 }
41
42 return false, errors.New("invalid password")
43}
44
45func (a *v2Auth) CreateUser(username, password string) error {
46 if _, exists := a.users[username]; exists {
47 return errors.New("user already exists")
48 }
49
50 a.users[username] = fmt.Sprintf("%x", password) // Simplified for example
51 return nil
52}
53
54func (a *v2Auth) ValidateToken(token string) (bool, string, error) {
55 info, exists := a.tokens[token]
56 if !exists {
57 return false, "", errors.New("invalid token")
58 }
59
60 if time.Now().After(info.expires) {
61 delete(a.tokens, token)
62 return false, "", errors.New("token expired")
63 }
64
65 return true, info.username, nil
66}
67
68// GenerateToken creates a JWT-like token
69func (a *v2Auth) GenerateToken(username string) (string, error) {
70 // In production, use proper JWT library
71 tokenBytes := make([]byte, 32)
72 if _, err := rand.Read(tokenBytes); err != nil {
73 return "", err
74 }
75
76 token := base64.StdEncoding.EncodeToString(tokenBytes)
77 a.tokens[token] = tokenInfo{
78 username: username,
79 expires: time.Now().Add(24 * time.Hour), // 24 hour expiry
80 }
81
82 return token, nil
83}
Build commands for different versions:
1# Build with V1 authentication
2go build -tags "!auth_v2" -o app-v1
3
4# Build with V2 authentication
5go build -tags "auth_v2" -o app-v2
6
7# Build experimental version
8go build -tags "auth_v2,experimental" -o app-v2-exp
Common Patterns and Best Practices
Pattern 1: Environment-Specific Configuration
Build tags excel at creating different configurations for different environments:
1// config/config.go - Common configuration types
2package config
3
4import (
5 "time"
6)
7
8// DatabaseConfig holds database connection settings
9type DatabaseConfig struct {
10 Host string
11 Port int
12 Name string
13 Username string
14 Password string
15 MaxOpenConns int
16 Timeout time.Duration
17 SSLMode bool
18}
19
20// APIConfig holds API server settings
21type APIConfig struct {
22 Port int
23 Timeout time.Duration
24 RateLimit int
25 EnableCORS bool
26 TrustedOrigins []string
27}
28
29// Config holds all application configuration
30type Config struct {
31 Environment string
32 Database DatabaseConfig
33 API APIConfig
34 LogLevel string
35 Debug bool
36 Features map[string]bool
37}
38
39// GetConfig returns environment-specific configuration
40func GetConfig() *Config {
41 return getConfig()
42}
1// config/config_development.go - Development configuration
2//go:build development
3
4package config
5
6import "time"
7
8func getConfig() *Config {
9 return &Config{
10 Environment: "development",
11 Database: DatabaseConfig{
12 Host: "localhost",
13 Port: 5432,
14 Name: "myapp_dev",
15 Username: "dev_user",
16 Password: "dev_password",
17 MaxOpenConns: 5,
18 Timeout: 30 * time.Second,
19 SSLMode: false,
20 },
21 API: APIConfig{
22 Port: 8080,
23 Timeout: 5 * time.Second,
24 RateLimit: 1000,
25 EnableCORS: true,
26 TrustedOrigins: []string{"http://localhost:3000"},
27 },
28 LogLevel: "debug",
29 Debug: true,
30 Features: map[string]bool{
31 "new_ui": true,
32 "analytics": false,
33 "beta_features": true,
34 "verbose_logging": true,
35 },
36 }
37}
1// config/config_production.go - Production configuration
2//go:build !development && !staging
3
4package config
5
6import (
7 "os"
8 "strconv"
9 "time"
10)
11
12func getConfig() *Config {
13 return &Config{
14 Environment: "production",
15 Database: DatabaseConfig{
16 Host: getEnv("DB_HOST", "prod-db.example.com"),
17 Port: getEnvInt("DB_PORT", 5432),
18 Name: getEnv("DB_NAME", "myapp_prod"),
19 Username: getEnv("DB_USER", "app_user"),
20 Password: getEnv("DB_PASSWORD", ""),
21 MaxOpenConns: getEnvInt("DB_MAX_CONNS", 100),
22 Timeout: 10 * time.Second,
23 SSLMode: true,
24 },
25 API: APIConfig{
26 Port: getEnvInt("API_PORT", 80),
27 Timeout: time.Duration(getEnvInt("API_TIMEOUT", 10)) * time.Second,
28 RateLimit: getEnvInt("API_RATELIMIT", 10000),
29 EnableCORS: false,
30 TrustedOrigins: []string{
31 "https://api.example.com",
32 "https://admin.example.com",
33 },
34 },
35 LogLevel: "info",
36 Debug: false,
37 Features: map[string]bool{
38 "new_ui": false,
39 "analytics": true,
40 "beta_features": false,
41 "verbose_logging": false,
42 },
43 }
44}
45
46func getEnv(key, defaultValue string) string {
47 if value := os.Getenv(key); value != "" {
48 return value
49 }
50 return defaultValue
51}
52
53func getEnvInt(key string, defaultValue int) int {
54 if value := os.Getenv(key); value != "" {
55 if intValue, err := strconv.Atoi(value); err == nil {
56 return intValue
57 }
58 }
59 return defaultValue
60}
Build commands:
1# Development build
2go build -tags "development" -o app-dev
3
4# Production build
5go build -o app-prod
6
7# Staging build
8go build -tags "staging" -o app-staging
Pattern 2: Testing with Build Tags
Separate unit tests from integration tests using build tags:
1// database/user_test.go - Unit tests
2package database
3
4import (
5 "testing"
6)
7
8func TestUserCreation(t *testing.T) {
9 db := NewMemoryDB()
10 defer db.Close()
11
12 user := &User{
13 ID: 1,
14 Username: "testuser",
15 Email: "test@example.com",
16 }
17
18 err := db.CreateUser(user)
19 if err != nil {
20 t.Fatalf("Failed to create user: %v", err)
21 }
22
23 retrieved, err := db.GetUser(1)
24 if err != nil {
25 t.Fatalf("Failed to get user: %v", err)
26 }
27
28 if retrieved.Username != "testuser" {
29 t.Errorf("Expected username 'testuser', got '%s'", retrieved.Username)
30 }
31}
32
33func BenchmarkUserOperations(b *testing.B) {
34 db := NewMemoryDB()
35 defer db.Close()
36
37 user := &User{
38 ID: 1,
39 Username: "benchmark_user",
40 Email: "bench@example.com",
41 }
42
43 b.ResetTimer()
44 for i := 0; i < b.N; i++ {
45 db.CreateUser(user)
46 db.GetUser(1)
47 }
48}
1// database/user_integration_test.go - Integration tests
2//go:build integration
3
4package database
5
6import (
7 "testing"
8)
9
10func TestUserIntegration(t *testing.T) {
11 // This test requires a real database
12 db, err := NewPostgresDB("postgres://localhost:5432/testdb?sslmode=disable")
13 if err != nil {
14 t.Skipf("Skipping integration test: %v", err)
15 }
16 defer db.Close()
17
18 user := &User{
19 ID: 1,
20 Username: "integration_user",
21 Email: "integration@example.com",
22 }
23
24 err = db.CreateUser(user)
25 if err != nil {
26 t.Fatalf("Failed to create user: %v", err)
27 }
28
29 // Test that user persists across connections
30 db2, err := NewPostgresDB("postgres://localhost:5432/testdb?sslmode=disable")
31 if err != nil {
32 t.Fatalf("Failed to connect second database: %v", err)
33 }
34 defer db2.Close()
35
36 retrieved, err := db2.GetUser(1)
37 if err != nil {
38 t.Fatalf("Failed to get user from second connection: %v", err)
39 }
40
41 if retrieved.Username != "integration_user" {
42 t.Errorf("Expected username 'integration_user', got '%s'", retrieved.Username)
43 }
44}
Test commands:
1# Run unit tests only
2go test ./database/...
3
4# Run all tests including integration
5go test -tags "integration" ./database/...
6
7# Run specific test
8go test -tags "integration" -run TestUserIntegration ./database/
Pattern 3: Fallback Implementations
Always provide fallbacks for unsupported platforms:
1// notifications/notifier.go - Main notification interface
2package notifications
3
4type Notifier interface {
5 Send(title, message string) error
6 IsSupported() bool
7}
8
9// NewNotifier returns platform-appropriate notifier
10func NewNotifier() Notifier {
11 return newNotifier()
12}
13
14func IsDesktopNotificationSupported() bool {
15 return isDesktopSupported()
16}
1// notifications/notifier_desktop.go - Desktop notifications
2//go:build (linux || darwin || windows) && !mobile
3
4package notifications
5
6import (
7 "errors"
8 "runtime"
9)
10
11type desktopNotifier struct{}
12
13func newNotifier() Notifier {
14 return &desktopNotifier{}
15}
16
17func (d *desktopNotifier) Send(title, message string) error {
18 // Platform-specific implementation would go here
19 // This is a simplified example
20 switch runtime.GOOS {
21 case "darwin":
22 return sendMacOSNotification(title, message)
23 case "linux":
24 return sendLinuxNotification(title, message)
25 case "windows":
26 return sendWindowsNotification(title, message)
27 default:
28 return errors.New("unsupported platform")
29 }
30}
31
32func (d *desktopNotifier) IsSupported() bool {
33 return true
34}
35
36func isDesktopSupported() bool {
37 return runtime.GOOS == "darwin" || runtime.GOOS == "linux" || runtime.GOOS == "windows"
38}
39
40// Placeholder implementations
41func sendMacOSNotification(title, message string) error {
42 // Use osascript or NotificationCenter
43 return nil
44}
45
46func sendLinuxNotification(title, message string) error {
47 // Use libnotify or D-Bus
48 return nil
49}
50
51func sendWindowsNotification(title, message string) error {
52 // Use Windows Toast API
53 return nil
54}
1// notifications/notifier_fallback.go - Fallback for unsupported platforms
2//go:build !(linux || darwin || windows) || mobile
3
4package notifications
5
6import (
7 "fmt"
8 "log"
9)
10
11type fallbackNotifier struct{}
12
13func newNotifier() Notifier {
14 return &fallbackNotifier{}
15}
16
17func (f *fallbackNotifier) Send(title, message string) error {
18 // Log the notification instead
19 logMsg := fmt.Sprintf("[NOTIFICATION] %s: %s", title, message)
20 log.Println(logMsg)
21 return nil
22}
23
24func (f *fallbackNotifier) IsSupported() bool {
25 return false
26}
27
28func isDesktopSupported() bool {
29 return false
30}
Integration and Mastery - Advanced Techniques
Technique 1: Complex Build Tag Expressions
Master complex boolean expressions for fine-grained control:
Common Patterns:
1// Platform AND architecture
2//go:build linux && amd64
3
4// Platform OR platform
5//go:build linux || darwin
6
7// NOT platform AND feature
8//go:build !windows && production
9
10// Complex nested expression
11//go:build (linux && amd64) || (darwin && arm64) && !debug
12
13// Multiple conditions
14//go:build (production || staging) && !test && (amd64 || arm64)
Technique 2: Build Tag Validation System
Create a system to validate build tag combinations:
1// build/validator.go - Build tag validation
2package build
3
4import (
5 "fmt"
6 "os"
7 "runtime"
8)
9
10type ValidationResult struct {
11 Valid bool
12 Errors []string
13 Warnings []string
14}
15
16type BuildContext struct {
17 OS string
18 Arch string
19 Compiler string
20 Tags []string
21}
22
23func ValidateTags(tags []string) ValidationResult {
24 result := ValidationResult{Valid: true}
25
26 ctx := BuildContext{
27 OS: runtime.GOOS,
28 Arch: runtime.GOARCH,
29 Compiler: getCompiler(),
30 Tags: tags,
31 }
32
33 // Check for conflicting tags
34 if contains(tags, "dev") && contains(tags, "prod") {
35 result.Valid = false
36 result.Errors = append(result.Errors, "cannot use both 'dev' and 'prod' tags")
37 }
38
39 // Check for incompatible platform tags
40 if contains(tags, "windows-only") && ctx.OS != "windows" {
41 result.Valid = false
42 result.Errors = append(result.Errors, "windows-only tag requires Windows OS")
43 }
44
45 // Check for missing required tags
46 if contains(tags, "postgres") && !contains(tags, "database") {
47 result.Warnings = append(result.Warnings, "postgres tag typically requires database tag")
48 }
49
50 // Validate tag syntax
51 for _, tag := range tags {
52 if !isValidTag(tag) {
53 result.Errors = append(result.Errors, fmt.Sprintf("invalid tag format: %s", tag))
54 }
55 }
56
57 return result
58}
59
60func contains(slice []string, item string) bool {
61 for _, s := range slice {
62 if s == item {
63 return true
64 }
65 }
66 return false
67}
68
69func isValidTag(tag string) bool {
70 // Basic validation - tags should be alphanumeric and underscores
71 for _, r := range tag {
72 if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') {
73 return false
74 }
75 }
76 return len(tag) > 0
77}
78
79func getCompiler() string {
80 if os.Getenv("GCCGO") == "1" {
81 return "gccgo"
82 }
83 return "gc"
84}
Technique 3: Build Tag Generation System
Automate build tag generation for CI/CD:
1// build/generator.go - Build tag generator for CI/CD
2package build
3
4import (
5 "fmt"
6 "os"
7 "strings"
8)
9
10type BuildConfig struct {
11 Environment string // dev, staging, prod
12 Features []string
13 Platform string // auto, linux, windows, darwin
14 Arch string // auto, amd64, arm64
15 Compiler string // auto, gc, gccgo
16}
17
18func GenerateTags(config BuildConfig) (string, error) {
19 var tags []string
20
21 // Environment tags
22 if config.Environment != "" && config.Environment != "auto" {
23 tags = append(tags, config.Environment)
24 }
25
26 // Platform tags
27 if config.Platform != "" && config.Platform != "auto" {
28 tags = append(tags, config.Platform)
29 }
30
31 // Architecture tags
32 if config.Arch != "" && config.Arch != "auto" {
33 tags = append(tags, config.Arch)
34 }
35
36 // Compiler tags
37 if config.Compiler != "" && config.Compiler != "auto" {
38 tags = append(tags, config.Compiler)
39 }
40
41 // Feature tags
42 for _, feature := range config.Features {
43 if feature != "" {
44 tags = append(tags, feature)
45 }
46 }
47
48 if len(tags) == 0 {
49 return "", fmt.Errorf("no build tags specified")
50 }
51
52 return strings.Join(tags, ","), nil
53}
54
55// GetDefaultConfig returns build config based on environment
56func GetDefaultConfig() BuildConfig {
57 config := BuildConfig{
58 Environment: os.Getenv("BUILD_ENV"),
59 Platform: os.Getenv("BUILD_PLATFORM"),
60 Arch: os.Getenv("BUILD_ARCH"),
61 Compiler: os.Getenv("BUILD_COMPILER"),
62 }
63
64 // Parse feature flags from comma-separated list
65 if features := os.Getenv("BUILD_FEATURES"); features != "" {
66 config.Features = strings.Split(features, ",")
67 for i, feature := range config.Features {
68 config.Features[i] = strings.TrimSpace(feature)
69 }
70 }
71
72 return config
73}
Technique 4: Version Constraints
Use build tags to support different Go versions:
1// features_new.go - Features for Go 1.20+
2//go:build go1.20
3
4package features
5
6import "fmt"
7
8func UseNewFeature() string {
9 // Use features available in Go 1.20+
10 return fmt.Sprintf("Using Go 1.20+ features")
11}
1// features_old.go - Fallback for older Go versions
2//go:build !go1.20
3
4package features
5
6import "fmt"
7
8func UseNewFeature() string {
9 // Fallback implementation
10 return fmt.Sprintf("Using fallback for older Go versions")
11}
Technique 5: CGo Control
Control CGo usage with build tags:
1// database_cgo.go - CGo-enabled SQLite driver
2//go:build cgo
3
4package database
5
6/*
7#cgo LDFLAGS: -lsqlite3
8#include <sqlite3.h>
9*/
10import "C"
11
12func OpenDatabase(path string) (*DB, error) {
13 // Use CGo-based SQLite driver for best performance
14 return openNativeDB(path)
15}
1// database_nocgo.go - Pure Go SQLite driver
2//go:build !cgo
3
4package database
5
6import (
7 "github.com/mattn/go-sqlite3"
8)
9
10func OpenDatabase(path string) (*DB, error) {
11 // Use pure Go implementation
12 return openPureGoDB(path)
13}
Best Practices and Common Pitfalls
Best Practices
- Use modern syntax - Always prefer
//go:buildover// +build - Keep expressions simple - Complex tags are hard to maintain and debug
- Document tag purpose - Add comments explaining why each tag is needed
- Provide fallbacks - Always have a default implementation for unsupported platforms
- Test all combinations - Ensure your CI tests all build tag variations
- Use naming conventions - Prefer
*_GOOS.goover explicit build tags when possible - Version control compatibility - Use both syntaxes for backward compatibility if needed
Common Pitfalls and Solutions
Pitfall 1: Missing Blank Line
Problem:
1// ❌ WRONG - Missing blank line after build tag
2//go:build linux
3package main
Solution:
1// ✅ CORRECT - Blank line required before package
2//go:build linux
3
4package main
Pitfall 2: Conflicting Modern and Legacy Tags
Problem:
1//go:build linux && amd64
2// +build linux,amd64 // These don't match!
3package main
Solution:
1//go:build linux && amd64
2// +build linux,amd64 // Must be equivalent
3package main
Pitfall 3: No Fallback Implementation
Problem:
1//go:build windows
2func GetPlatformInfo() string {
3 return "Windows specific info"
4}
5// What happens on Linux? Compilation error!
Solution:
1//go:build windows
2func GetPlatformInfo() string {
3 return "Windows specific info"
4}
5
6//go:build !windows
7func GetPlatformInfo() string {
8 return "Generic platform info"
9}
Pitfall 4: Overly Complex Expressions
Problem:
1//go:build (windows && amd64) || (linux && (amd64 || arm64)) && !development && (production || staging)
Solution:
Split into multiple files with simpler tags:
1// platform_windows_amd64.go
2//go:build windows && amd64
3
4// platform_linux_amd64.go
5//go:build linux && amd64
6
7// platform_generic.go
8//go:build !windows && !linux
Pitfall 5: Using Build Tags for Runtime Behavior
Wrong:
1//go:build feature_x
2func ProcessData(data []byte) []byte {
3 if hasFeatureX() { // Runtime check defeats purpose!
4 return processWithFeatureX(data)
5 }
6 return processDataNormal(data)
7}
Right:
1//go:build feature_x
2func ProcessData(data []byte) []byte {
3 return processWithFeatureX(data)
4}
5
6//go:build !feature_x
7func ProcessData(data []byte) []byte {
8 return processDataNormal(data)
9}
Build Tag Troubleshooting
Common issues and solutions:
-
Build tag not recognized
- Check for blank line before package
- Verify tag syntax
- Use
go vetto check for issues
-
Unexpected files included/excluded
- Use
go list -f '{{.GoFiles}}' ./...to see which files are included - Check for conflicting tags
- Verify environment variables
- Use
-
Cross-compilation issues
- Build tags can make cross-compilation complex
- Consider using CGo-free alternatives for broad compatibility
- Test on target platforms early
Practice Exercises
Exercise 1: Cross-Platform Configuration Manager
Learning Objectives: Master platform-specific implementations and file naming conventions
Difficulty: Intermediate
Real-World Context: Configuration management systems need different paths, defaults, and behaviors on each platform. This exercise simulates building a production-ready configuration loader that adapts to Windows, Linux, and macOS.
Task: Create a configuration package that:
- Uses file naming conventions (no explicit build tags) for platform detection
- Implements platform-specific config file paths
- Provides different default values per platform
- Includes a test suite that validates platform behavior
Requirements:
config_windows.go- Windows-specific paths (AppData, Registry)config_linux.go- Linux-specific paths (/etc, ~/.config)config_darwin.go- macOS-specific paths (~/Library/Preferences)- Common interface for all platforms
- Unit tests for each platform
Solution:
Show Solution
1// config/config.go - Common interface
2package config
3
4import (
5 "encoding/json"
6 "fmt"
7 "os"
8 "path/filepath"
9)
10
11// Config represents application configuration
12type Config struct {
13 AppName string `json:"app_name"`
14 Version string `json:"version"`
15 Settings map[string]string `json:"settings"`
16 ConfigPath string `json:"-"`
17}
18
19// Loader defines platform-specific config loading
20type Loader interface {
21 GetConfigDir() string
22 GetCacheDir() string
23 GetLogDir() string
24 GetDefaultEditor() string
25}
26
27// LoadConfig loads configuration from platform-specific location
28func LoadConfig(appName string) (*Config, error) {
29 loader := newLoader()
30 configDir := loader.GetConfigDir()
31 configPath := filepath.Join(configDir, appName+".json")
32
33 cfg := &Config{
34 AppName: appName,
35 Version: "1.0.0",
36 Settings: make(map[string]string),
37 ConfigPath: configPath,
38 }
39
40 // Try to load existing config
41 data, err := os.ReadFile(configPath)
42 if err == nil {
43 if err := json.Unmarshal(data, cfg); err != nil {
44 return nil, fmt.Errorf("failed to parse config: %w", err)
45 }
46 }
47
48 // Set platform-specific defaults
49 if cfg.Settings["editor"] == "" {
50 cfg.Settings["editor"] = loader.GetDefaultEditor()
51 }
52 if cfg.Settings["cache_dir"] == "" {
53 cfg.Settings["cache_dir"] = loader.GetCacheDir()
54 }
55 if cfg.Settings["log_dir"] == "" {
56 cfg.Settings["log_dir"] = loader.GetLogDir()
57 }
58
59 return cfg, nil
60}
61
62// Save writes config to disk
63func (c *Config) Save() error {
64 // Ensure config directory exists
65 configDir := filepath.Dir(c.ConfigPath)
66 if err := os.MkdirAll(configDir, 0755); err != nil {
67 return fmt.Errorf("failed to create config directory: %w", err)
68 }
69
70 data, err := json.MarshalIndent(c, "", " ")
71 if err != nil {
72 return fmt.Errorf("failed to marshal config: %w", err)
73 }
74
75 if err := os.WriteFile(c.ConfigPath, data, 0644); err != nil {
76 return fmt.Errorf("failed to write config: %w", err)
77 }
78
79 return nil
80}
1// config/config_windows.go - Windows implementation
2package config
3
4import (
5 "os"
6 "path/filepath"
7)
8
9type windowsLoader struct{}
10
11func newLoader() Loader {
12 return &windowsLoader{}
13}
14
15func (w *windowsLoader) GetConfigDir() string {
16 appData := os.Getenv("APPDATA")
17 if appData == "" {
18 appData = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Roaming")
19 }
20 return appData
21}
22
23func (w *windowsLoader) GetCacheDir() string {
24 localAppData := os.Getenv("LOCALAPPDATA")
25 if localAppData == "" {
26 localAppData = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local")
27 }
28 return filepath.Join(localAppData, "Cache")
29}
30
31func (w *windowsLoader) GetLogDir() string {
32 return filepath.Join(w.GetCacheDir(), "Logs")
33}
34
35func (w *windowsLoader) GetDefaultEditor() string {
36 return "notepad.exe"
37}
1// config/config_linux.go - Linux implementation
2package config
3
4import (
5 "os"
6 "path/filepath"
7)
8
9type linuxLoader struct{}
10
11func newLoader() Loader {
12 return &linuxLoader{}
13}
14
15func (l *linuxLoader) GetConfigDir() string {
16 configHome := os.Getenv("XDG_CONFIG_HOME")
17 if configHome == "" {
18 configHome = filepath.Join(os.Getenv("HOME"), ".config")
19 }
20 return configHome
21}
22
23func (l *linuxLoader) GetCacheDir() string {
24 cacheHome := os.Getenv("XDG_CACHE_HOME")
25 if cacheHome == "" {
26 cacheHome = filepath.Join(os.Getenv("HOME"), ".cache")
27 }
28 return cacheHome
29}
30
31func (l *linuxLoader) GetLogDir() string {
32 return "/var/log"
33}
34
35func (l *linuxLoader) GetDefaultEditor() string {
36 editor := os.Getenv("EDITOR")
37 if editor == "" {
38 return "vi"
39 }
40 return editor
41}
1// config/config_darwin.go - macOS implementation
2package config
3
4import (
5 "os"
6 "path/filepath"
7)
8
9type darwinLoader struct{}
10
11func newLoader() Loader {
12 return &darwinLoader{}
13}
14
15func (d *darwinLoader) GetConfigDir() string {
16 home := os.Getenv("HOME")
17 return filepath.Join(home, "Library", "Application Support")
18}
19
20func (d *darwinLoader) GetCacheDir() string {
21 home := os.Getenv("HOME")
22 return filepath.Join(home, "Library", "Caches")
23}
24
25func (d *darwinLoader) GetLogDir() string {
26 home := os.Getenv("HOME")
27 return filepath.Join(home, "Library", "Logs")
28}
29
30func (d *darwinLoader) GetDefaultEditor() string {
31 return "nano"
32}
1// config/config_test.go - Tests
2package config
3
4import (
5 "os"
6 "path/filepath"
7 "runtime"
8 "testing"
9)
10
11func TestLoadConfig(t *testing.T) {
12 cfg, err := LoadConfig("testapp")
13 if err != nil {
14 t.Fatalf("Failed to load config: %v", err)
15 }
16
17 if cfg.AppName != "testapp" {
18 t.Errorf("Expected app name 'testapp', got '%s'", cfg.AppName)
19 }
20
21 // Verify platform-specific defaults
22 loader := newLoader()
23 expectedEditor := loader.GetDefaultEditor()
24 if cfg.Settings["editor"] != expectedEditor {
25 t.Errorf("Expected editor '%s', got '%s'", expectedEditor, cfg.Settings["editor"])
26 }
27}
28
29func TestPlatformSpecificPaths(t *testing.T) {
30 loader := newLoader()
31
32 configDir := loader.GetConfigDir()
33 if configDir == "" {
34 t.Error("Config dir should not be empty")
35 }
36
37 cacheDir := loader.GetCacheDir()
38 if cacheDir == "" {
39 t.Error("Cache dir should not be empty")
40 }
41
42 // Platform-specific validations
43 switch runtime.GOOS {
44 case "windows":
45 if !filepath.IsAbs(configDir) {
46 t.Error("Windows config dir should be absolute path")
47 }
48 case "linux":
49 if !filepath.IsAbs(configDir) {
50 t.Error("Linux config dir should be absolute path")
51 }
52 case "darwin":
53 if !filepath.IsAbs(configDir) {
54 t.Error("macOS config dir should be absolute path")
55 }
56 }
57}
58
59func TestConfigSaveLoad(t *testing.T) {
60 // Create temporary directory
61 tempDir, err := os.MkdirTemp("", "config-test")
62 if err != nil {
63 t.Fatalf("Failed to create temp dir: %v", err)
64 }
65 defer os.RemoveAll(tempDir)
66
67 // Create config
68 cfg := &Config{
69 AppName: "testapp",
70 Version: "1.0.0",
71 Settings: map[string]string{"key": "value"},
72 ConfigPath: filepath.Join(tempDir, "testapp.json"),
73 }
74
75 // Save config
76 if err := cfg.Save(); err != nil {
77 t.Fatalf("Failed to save config: %v", err)
78 }
79
80 // Verify file exists
81 if _, err := os.Stat(cfg.ConfigPath); os.IsNotExist(err) {
82 t.Error("Config file was not created")
83 }
84}
Usage Example:
1package main
2
3import (
4 "fmt"
5 "yourproject/config"
6)
7
8func main() {
9 cfg, err := config.LoadConfig("myapp")
10 if err != nil {
11 panic(err)
12 }
13
14 fmt.Printf("Platform: %s\n", runtime.GOOS)
15 fmt.Printf("Config path: %s\n", cfg.ConfigPath)
16 fmt.Printf("Default editor: %s\n", cfg.Settings["editor"])
17 fmt.Printf("Cache dir: %s\n", cfg.Settings["cache_dir"])
18 fmt.Printf("Log dir: %s\n", cfg.Settings["log_dir"])
19
20 // Save configuration
21 cfg.Settings["theme"] = "dark"
22 if err := cfg.Save(); err != nil {
23 panic(err)
24 }
25}
26
27// run
Exercise 2: CPU Architecture-Optimized Bitmap Operations
Learning Objectives: Implement architecture-specific optimizations using build tags
Difficulty: Advanced
Real-World Context: Graphics libraries, compression algorithms, and cryptographic systems benefit massively from CPU-specific SIMD instructions. This exercise simulates building optimized bitmap operations.
Task: Create a bitmap package that:
- Implements generic bitmap operations (AND, OR, XOR, NOT)
- Provides AMD64-optimized versions using 64-bit operations
- Provides ARM64-optimized versions
- Includes benchmarks comparing architectures
- Falls back gracefully on unsupported architectures
Solution:
Show Solution
1// bitmap/bitmap.go - Common interface
2package bitmap
3
4// Bitmap represents a bit array
5type Bitmap struct {
6 data []byte
7 size int
8}
9
10// New creates a new bitmap with specified size in bits
11func New(size int) *Bitmap {
12 byteSize := (size + 7) / 8
13 return &Bitmap{
14 data: make([]byte, byteSize),
15 size: size,
16 }
17}
18
19// Set sets a bit at position
20func (b *Bitmap) Set(pos int) {
21 if pos < 0 || pos >= b.size {
22 return
23 }
24 byteIdx := pos / 8
25 bitIdx := pos % 8
26 b.data[byteIdx] |= 1 << bitIdx
27}
28
29// Clear clears a bit at position
30func (b *Bitmap) Clear(pos int) {
31 if pos < 0 || pos >= b.size {
32 return
33 }
34 byteIdx := pos / 8
35 bitIdx := pos % 8
36 b.data[byteIdx] &^= 1 << bitIdx
37}
38
39// Get returns the bit value at position
40func (b *Bitmap) Get(pos int) bool {
41 if pos < 0 || pos >= b.size {
42 return false
43 }
44 byteIdx := pos / 8
45 bitIdx := pos % 8
46 return (b.data[byteIdx] & (1 << bitIdx)) != 0
47}
48
49// Size returns bitmap size in bits
50func (b *Bitmap) Size() int {
51 return b.size
52}
53
54// And performs bitwise AND with another bitmap
55func (b *Bitmap) And(other *Bitmap) *Bitmap {
56 return bitwiseAnd(b, other)
57}
58
59// Or performs bitwise OR with another bitmap
60func (b *Bitmap) Or(other *Bitmap) *Bitmap {
61 return bitwiseOr(b, other)
62}
63
64// Xor performs bitwise XOR with another bitmap
65func (b *Bitmap) Xor(other *Bitmap) *Bitmap {
66 return bitwiseXor(b, other)
67}
68
69// Not performs bitwise NOT
70func (b *Bitmap) Not() *Bitmap {
71 return bitwiseNot(b)
72}
73
74// Count returns number of set bits
75func (b *Bitmap) Count() int {
76 return popcount(b.data)
77}
1// bitmap/bitmap_amd64.go - AMD64 optimizations
2package bitmap
3
4// bitwiseAnd performs AND operation using 64-bit chunks
5func bitwiseAnd(a, b *Bitmap) *Bitmap {
6 size := a.size
7 if b.size < size {
8 size = b.size
9 }
10
11 result := New(size)
12
13 // Process in 64-bit chunks
14 chunks := len(a.data) / 8
15 for i := 0; i < chunks; i++ {
16 offset := i * 8
17 aChunk := uint64(a.data[offset]) | uint64(a.data[offset+1])<<8 |
18 uint64(a.data[offset+2])<<16 | uint64(a.data[offset+3])<<24 |
19 uint64(a.data[offset+4])<<32 | uint64(a.data[offset+5])<<40 |
20 uint64(a.data[offset+6])<<48 | uint64(a.data[offset+7])<<56
21
22 bChunk := uint64(b.data[offset]) | uint64(b.data[offset+1])<<8 |
23 uint64(b.data[offset+2])<<16 | uint64(b.data[offset+3])<<24 |
24 uint64(b.data[offset+4])<<32 | uint64(b.data[offset+5])<<40 |
25 uint64(b.data[offset+6])<<48 | uint64(b.data[offset+7])<<56
26
27 resultChunk := aChunk & bChunk
28
29 result.data[offset] = byte(resultChunk)
30 result.data[offset+1] = byte(resultChunk >> 8)
31 result.data[offset+2] = byte(resultChunk >> 16)
32 result.data[offset+3] = byte(resultChunk >> 24)
33 result.data[offset+4] = byte(resultChunk >> 32)
34 result.data[offset+5] = byte(resultChunk >> 40)
35 result.data[offset+6] = byte(resultChunk >> 48)
36 result.data[offset+7] = byte(resultChunk >> 56)
37 }
38
39 // Process remaining bytes
40 for i := chunks * 8; i < len(a.data) && i < len(b.data); i++ {
41 result.data[i] = a.data[i] & b.data[i]
42 }
43
44 return result
45}
46
47func bitwiseOr(a, b *Bitmap) *Bitmap {
48 size := a.size
49 if b.size > size {
50 size = b.size
51 }
52
53 result := New(size)
54
55 chunks := len(a.data) / 8
56 for i := 0; i < chunks; i++ {
57 offset := i * 8
58 aChunk := uint64(a.data[offset]) | uint64(a.data[offset+1])<<8 |
59 uint64(a.data[offset+2])<<16 | uint64(a.data[offset+3])<<24 |
60 uint64(a.data[offset+4])<<32 | uint64(a.data[offset+5])<<40 |
61 uint64(a.data[offset+6])<<48 | uint64(a.data[offset+7])<<56
62
63 bChunk := uint64(b.data[offset]) | uint64(b.data[offset+1])<<8 |
64 uint64(b.data[offset+2])<<16 | uint64(b.data[offset+3])<<24 |
65 uint64(b.data[offset+4])<<32 | uint64(b.data[offset+5])<<40 |
66 uint64(b.data[offset+6])<<48 | uint64(b.data[offset+7])<<56
67
68 resultChunk := aChunk | bChunk
69
70 result.data[offset] = byte(resultChunk)
71 result.data[offset+1] = byte(resultChunk >> 8)
72 result.data[offset+2] = byte(resultChunk >> 16)
73 result.data[offset+3] = byte(resultChunk >> 24)
74 result.data[offset+4] = byte(resultChunk >> 32)
75 result.data[offset+5] = byte(resultChunk >> 40)
76 result.data[offset+6] = byte(resultChunk >> 48)
77 result.data[offset+7] = byte(resultChunk >> 56)
78 }
79
80 for i := chunks * 8; i < len(a.data) && i < len(b.data); i++ {
81 result.data[i] = a.data[i] | b.data[i]
82 }
83
84 return result
85}
86
87func bitwiseXor(a, b *Bitmap) *Bitmap {
88 size := a.size
89 if b.size > size {
90 size = b.size
91 }
92
93 result := New(size)
94
95 chunks := len(a.data) / 8
96 for i := 0; i < chunks; i++ {
97 offset := i * 8
98 aChunk := uint64(a.data[offset]) | uint64(a.data[offset+1])<<8 |
99 uint64(a.data[offset+2])<<16 | uint64(a.data[offset+3])<<24 |
100 uint64(a.data[offset+4])<<32 | uint64(a.data[offset+5])<<40 |
101 uint64(a.data[offset+6])<<48 | uint64(a.data[offset+7])<<56
102
103 bChunk := uint64(b.data[offset]) | uint64(b.data[offset+1])<<8 |
104 uint64(b.data[offset+2])<<16 | uint64(b.data[offset+3])<<24 |
105 uint64(b.data[offset+4])<<32 | uint64(b.data[offset+5])<<40 |
106 uint64(b.data[offset+6])<<48 | uint64(b.data[offset+7])<<56
107
108 resultChunk := aChunk ^ bChunk
109
110 result.data[offset] = byte(resultChunk)
111 result.data[offset+1] = byte(resultChunk >> 8)
112 result.data[offset+2] = byte(resultChunk >> 16)
113 result.data[offset+3] = byte(resultChunk >> 24)
114 result.data[offset+4] = byte(resultChunk >> 32)
115 result.data[offset+5] = byte(resultChunk >> 40)
116 result.data[offset+6] = byte(resultChunk >> 48)
117 result.data[offset+7] = byte(resultChunk >> 56)
118 }
119
120 for i := chunks * 8; i < len(a.data) && i < len(b.data); i++ {
121 result.data[i] = a.data[i] ^ b.data[i]
122 }
123
124 return result
125}
126
127func bitwiseNot(a *Bitmap) *Bitmap {
128 result := New(a.size)
129
130 chunks := len(a.data) / 8
131 for i := 0; i < chunks; i++ {
132 offset := i * 8
133 aChunk := uint64(a.data[offset]) | uint64(a.data[offset+1])<<8 |
134 uint64(a.data[offset+2])<<16 | uint64(a.data[offset+3])<<24 |
135 uint64(a.data[offset+4])<<32 | uint64(a.data[offset+5])<<40 |
136 uint64(a.data[offset+6])<<48 | uint64(a.data[offset+7])<<56
137
138 resultChunk := ^aChunk
139
140 result.data[offset] = byte(resultChunk)
141 result.data[offset+1] = byte(resultChunk >> 8)
142 result.data[offset+2] = byte(resultChunk >> 16)
143 result.data[offset+3] = byte(resultChunk >> 24)
144 result.data[offset+4] = byte(resultChunk >> 32)
145 result.data[offset+5] = byte(resultChunk >> 40)
146 result.data[offset+6] = byte(resultChunk >> 48)
147 result.data[offset+7] = byte(resultChunk >> 56)
148 }
149
150 for i := chunks * 8; i < len(a.data); i++ {
151 result.data[i] = ^a.data[i]
152 }
153
154 return result
155}
156
157// popcount counts set bits using optimized algorithm
158func popcount(data []byte) int {
159 count := 0
160 for _, b := range data {
161 // Brian Kernighan's algorithm
162 v := uint64(b)
163 for v != 0 {
164 v &= v - 1
165 count++
166 }
167 }
168 return count
169}
1// bitmap/bitmap_generic.go - Generic fallback
2//go:build !amd64 && !arm64
3
4package bitmap
5
6// bitwiseAnd performs AND operation byte by byte
7func bitwiseAnd(a, b *Bitmap) *Bitmap {
8 size := a.size
9 if b.size < size {
10 size = b.size
11 }
12
13 result := New(size)
14 minLen := len(a.data)
15 if len(b.data) < minLen {
16 minLen = len(b.data)
17 }
18
19 for i := 0; i < minLen; i++ {
20 result.data[i] = a.data[i] & b.data[i]
21 }
22
23 return result
24}
25
26func bitwiseOr(a, b *Bitmap) *Bitmap {
27 size := a.size
28 if b.size > size {
29 size = b.size
30 }
31
32 result := New(size)
33 minLen := len(a.data)
34 if len(b.data) < minLen {
35 minLen = len(b.data)
36 }
37
38 for i := 0; i < minLen; i++ {
39 result.data[i] = a.data[i] | b.data[i]
40 }
41
42 return result
43}
44
45func bitwiseXor(a, b *Bitmap) *Bitmap {
46 size := a.size
47 if b.size > size {
48 size = b.size
49 }
50
51 result := New(size)
52 minLen := len(a.data)
53 if len(b.data) < minLen {
54 minLen = len(b.data)
55 }
56
57 for i := 0; i < minLen; i++ {
58 result.data[i] = a.data[i] ^ b.data[i]
59 }
60
61 return result
62}
63
64func bitwiseNot(a *Bitmap) *Bitmap {
65 result := New(a.size)
66 for i := range a.data {
67 result.data[i] = ^a.data[i]
68 }
69 return result
70}
71
72func popcount(data []byte) int {
73 count := 0
74 for _, b := range data {
75 for b != 0 {
76 count += int(b & 1)
77 b >>= 1
78 }
79 }
80 return count
81}
1// bitmap/bitmap_test.go - Tests and benchmarks
2package bitmap
3
4import (
5 "runtime"
6 "testing"
7)
8
9func TestBitmapOperations(t *testing.T) {
10 bm1 := New(16)
11 bm1.Set(0)
12 bm1.Set(3)
13 bm1.Set(7)
14
15 bm2 := New(16)
16 bm2.Set(3)
17 bm2.Set(5)
18 bm2.Set(7)
19
20 // Test AND
21 result := bm1.And(bm2)
22 if !result.Get(3) || !result.Get(7) {
23 t.Error("AND operation failed")
24 }
25 if result.Get(0) || result.Get(5) {
26 t.Error("AND operation produced incorrect results")
27 }
28
29 // Test OR
30 result = bm1.Or(bm2)
31 if !result.Get(0) || !result.Get(3) || !result.Get(5) || !result.Get(7) {
32 t.Error("OR operation failed")
33 }
34
35 // Test XOR
36 result = bm1.Xor(bm2)
37 if !result.Get(0) || !result.Get(5) {
38 t.Error("XOR operation failed")
39 }
40 if result.Get(3) || result.Get(7) {
41 t.Error("XOR operation produced incorrect results")
42 }
43
44 // Test NOT
45 result = bm1.Not()
46 if result.Get(0) || result.Get(3) || result.Get(7) {
47 t.Error("NOT operation failed")
48 }
49}
50
51func TestPopCount(t *testing.T) {
52 bm := New(64)
53 for i := 0; i < 10; i++ {
54 bm.Set(i * 6)
55 }
56
57 count := bm.Count()
58 if count != 10 {
59 t.Errorf("Expected count 10, got %d", count)
60 }
61}
62
63func BenchmarkBitwiseAnd(b *testing.B) {
64 bm1 := New(1024)
65 bm2 := New(1024)
66
67 // Set every other bit
68 for i := 0; i < 1024; i += 2 {
69 bm1.Set(i)
70 bm2.Set(i + 1)
71 }
72
73 b.ResetTimer()
74 b.ReportAllocs()
75
76 for i := 0; i < b.N; i++ {
77 _ = bm1.And(bm2)
78 }
79
80 b.ReportMetric(float64(b.N*1024)/b.Elapsed().Seconds()/1e9, "Gbits/sec")
81}
82
83func BenchmarkPopCount(b *testing.B) {
84 bm := New(10240)
85 for i := 0; i < 10240; i += 3 {
86 bm.Set(i)
87 }
88
89 b.ResetTimer()
90
91 for i := 0; i < b.N; i++ {
92 _ = bm.Count()
93 }
94}
95
96func TestArchitectureInfo(t *testing.T) {
97 t.Logf("Running on architecture: %s", runtime.GOARCH)
98 t.Logf("Operating system: %s", runtime.GOOS)
99}
Usage Example:
1package main
2
3import (
4 "fmt"
5 "runtime"
6 "yourproject/bitmap"
7)
8
9func main() {
10 fmt.Printf("Architecture: %s\n", runtime.GOARCH)
11
12 // Create bitmaps
13 bm1 := bitmap.New(32)
14 bm2 := bitmap.New(32)
15
16 // Set some bits
17 for i := 0; i < 32; i += 2 {
18 bm1.Set(i)
19 }
20 for i := 1; i < 32; i += 2 {
21 bm2.Set(i)
22 }
23
24 // Perform operations
25 andResult := bm1.And(bm2)
26 orResult := bm1.Or(bm2)
27 xorResult := bm1.Xor(bm2)
28
29 fmt.Printf("AND result: %d bits set\n", andResult.Count())
30 fmt.Printf("OR result: %d bits set\n", orResult.Count())
31 fmt.Printf("XOR result: %d bits set\n", xorResult.Count())
32}
33
34// run
Exercise 3: Multi-Environment Feature Flag System
Learning Objectives: Master feature flags using build tags for A/B testing and gradual rollouts
Difficulty: Intermediate
Real-World Context: Modern SaaS platforms deploy multiple versions simultaneously to test features with different user groups. This exercise simulates a feature flag system that's compiled in, not runtime-checked.
Task: Create a feature flag system that:
- Defines features with build tags (stable, beta, experimental)
- Implements different feature sets for each environment
- Provides compile-time guarantees about feature availability
- Includes a feature registry and discovery mechanism
Solution:
Show Solution
1// features/features.go - Common interface
2package features
3
4import (
5 "fmt"
6 "sort"
7)
8
9// Feature represents a single feature flag
10type Feature struct {
11 Name string
12 Enabled bool
13 Description string
14 Category string
15}
16
17// Registry holds all available features
18type Registry struct {
19 features map[string]Feature
20}
21
22// Global registry instance
23var globalRegistry = &Registry{
24 features: make(map[string]Feature),
25}
26
27// Register adds a feature to the registry
28func Register(name, description, category string, enabled bool) {
29 globalRegistry.features[name] = Feature{
30 Name: name,
31 Enabled: enabled,
32 Description: description,
33 Category: category,
34 }
35}
36
37// IsEnabled checks if a feature is enabled
38func IsEnabled(name string) bool {
39 if feature, exists := globalRegistry.features[name]; exists {
40 return feature.Enabled
41 }
42 return false
43}
44
45// GetFeature returns a feature by name
46func GetFeature(name string) (Feature, bool) {
47 feature, exists := globalRegistry.features[name]
48 return feature, exists
49}
50
51// ListFeatures returns all registered features
52func ListFeatures() []Feature {
53 features := make([]Feature, 0, len(globalRegistry.features))
54 for _, feature := range globalRegistry.features {
55 features = append(features, feature)
56 }
57
58 // Sort by name
59 sort.Slice(features, func(i, j int) bool {
60 return features[i].Name < features[j].Name
61 })
62
63 return features
64}
65
66// ListEnabledFeatures returns only enabled features
67func ListEnabledFeatures() []Feature {
68 var enabled []Feature
69 for _, feature := range globalRegistry.features {
70 if feature.Enabled {
71 enabled = append(enabled, feature)
72 }
73 }
74
75 sort.Slice(enabled, func(i, j int) bool {
76 return enabled[i].Name < enabled[j].Name
77 })
78
79 return enabled
80}
81
82// PrintFeatures prints all features to stdout
83func PrintFeatures() {
84 fmt.Println("Feature Flags:")
85 fmt.Println("==============")
86
87 for _, feature := range ListFeatures() {
88 status := "disabled"
89 if feature.Enabled {
90 status = "enabled"
91 }
92 fmt.Printf("[%s] %s - %s (%s)\n", status, feature.Name, feature.Description, feature.Category)
93 }
94}
1// features/features_stable.go - Stable features only
2//go:build !beta && !experimental
3
4package features
5
6func init() {
7 // Core stable features
8 Register("user_authentication", "User login and authentication", "core", true)
9 Register("user_profile", "User profile management", "core", true)
10 Register("basic_search", "Basic search functionality", "core", true)
11 Register("email_notifications", "Email notification system", "core", true)
12
13 // Stable premium features
14 Register("advanced_analytics", "Advanced analytics dashboard", "premium", true)
15 Register("export_data", "Data export functionality", "premium", true)
16
17 // Beta features - disabled in stable
18 Register("ai_recommendations", "AI-powered recommendations", "beta", false)
19 Register("real_time_collab", "Real-time collaboration", "beta", false)
20
21 // Experimental features - disabled in stable
22 Register("voice_commands", "Voice command interface", "experimental", false)
23 Register("blockchain_verify", "Blockchain verification", "experimental", false)
24}
1// features/features_beta.go - Stable + beta features
2//go:build beta && !experimental
3
4package features
5
6func init() {
7 // Core stable features
8 Register("user_authentication", "User login and authentication", "core", true)
9 Register("user_profile", "User profile management", "core", true)
10 Register("basic_search", "Basic search functionality", "core", true)
11 Register("email_notifications", "Email notification system", "core", true)
12
13 // Stable premium features
14 Register("advanced_analytics", "Advanced analytics dashboard", "premium", true)
15 Register("export_data", "Data export functionality", "premium", true)
16
17 // Beta features - enabled in beta
18 Register("ai_recommendations", "AI-powered recommendations", "beta", true)
19 Register("real_time_collab", "Real-time collaboration", "beta", true)
20 Register("dark_mode", "Dark mode theme", "beta", true)
21 Register("api_v2", "Next-generation API", "beta", true)
22
23 // Experimental features - disabled in beta
24 Register("voice_commands", "Voice command interface", "experimental", false)
25 Register("blockchain_verify", "Blockchain verification", "experimental", false)
26}
1// features/features_experimental.go - All features including experimental
2//go:build experimental
3
4package features
5
6func init() {
7 // Core stable features
8 Register("user_authentication", "User login and authentication", "core", true)
9 Register("user_profile", "User profile management", "core", true)
10 Register("basic_search", "Basic search functionality", "core", true)
11 Register("email_notifications", "Email notification system", "core", true)
12
13 // Stable premium features
14 Register("advanced_analytics", "Advanced analytics dashboard", "premium", true)
15 Register("export_data", "Data export functionality", "premium", true)
16
17 // Beta features
18 Register("ai_recommendations", "AI-powered recommendations", "beta", true)
19 Register("real_time_collab", "Real-time collaboration", "beta", true)
20 Register("dark_mode", "Dark mode theme", "beta", true)
21 Register("api_v2", "Next-generation API", "beta", true)
22
23 // Experimental features - all enabled
24 Register("voice_commands", "Voice command interface", "experimental", true)
25 Register("blockchain_verify", "Blockchain verification", "experimental", true)
26 Register("quantum_encryption", "Quantum-resistant encryption", "experimental", true)
27 Register("ar_interface", "Augmented reality interface", "experimental", true)
28}
1// features/features_test.go - Tests
2package features
3
4import (
5 "testing"
6)
7
8func TestFeatureRegistration(t *testing.T) {
9 features := ListFeatures()
10 if len(features) == 0 {
11 t.Error("No features registered")
12 }
13
14 // Core features should always be enabled
15 coreFeatures := []string{"user_authentication", "user_profile", "basic_search"}
16 for _, name := range coreFeatures {
17 if !IsEnabled(name) {
18 t.Errorf("Core feature %s should be enabled", name)
19 }
20 }
21}
22
23func TestFeatureCategories(t *testing.T) {
24 categories := make(map[string]int)
25
26 for _, feature := range ListFeatures() {
27 categories[feature.Category]++
28 }
29
30 if categories["core"] == 0 {
31 t.Error("Should have at least one core feature")
32 }
33}
34
35func TestEnabledFeatures(t *testing.T) {
36 enabled := ListEnabledFeatures()
37
38 for _, feature := range enabled {
39 if !feature.Enabled {
40 t.Errorf("Feature %s in enabled list but not enabled", feature.Name)
41 }
42 }
43}
Usage Example:
1package main
2
3import (
4 "fmt"
5 "yourproject/features"
6)
7
8func main() {
9 fmt.Println("Application Feature Flags\n")
10
11 // Print all features
12 features.PrintFeatures()
13
14 fmt.Println("\nFeature Checks:")
15
16 // Check specific features
17 if features.IsEnabled("ai_recommendations") {
18 fmt.Println("✓ AI Recommendations are enabled")
19 } else {
20 fmt.Println("✗ AI Recommendations are disabled")
21 }
22
23 if features.IsEnabled("voice_commands") {
24 fmt.Println("✓ Voice Commands are enabled")
25 } else {
26 fmt.Println("✗ Voice Commands are disabled")
27 }
28
29 // Get enabled feature count
30 enabled := features.ListEnabledFeatures()
31 fmt.Printf("\nTotal enabled features: %d\n", len(enabled))
32}
33
34// run
Build Commands:
1# Stable build (production)
2go build -o app-stable
3
4# Beta build (beta testers)
5go build -tags "beta" -o app-beta
6
7# Experimental build (internal testing)
8go build -tags "experimental" -o app-experimental
Exercise 4: Cross-Platform System Information Collector
Learning Objectives: Implement platform-specific system calls and information gathering
Difficulty: Advanced
Real-World Context: Monitoring tools, system diagnostics, and infrastructure management need to collect platform-specific metrics. This exercise builds a cross-platform system information collector.
Task: Create a sysinfo package that:
- Collects CPU, memory, disk, and network information
- Uses platform-specific syscalls and APIs
- Provides a unified interface across platforms
- Handles unsupported platforms gracefully
- Includes JSON serialization for reporting
Solution:
Show Solution
1// sysinfo/sysinfo.go - Common interface
2package sysinfo
3
4import (
5 "encoding/json"
6 "time"
7)
8
9// SystemInfo contains all system information
10type SystemInfo struct {
11 Hostname string `json:"hostname"`
12 Platform string `json:"platform"`
13 Arch string `json:"architecture"`
14 CPUCount int `json:"cpu_count"`
15 MemoryTotal uint64 `json:"memory_total_bytes"`
16 MemoryFree uint64 `json:"memory_free_bytes"`
17 MemoryUsed uint64 `json:"memory_used_bytes"`
18 DiskTotal uint64 `json:"disk_total_bytes"`
19 DiskFree uint64 `json:"disk_free_bytes"`
20 Uptime time.Duration `json:"uptime_seconds"`
21 LoadAverage []float64 `json:"load_average,omitempty"`
22 Timestamp time.Time `json:"timestamp"`
23}
24
25// Collector defines platform-specific collection methods
26type Collector interface {
27 CollectCPU() (int, error)
28 CollectMemory() (total, free, used uint64, err error)
29 CollectDisk(path string) (total, free uint64, err error)
30 CollectUptime() (time.Duration, error)
31 CollectLoadAverage() ([]float64, error)
32}
33
34// Collect gathers all system information
35func Collect() (*SystemInfo, error) {
36 collector := newCollector()
37
38 info := &SystemInfo{
39 Hostname: getHostname(),
40 Platform: getPlatform(),
41 Arch: getArch(),
42 Timestamp: time.Now(),
43 }
44
45 // Collect CPU information
46 if cpuCount, err := collector.CollectCPU(); err == nil {
47 info.CPUCount = cpuCount
48 }
49
50 // Collect memory information
51 if total, free, used, err := collector.CollectMemory(); err == nil {
52 info.MemoryTotal = total
53 info.MemoryFree = free
54 info.MemoryUsed = used
55 }
56
57 // Collect disk information (root/C: drive)
58 diskPath := "/"
59 if getPlatform() == "windows" {
60 diskPath = "C:\\"
61 }
62 if total, free, err := collector.CollectDisk(diskPath); err == nil {
63 info.DiskTotal = total
64 info.DiskFree = free
65 }
66
67 // Collect uptime
68 if uptime, err := collector.CollectUptime(); err == nil {
69 info.Uptime = uptime
70 }
71
72 // Collect load average (Unix-like systems only)
73 if loadAvg, err := collector.CollectLoadAverage(); err == nil {
74 info.LoadAverage = loadAvg
75 }
76
77 return info, nil
78}
79
80// ToJSON converts system info to JSON
81func (s *SystemInfo) ToJSON() ([]byte, error) {
82 return json.MarshalIndent(s, "", " ")
83}
84
85// MemoryUsagePercent returns memory usage as percentage
86func (s *SystemInfo) MemoryUsagePercent() float64 {
87 if s.MemoryTotal == 0 {
88 return 0
89 }
90 return float64(s.MemoryUsed) / float64(s.MemoryTotal) * 100
91}
92
93// DiskUsagePercent returns disk usage as percentage
94func (s *SystemInfo) DiskUsagePercent() float64 {
95 if s.DiskTotal == 0 {
96 return 0
97 }
98 used := s.DiskTotal - s.DiskFree
99 return float64(used) / float64(s.DiskTotal) * 100
100}
1// sysinfo/sysinfo_linux.go - Linux implementation
2package sysinfo
3
4import (
5 "bufio"
6 "fmt"
7 "os"
8 "runtime"
9 "strconv"
10 "strings"
11 "syscall"
12 "time"
13)
14
15type linuxCollector struct{}
16
17func newCollector() Collector {
18 return &linuxCollector{}
19}
20
21func getPlatform() string {
22 return "linux"
23}
24
25func getArch() string {
26 return runtime.GOARCH
27}
28
29func getHostname() string {
30 hostname, err := os.Hostname()
31 if err != nil {
32 return "unknown"
33 }
34 return hostname
35}
36
37func (l *linuxCollector) CollectCPU() (int, error) {
38 return runtime.NumCPU(), nil
39}
40
41func (l *linuxCollector) CollectMemory() (uint64, uint64, uint64, error) {
42 file, err := os.Open("/proc/meminfo")
43 if err != nil {
44 return 0, 0, 0, err
45 }
46 defer file.Close()
47
48 var total, free, available uint64
49 scanner := bufio.NewScanner(file)
50
51 for scanner.Scan() {
52 line := scanner.Text()
53 fields := strings.Fields(line)
54 if len(fields) < 2 {
55 continue
56 }
57
58 value, err := strconv.ParseUint(fields[1], 10, 64)
59 if err != nil {
60 continue
61 }
62 value *= 1024 // Convert from KB to bytes
63
64 switch fields[0] {
65 case "MemTotal:":
66 total = value
67 case "MemFree:":
68 free = value
69 case "MemAvailable:":
70 available = value
71 }
72 }
73
74 used := total - available
75 return total, free, used, nil
76}
77
78func (l *linuxCollector) CollectDisk(path string) (uint64, uint64, error) {
79 var stat syscall.Statfs_t
80 if err := syscall.Statfs(path, &stat); err != nil {
81 return 0, 0, err
82 }
83
84 total := stat.Blocks * uint64(stat.Bsize)
85 free := stat.Bfree * uint64(stat.Bsize)
86
87 return total, free, nil
88}
89
90func (l *linuxCollector) CollectUptime() (time.Duration, error) {
91 data, err := os.ReadFile("/proc/uptime")
92 if err != nil {
93 return 0, err
94 }
95
96 fields := strings.Fields(string(data))
97 if len(fields) < 1 {
98 return 0, fmt.Errorf("invalid uptime format")
99 }
100
101 seconds, err := strconv.ParseFloat(fields[0], 64)
102 if err != nil {
103 return 0, err
104 }
105
106 return time.Duration(seconds * float64(time.Second)), nil
107}
108
109func (l *linuxCollector) CollectLoadAverage() ([]float64, error) {
110 data, err := os.ReadFile("/proc/loadavg")
111 if err != nil {
112 return nil, err
113 }
114
115 fields := strings.Fields(string(data))
116 if len(fields) < 3 {
117 return nil, fmt.Errorf("invalid loadavg format")
118 }
119
120 loadAvg := make([]float64, 3)
121 for i := 0; i < 3; i++ {
122 load, err := strconv.ParseFloat(fields[i], 64)
123 if err != nil {
124 return nil, err
125 }
126 loadAvg[i] = load
127 }
128
129 return loadAvg, nil
130}
1// sysinfo/sysinfo_darwin.go - macOS implementation
2package sysinfo
3
4import (
5 "os"
6 "os/exec"
7 "runtime"
8 "strconv"
9 "strings"
10 "syscall"
11 "time"
12)
13
14type darwinCollector struct{}
15
16func newCollector() Collector {
17 return &darwinCollector{}
18}
19
20func getPlatform() string {
21 return "darwin"
22}
23
24func getArch() string {
25 return runtime.GOARCH
26}
27
28func getHostname() string {
29 hostname, err := os.Hostname()
30 if err != nil {
31 return "unknown"
32 }
33 return hostname
34}
35
36func (d *darwinCollector) CollectCPU() (int, error) {
37 return runtime.NumCPU(), nil
38}
39
40func (d *darwinCollector) CollectMemory() (uint64, uint64, uint64, error) {
41 // Use sysctl to get memory info on macOS
42 cmd := exec.Command("sysctl", "hw.memsize")
43 output, err := cmd.Output()
44 if err != nil {
45 return 0, 0, 0, err
46 }
47
48 fields := strings.Fields(string(output))
49 if len(fields) < 2 {
50 return 0, 0, 0, nil
51 }
52
53 total, err := strconv.ParseUint(fields[1], 10, 64)
54 if err != nil {
55 return 0, 0, 0, err
56 }
57
58 // Get free memory using vm_stat
59 cmd = exec.Command("vm_stat")
60 output, err = cmd.Output()
61 if err != nil {
62 return total, 0, 0, nil
63 }
64
65 // Parse vm_stat output (simplified)
66 free := total / 4 // Placeholder estimation
67 used := total - free
68
69 return total, free, used, nil
70}
71
72func (d *darwinCollector) CollectDisk(path string) (uint64, uint64, error) {
73 var stat syscall.Statfs_t
74 if err := syscall.Statfs(path, &stat); err != nil {
75 return 0, 0, err
76 }
77
78 total := stat.Blocks * uint64(stat.Bsize)
79 free := stat.Bfree * uint64(stat.Bsize)
80
81 return total, free, nil
82}
83
84func (d *darwinCollector) CollectUptime() (time.Duration, error) {
85 cmd := exec.Command("sysctl", "-n", "kern.boottime")
86 output, err := cmd.Output()
87 if err != nil {
88 return 0, err
89 }
90
91 // Parse boot time and calculate uptime
92 // Simplified version
93 return time.Hour * 24, nil // Placeholder
94}
95
96func (d *darwinCollector) CollectLoadAverage() ([]float64, error) {
97 cmd := exec.Command("sysctl", "-n", "vm.loadavg")
98 output, err := cmd.Output()
99 if err != nil {
100 return nil, err
101 }
102
103 // Parse load average
104 fields := strings.Fields(string(output))
105 if len(fields) < 3 {
106 return nil, nil
107 }
108
109 loadAvg := make([]float64, 3)
110 for i := 0; i < 3 && i < len(fields); i++ {
111 load, err := strconv.ParseFloat(fields[i], 64)
112 if err != nil {
113 continue
114 }
115 loadAvg[i] = load
116 }
117
118 return loadAvg, nil
119}
1// sysinfo/sysinfo_windows.go - Windows implementation
2package sysinfo
3
4import (
5 "os"
6 "runtime"
7 "syscall"
8 "time"
9 "unsafe"
10)
11
12type windowsCollector struct{}
13
14func newCollector() Collector {
15 return &windowsCollector{}
16}
17
18func getPlatform() string {
19 return "windows"
20}
21
22func getArch() string {
23 return runtime.GOARCH
24}
25
26func getHostname() string {
27 hostname, err := os.Hostname()
28 if err != nil {
29 return "unknown"
30 }
31 return hostname
32}
33
34func (w *windowsCollector) CollectCPU() (int, error) {
35 return runtime.NumCPU(), nil
36}
37
38func (w *windowsCollector) CollectMemory() (uint64, uint64, uint64, error) {
39 kernel32 := syscall.NewLazyDLL("kernel32.dll")
40 globalMemoryStatusEx := kernel32.NewProc("GlobalMemoryStatusEx")
41
42 type memoryStatusEx struct {
43 dwLength uint32
44 dwMemoryLoad uint32
45 ullTotalPhys uint64
46 ullAvailPhys uint64
47 ullTotalPageFile uint64
48 ullAvailPageFile uint64
49 ullTotalVirtual uint64
50 ullAvailVirtual uint64
51 ullAvailExtendedVirtual uint64
52 }
53
54 var memStatus memoryStatusEx
55 memStatus.dwLength = uint32(unsafe.Sizeof(memStatus))
56
57 ret, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&memStatus)))
58 if ret == 0 {
59 return 0, 0, 0, nil
60 }
61
62 total := memStatus.ullTotalPhys
63 free := memStatus.ullAvailPhys
64 used := total - free
65
66 return total, free, used, nil
67}
68
69func (w *windowsCollector) CollectDisk(path string) (uint64, uint64, error) {
70 kernel32 := syscall.NewLazyDLL("kernel32.dll")
71 getDiskFreeSpaceEx := kernel32.NewProc("GetDiskFreeSpaceExW")
72
73 pathPtr, err := syscall.UTF16PtrFromString(path)
74 if err != nil {
75 return 0, 0, err
76 }
77
78 var freeBytesAvailable, totalBytes, totalFreeBytes uint64
79
80 ret, _, _ := getDiskFreeSpaceEx.Call(
81 uintptr(unsafe.Pointer(pathPtr)),
82 uintptr(unsafe.Pointer(&freeBytesAvailable)),
83 uintptr(unsafe.Pointer(&totalBytes)),
84 uintptr(unsafe.Pointer(&totalFreeBytes)),
85 )
86
87 if ret == 0 {
88 return 0, 0, nil
89 }
90
91 return totalBytes, freeBytesAvailable, nil
92}
93
94func (w *windowsCollector) CollectUptime() (time.Duration, error) {
95 kernel32 := syscall.NewLazyDLL("kernel32.dll")
96 getTickCount64 := kernel32.NewProc("GetTickCount64")
97
98 ret, _, _ := getTickCount64.Call()
99 milliseconds := uint64(ret)
100
101 return time.Duration(milliseconds) * time.Millisecond, nil
102}
103
104func (w *windowsCollector) CollectLoadAverage() ([]float64, error) {
105 // Windows doesn't have load average concept
106 return nil, nil
107}
1// sysinfo/sysinfo_test.go - Tests
2package sysinfo
3
4import (
5 "testing"
6)
7
8func TestCollect(t *testing.T) {
9 info, err := Collect()
10 if err != nil {
11 t.Fatalf("Failed to collect system info: %v", err)
12 }
13
14 if info.Hostname == "" {
15 t.Error("Hostname should not be empty")
16 }
17
18 if info.Platform == "" {
19 t.Error("Platform should not be empty")
20 }
21
22 if info.CPUCount == 0 {
23 t.Error("CPU count should be greater than 0")
24 }
25
26 if info.MemoryTotal == 0 {
27 t.Error("Memory total should be greater than 0")
28 }
29}
30
31func TestMemoryUsagePercent(t *testing.T) {
32 info, err := Collect()
33 if err != nil {
34 t.Fatalf("Failed to collect system info: %v", err)
35 }
36
37 usage := info.MemoryUsagePercent()
38 if usage < 0 || usage > 100 {
39 t.Errorf("Memory usage percent should be 0-100, got %.2f", usage)
40 }
41}
42
43func TestJSON(t *testing.T) {
44 info, err := Collect()
45 if err != nil {
46 t.Fatalf("Failed to collect system info: %v", err)
47 }
48
49 jsonData, err := info.ToJSON()
50 if err != nil {
51 t.Fatalf("Failed to convert to JSON: %v", err)
52 }
53
54 if len(jsonData) == 0 {
55 t.Error("JSON output should not be empty")
56 }
57}
Usage Example:
1package main
2
3import (
4 "fmt"
5 "yourproject/sysinfo"
6)
7
8func main() {
9 info, err := sysinfo.Collect()
10 if err != nil {
11 panic(err)
12 }
13
14 fmt.Printf("System Information\n")
15 fmt.Printf("==================\n")
16 fmt.Printf("Hostname: %s\n", info.Hostname)
17 fmt.Printf("Platform: %s\n", info.Platform)
18 fmt.Printf("Architecture: %s\n", info.Arch)
19 fmt.Printf("CPU Count: %d\n", info.CPUCount)
20 fmt.Printf("Memory Total: %.2f GB\n", float64(info.MemoryTotal)/1024/1024/1024)
21 fmt.Printf("Memory Used: %.2f GB (%.1f%%)\n",
22 float64(info.MemoryUsed)/1024/1024/1024,
23 info.MemoryUsagePercent())
24 fmt.Printf("Disk Total: %.2f GB\n", float64(info.DiskTotal)/1024/1024/1024)
25 fmt.Printf("Disk Free: %.2f GB (%.1f%% used)\n",
26 float64(info.DiskFree)/1024/1024/1024,
27 info.DiskUsagePercent())
28 fmt.Printf("Uptime: %v\n", info.Uptime)
29
30 if len(info.LoadAverage) > 0 {
31 fmt.Printf("Load Average: %.2f %.2f %.2f\n",
32 info.LoadAverage[0], info.LoadAverage[1], info.LoadAverage[2])
33 }
34
35 // Print JSON representation
36 fmt.Println("\nJSON Output:")
37 jsonData, _ := info.ToJSON()
38 fmt.Println(string(jsonData))
39}
40
41// run
Exercise 5: Build Tag-Based Test Suite Manager
Learning Objectives: Create comprehensive testing infrastructure using build tags
Difficulty: Advanced
Real-World Context: Large projects need different test suites (unit, integration, performance, end-to-end) that run in different CI/CD stages. This exercise builds a test suite manager.
Task: Create a testing package that:
- Separates tests into categories using build tags
- Implements unit, integration, and e2e test suites
- Provides test fixtures and helpers for each suite
- Includes performance benchmarks with different tag combinations
- Creates a test runner with selective execution
Solution:
Show Solution
1// testing/suite.go - Common test infrastructure
2package testing
3
4import (
5 "fmt"
6 "testing"
7 "time"
8)
9
10// TestSuite defines common test suite interface
11type TestSuite interface {
12 SetupSuite() error
13 TeardownSuite() error
14 SetupTest() error
15 TeardownTest() error
16 Name() string
17}
18
19// BaseSuite provides common functionality
20type BaseSuite struct {
21 T *testing.T
22 StartTime time.Time
23}
24
25// LogInfo logs informational messages
26func (b *BaseSuite) LogInfo(format string, args ...interface{}) {
27 msg := fmt.Sprintf(format, args...)
28 b.T.Logf("[INFO] %s", msg)
29}
30
31// LogError logs error messages
32func (b *BaseSuite) LogError(format string, args ...interface{}) {
33 msg := fmt.Sprintf(format, args...)
34 b.T.Logf("[ERROR] %s", msg)
35}
36
37// AssertEqual asserts two values are equal
38func (b *BaseSuite) AssertEqual(expected, actual interface{}, msgAndArgs ...interface{}) {
39 if expected != actual {
40 msg := fmt.Sprintf("Expected %v, got %v", expected, actual)
41 if len(msgAndArgs) > 0 {
42 msg = fmt.Sprintf("%v: %s", msgAndArgs[0], msg)
43 }
44 b.T.Error(msg)
45 }
46}
47
48// AssertNotNil asserts value is not nil
49func (b *BaseSuite) AssertNotNil(value interface{}, msgAndArgs ...interface{}) {
50 if value == nil {
51 msg := "Expected non-nil value"
52 if len(msgAndArgs) > 0 {
53 msg = fmt.Sprintf("%v: %s", msgAndArgs[0], msg)
54 }
55 b.T.Error(msg)
56 }
57}
58
59// Elapsed returns time since test started
60func (b *BaseSuite) Elapsed() time.Duration {
61 return time.Since(b.StartTime)
62}
1// testing/unit_test.go - Unit tests
2package testing
3
4import (
5 "testing"
6 "time"
7)
8
9type UnitTestSuite struct {
10 BaseSuite
11}
12
13func (u *UnitTestSuite) SetupSuite() error {
14 u.LogInfo("Setting up unit test suite")
15 return nil
16}
17
18func (u *UnitTestSuite) TeardownSuite() error {
19 u.LogInfo("Tearing down unit test suite")
20 return nil
21}
22
23func (u *UnitTestSuite) SetupTest() error {
24 u.StartTime = time.Now()
25 return nil
26}
27
28func (u *UnitTestSuite) TeardownTest() error {
29 u.LogInfo("Test completed in %v", u.Elapsed())
30 return nil
31}
32
33func (u *UnitTestSuite) Name() string {
34 return "Unit Tests"
35}
36
37// TestBasicOperations tests basic functionality
38func TestBasicOperations(t *testing.T) {
39 suite := &UnitTestSuite{BaseSuite: BaseSuite{T: t}}
40 suite.SetupSuite()
41 defer suite.TeardownSuite()
42
43 suite.SetupTest()
44 defer suite.TeardownTest()
45
46 // Test string concatenation
47 result := "hello" + " " + "world"
48 suite.AssertEqual("hello world", result, "String concatenation")
49
50 // Test arithmetic
51 sum := 2 + 2
52 suite.AssertEqual(4, sum, "Addition")
53
54 // Test nil check
55 var ptr *int
56 if ptr != nil {
57 suite.T.Error("Pointer should be nil")
58 }
59
60 suite.LogInfo("All basic operations passed")
61}
62
63// TestDataStructures tests data structure operations
64func TestDataStructures(t *testing.T) {
65 suite := &UnitTestSuite{BaseSuite: BaseSuite{T: t}}
66 suite.SetupSuite()
67 defer suite.TeardownSuite()
68
69 suite.SetupTest()
70 defer suite.TeardownTest()
71
72 // Test slice operations
73 slice := []int{1, 2, 3, 4, 5}
74 suite.AssertEqual(5, len(slice), "Slice length")
75
76 // Test map operations
77 m := make(map[string]int)
78 m["key"] = 42
79 suite.AssertEqual(42, m["key"], "Map access")
80
81 suite.LogInfo("All data structure tests passed")
82}
1// testing/integration_test.go - Integration tests
2//go:build integration
3
4package testing
5
6import (
7 "testing"
8 "time"
9)
10
11type IntegrationTestSuite struct {
12 BaseSuite
13 DBConnection interface{}
14 APIClient interface{}
15}
16
17func (i *IntegrationTestSuite) SetupSuite() error {
18 i.LogInfo("Setting up integration test suite")
19 i.LogInfo("Connecting to test database...")
20 // Simulate database connection
21 time.Sleep(100 * time.Millisecond)
22 i.DBConnection = "test-db-connection"
23
24 i.LogInfo("Initializing API client...")
25 time.Sleep(50 * time.Millisecond)
26 i.APIClient = "test-api-client"
27
28 return nil
29}
30
31func (i *IntegrationTestSuite) TeardownSuite() error {
32 i.LogInfo("Tearing down integration test suite")
33 i.LogInfo("Closing database connection...")
34 i.DBConnection = nil
35
36 i.LogInfo("Closing API client...")
37 i.APIClient = nil
38
39 return nil
40}
41
42func (i *IntegrationTestSuite) SetupTest() error {
43 i.StartTime = time.Now()
44 i.LogInfo("Preparing test data...")
45 return nil
46}
47
48func (i *IntegrationTestSuite) TeardownTest() error {
49 i.LogInfo("Cleaning up test data...")
50 i.LogInfo("Test completed in %v", i.Elapsed())
51 return nil
52}
53
54func (i *IntegrationTestSuite) Name() string {
55 return "Integration Tests"
56}
57
58// TestDatabaseOperations tests database interactions
59func TestDatabaseOperations(t *testing.T) {
60 suite := &IntegrationTestSuite{BaseSuite: BaseSuite{T: t}}
61 suite.SetupSuite()
62 defer suite.TeardownSuite()
63
64 suite.SetupTest()
65 defer suite.TeardownTest()
66
67 suite.AssertNotNil(suite.DBConnection, "Database connection")
68
69 // Simulate database query
70 suite.LogInfo("Executing database query...")
71 time.Sleep(50 * time.Millisecond)
72
73 // Simulate result validation
74 rowCount := 10
75 suite.AssertEqual(10, rowCount, "Query result count")
76
77 suite.LogInfo("Database operations test passed")
78}
79
80// TestAPIIntegration tests API interactions
81func TestAPIIntegration(t *testing.T) {
82 suite := &IntegrationTestSuite{BaseSuite: BaseSuite{T: t}}
83 suite.SetupSuite()
84 defer suite.TeardownSuite()
85
86 suite.SetupTest()
87 defer suite.TeardownTest()
88
89 suite.AssertNotNil(suite.APIClient, "API client")
90
91 // Simulate API call
92 suite.LogInfo("Making API request...")
93 time.Sleep(100 * time.Millisecond)
94
95 // Simulate response validation
96 statusCode := 200
97 suite.AssertEqual(200, statusCode, "API response status")
98
99 suite.LogInfo("API integration test passed")
100}
1// testing/e2e_test.go - End-to-end tests
2//go:build e2e
3
4package testing
5
6import (
7 "testing"
8 "time"
9)
10
11type E2ETestSuite struct {
12 BaseSuite
13 Browser interface{}
14 TestServer interface{}
15 TestDatabase interface{}
16}
17
18func (e *E2ETestSuite) SetupSuite() error {
19 e.LogInfo("Setting up E2E test suite")
20 e.LogInfo("Starting test server...")
21 time.Sleep(200 * time.Millisecond)
22 e.TestServer = "test-server"
23
24 e.LogInfo("Starting test database...")
25 time.Sleep(150 * time.Millisecond)
26 e.TestDatabase = "test-database"
27
28 e.LogInfo("Launching browser...")
29 time.Sleep(300 * time.Millisecond)
30 e.Browser = "headless-browser"
31
32 return nil
33}
34
35func (e *E2ETestSuite) TeardownSuite() error {
36 e.LogInfo("Tearing down E2E test suite")
37 e.LogInfo("Closing browser...")
38 e.Browser = nil
39
40 e.LogInfo("Stopping test server...")
41 e.TestServer = nil
42
43 e.LogInfo("Stopping test database...")
44 e.TestDatabase = nil
45
46 return nil
47}
48
49func (e *E2ETestSuite) SetupTest() error {
50 e.StartTime = time.Now()
51 e.LogInfo("Preparing E2E test scenario...")
52 return nil
53}
54
55func (e *E2ETestSuite) TeardownTest() error {
56 e.LogInfo("Cleaning up E2E test artifacts...")
57 e.LogInfo("Test completed in %v", e.Elapsed())
58 return nil
59}
60
61func (e *E2ETestSuite) Name() string {
62 return "E2E Tests"
63}
64
65// TestUserRegistrationFlow tests complete user registration
66func TestUserRegistrationFlow(t *testing.T) {
67 suite := &E2ETestSuite{BaseSuite: BaseSuite{T: t}}
68 suite.SetupSuite()
69 defer suite.TeardownSuite()
70
71 suite.SetupTest()
72 defer suite.TeardownTest()
73
74 suite.AssertNotNil(suite.Browser, "Browser")
75 suite.AssertNotNil(suite.TestServer, "Test server")
76
77 // Step 1: Navigate to registration page
78 suite.LogInfo("Navigating to registration page...")
79 time.Sleep(100 * time.Millisecond)
80
81 // Step 2: Fill registration form
82 suite.LogInfo("Filling registration form...")
83 time.Sleep(150 * time.Millisecond)
84
85 // Step 3: Submit form
86 suite.LogInfo("Submitting registration form...")
87 time.Sleep(200 * time.Millisecond)
88
89 // Step 4: Verify success
90 suite.LogInfo("Verifying registration success...")
91 time.Sleep(100 * time.Millisecond)
92
93 success := true
94 suite.AssertEqual(true, success, "Registration flow")
95
96 suite.LogInfo("User registration flow test passed")
97}
98
99// TestCheckoutProcess tests complete checkout process
100func TestCheckoutProcess(t *testing.T) {
101 suite := &E2ETestSuite{BaseSuite: BaseSuite{T: t}}
102 suite.SetupSuite()
103 defer suite.TeardownSuite()
104
105 suite.SetupTest()
106 defer suite.TeardownTest()
107
108 // Step 1: Add items to cart
109 suite.LogInfo("Adding items to cart...")
110 time.Sleep(100 * time.Millisecond)
111
112 // Step 2: Proceed to checkout
113 suite.LogInfo("Proceeding to checkout...")
114 time.Sleep(150 * time.Millisecond)
115
116 // Step 3: Fill payment info
117 suite.LogInfo("Filling payment information...")
118 time.Sleep(200 * time.Millisecond)
119
120 // Step 4: Complete purchase
121 suite.LogInfo("Completing purchase...")
122 time.Sleep(250 * time.Millisecond)
123
124 // Step 5: Verify order
125 suite.LogInfo("Verifying order completion...")
126 time.Sleep(100 * time.Millisecond)
127
128 orderComplete := true
129 suite.AssertEqual(true, orderComplete, "Checkout process")
130
131 suite.LogInfo("Checkout process test passed")
132}
1// testing/performance_test.go - Performance benchmarks
2//go:build performance
3
4package testing
5
6import (
7 "testing"
8)
9
10// BenchmarkStringConcatenation benchmarks string operations
11func BenchmarkStringConcatenation(b *testing.B) {
12 b.ReportAllocs()
13
14 for i := 0; i < b.N; i++ {
15 result := "hello" + " " + "world"
16 _ = result
17 }
18}
19
20// BenchmarkMapOperations benchmarks map access
21func BenchmarkMapOperations(b *testing.B) {
22 m := make(map[int]string)
23 for i := 0; i < 1000; i++ {
24 m[i] = "value"
25 }
26
27 b.ResetTimer()
28 b.ReportAllocs()
29
30 for i := 0; i < b.N; i++ {
31 _ = m[i%1000]
32 }
33}
34
35// BenchmarkSliceAppend benchmarks slice operations
36func BenchmarkSliceAppend(b *testing.B) {
37 b.ReportAllocs()
38
39 for i := 0; i < b.N; i++ {
40 slice := make([]int, 0)
41 for j := 0; j < 100; j++ {
42 slice = append(slice, j)
43 }
44 }
45}
Test Commands:
1# Run unit tests only (default)
2go test ./testing/...
3
4# Run integration tests
5go test -tags "integration" ./testing/...
6
7# Run E2E tests
8go test -tags "e2e" ./testing/... -timeout 30m
9
10# Run performance benchmarks
11go test -tags "performance" -bench=. ./testing/...
12
13# Run all tests
14go test -tags "integration,e2e,performance" -bench=. ./testing/...
15
16# Run specific test with verbose output
17go test -tags "integration" -v -run TestDatabaseOperations ./testing/
Summary
Key Takeaways
Build Tags Essentials:
- Compile-time selection - Build tags eliminate unused code at compilation time
- Platform targeting - Create optimized implementations for specific OS/architecture combinations
- Feature flags - Enable/disable functionality at compile time for different builds
- A/B testing - Deploy different variants for experimentation and gradual rollouts
- Environment configs - Create different configurations for dev/staging/prod environments
Critical Patterns:
- Platform-specific files: Use
*_GOOS.gonaming conventions - Feature toggles: Enable experimental features with build tags
- Environment configs: Different database URLs, API keys, debug settings
- Testing separation: Unit tests vs integration tests with build tags
- Fallback implementations: Always provide default implementations
Build Tag Workflow
- Identify variations - What needs to be different across platforms/environments?
- Choose strategy - Build tags vs runtime detection vs feature flags
- Implement tags - Use clear, maintainable tag expressions
- Test thoroughly - Verify all tag combinations work correctly
- Document clearly - Explain purpose and usage of each tag
Commands & Tools
1# Check which files would be included
2go list -f '{{.GoFiles}}' ./...
3
4# Build for specific platform
5GOOS=linux GOARCH=amd64 go build
6
7# Build with custom tags
8go build -tags "development,debug"
9
10# Check build tag syntax
11go vet
12
13# List all possible platforms
14go tool dist list
15
16# Test with specific tags
17go test -tags "integration"
18
19# Show included files for tags
20go list -tags "linux,amd64" -f '{{.GoFiles}}' ./...
Production Readiness Checklist
- All critical paths have platform-specific implementations
- Fallback implementations exist for edge cases
- CI tests all build tag combinations
- Documentation explains tag purpose and usage
- Build processes handle all target platforms
- Monitoring detects build tag usage issues
- A/B testing workflow established with build tags
Next Steps in Your Go Journey
For Cross-Platform Development:
- Study cross-platform GUI frameworks
- Learn about platform-specific APIs and syscalls
- Master container-based development
- Explore platform abstraction layers
For System Programming:
- Study operating system interfaces and kernel programming
- Learn about hardware-specific optimizations
- Master embedded systems development
- Explore system monitoring and observability
For DevOps and CI/CD:
- Study containerized build pipelines with build tags
- Learn about multi-platform deployment strategies
- Master infrastructure as code patterns
- Explore blue-green deployment techniques
Build tags are a powerful feature that makes Go truly "write once, run anywhere." The techniques and patterns you've learned here will serve you throughout your Go development career, whether you're building desktop applications, server software, or embedded systems.
Remember the Build Tag Golden Rule: Compile early, compile once, run everywhere! Your users will thank you for applications that work perfectly on every platform they target!