Cross-Compilation in Golang
Cross-compilation allows you to build Go binaries for different operating systems and architectures than the one you’re currently using. This is particularly useful for deploying applications to servers, creating distributable binaries, or supporting multiple platforms from a single development machine.
Understanding GOOS and GOARCH
Go uses two environment variables to control the target platform:
GOOS: Target operating system (windows, linux, darwin, etc.)GOARCH: Target architecture (amd64, arm64, 386, etc.)
Supported Platforms
# Show current platform
go env GOOS GOARCH
# List common combinations
# Windows
GOOS=windows GOARCH=amd64 # 64-bit Windows
GOOS=windows GOARCH=386 # 32-bit Windows
# Linux
GOOS=linux GOARCH=amd64 # 64-bit Linux
GOOS=linux GOARCH=386 # 32-bit Linux
GOOS=linux GOARCH=arm # 32-bit ARM (Raspberry Pi)
GOOS=linux GOARCH=arm64 # 64-bit ARM
# macOS
GOOS=darwin GOARCH=amd64 # Intel Macs
GOOS=darwin GOARCH=arm64 # Apple Silicon Macs
# FreeBSD
GOOS=freebsd GOARCH=amd64 # 64-bit FreeBSD
# And many more...Basic Cross-Compilation
Simple Build for Different Platforms
# Build for Windows from Linux/Mac
GOOS=windows GOARCH=amd64 go build -o app.exe
# Build for Linux from Windows
GOOS=linux GOARCH=amd64 go build -o app-linux
# Build for macOS from Linux
GOOS=darwin GOARCH=amd64 go build -o app-macCross-Compiling with CGO
Cross-compilation becomes more complex when your code uses CGO (C bindings).
# Disable CGO for pure Go cross-compilation
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build
# For CGO dependencies, you need cross-compilation toolchain
# This requires installing cross-compilers for target platformsBuild Scripts and Automation
Makefile for Cross-Compilation
.PHONY: build-all build-linux build-windows build-mac clean
BINARY_NAME=myapp
VERSION=1.0.0
build-all: build-linux build-windows build-mac
build-linux:
GOOS=linux GOARCH=amd64 go build -o bin/${BINARY_NAME}-linux-amd64-${VERSION} .
GOOS=linux GOARCH=arm64 go build -o bin/${BINARY_NAME}-linux-arm64-${VERSION} .
build-windows:
GOOS=windows GOARCH=amd64 go build -o bin/${BINARY_NAME}-windows-amd64-${VERSION}.exe .
build-mac:
GOOS=darwin GOARCH=amd64 go build -o bin/${BINARY_NAME}-darwin-amd64-${VERSION} .
GOOS=darwin GOARCH=arm64 go build -o bin/${BINARY_NAME}-darwin-arm64-${VERSION} .
clean:
rm -rf bin/
Shell Script for Cross-Compilation
#!/bin/bash
APP_NAME="myapp"
VERSION="1.0.0"
OUTPUT_DIR="dist"
# Create output directory
mkdir -p $OUTPUT_DIR
# Define platforms
PLATFORMS=("linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64" "windows/amd64")
for platform in "${PLATFORMS[@]}"; do
IFS='/' read -r -a array <<< "$platform"
GOOS="${array[0]}"
GOARCH="${array[1]}"
output_name="$APP_NAME-$GOOS-$GOARCH-$VERSION"
if [ "$GOOS" = "windows" ]; then
output_name="$output_name.exe"
fi
echo "Building for $GOOS/$GOARCH..."
GOOS=$GOOS GOARCH=$GOARCH go build -o "$OUTPUT_DIR/$output_name" .
if [ $? -ne 0 ]; then
echo "Failed to build for $GOOS/$GOARCH"
exit 1
fi
done
echo "Cross-compilation completed!"Handling Platform-Specific Code
Build Tags
Use build tags to include/exclude platform-specific code.
// +build linux
package main
import "fmt"
func init() {
fmt.Println("This code only runs on Linux")
}// +build windows
package main
import "fmt"
func init() {
fmt.Println("This code only runs on Windows")
}New Build Tag Syntax (Go 1.17+)
//go:build linux
package main
import "fmt"
func init() {
fmt.Println("This code only runs on Linux")
}Building with Tags
# Build with specific tag
go build -tags=linux
# Build for production (excluding debug code)
go build -tags=productionFile Extensions and Naming
Automatic File Extensions
Go automatically adds .exe extension for Windows binaries:
# On Linux/Mac, building for Windows
GOOS=windows GOARCH=amd64 go build -o app
# Creates: app.exe
# Explicit extension
GOOS=windows GOARCH=amd64 go build -o app.exeNaming Conventions
Common naming patterns for distributable binaries:
# Format: name-os-architecture-version
myapp-linux-amd64-v1.0.0
myapp-darwin-arm64-v1.0.0
myapp-windows-amd64-v1.0.0.exe
# Or simpler: name-os-arch
myapp-linux-amd64
myapp-darwin-amd64CGO Cross-Compilation
When CGO is Required
CGO is needed when your Go code interfaces with C libraries:
package main
import "C"
import "fmt"
//export GoFunction
func GoFunction() {
fmt.Println("Called from C")
}
func main() {
// This code uses CGO
}Cross-Compiling with CGO
Cross-compiling with CGO requires cross-compilation toolchains:
# For ARM Linux (requires arm-linux-gnueabihf-gcc)
CC=arm-linux-gnueabihf-gcc GOOS=linux GOARCH=arm go build
# For Windows from Linux (requires mingw-w64)
CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64 go buildAvoiding CGO
For easier cross-compilation, avoid CGO when possible:
# Disable CGO
CGO_ENABLED=0 go build
# Check if your code can build without CGO
CGO_ENABLED=0 go build .Embedding Build Information
Version Information
Embed build information into your binary:
package main
import (
"fmt"
"runtime"
)
var (
version = "dev"
buildTime = "unknown"
gitCommit = "unknown"
)
func main() {
fmt.Printf("Version: %s\n", version)
fmt.Printf("Built: %s\n", buildTime)
fmt.Printf("Commit: %s\n", gitCommit)
fmt.Printf("Platform: %s/%s\n", runtime.GOOS, runtime.GOARCH)
}Build Script with Version Info
#!/bin/bash
VERSION="1.0.0"
GIT_COMMIT=$(git rev-parse HEAD)
BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
GOOS=linux GOARCH=amd64 go build \
-ldflags "-X main.version=$VERSION -X main.buildTime=$BUILD_TIME -X main.gitCommit=$GIT_COMMIT" \
-o app-linux-amd64 .Docker for Cross-Compilation
Using Docker for Consistent Builds
# Dockerfile for cross-compilation
FROM golang:1.21-alpine AS builder
WORKDIR /app
# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download
# Copy source code
COPY . .
# Build for multiple platforms
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app-linux-amd64 .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o app-linux-arm64 .
RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o app-windows-amd64.exe .Multi-Stage Docker Build
# Build stage
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
# Build for target platform
ARG TARGETOS
ARG TARGETARCH
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o app .
# Runtime stage
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/app .
CMD ["./app"]Building Multi-Platform Images
# Build for multiple platforms
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .CI/CD Integration
GitHub Actions Example
name: Cross-Platform Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- os: linux
arch: amd64
- os: linux
arch: arm64
- os: windows
arch: amd64
- os: darwin
arch: amd64
- os: darwin
arch: arm64
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Build
run: |
output_name="myapp-${{ matrix.os }}-${{ matrix.arch }}"
if [ "${{ matrix.os }}" = "windows" ]; then
output_name="$output_name.exe"
fi
GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build -o $output_name .
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: binaries
path: myapp-*GitLab CI Example
stages:
- build
build:
stage: build
image: golang:1.21
script:
- mkdir -p dist
- |
for platform in "linux/amd64" "linux/arm64" "windows/amd64" "darwin/amd64" "darwin/arm64"; do
IFS='/' read -r -a array <<< "$platform"
GOOS="${array[0]}"
GOARCH="${array[1]}"
output_name="myapp-$GOOS-$GOARCH"
if [ "$GOOS" = "windows" ]; then
output_name="$output_name.exe"
fi
echo "Building for $GOOS/$GOARCH..."
GOOS=$GOOS GOARCH=$GOARCH go build -o "dist/$output_name" .
done
artifacts:
paths:
- dist/
expire_in: 1 weekOptimizing for Size
Reducing Binary Size
# Strip debug information
go build -ldflags="-s -w" -o app
# Compress with UPX (if appropriate)
# upx app
# Build with optimization flags
go build -ldflags="-s -w" -gcflags="-l -B" -o appStatic Linking
# Create statically linked binary
CGO_ENABLED=0 go build -ldflags '-extldflags "-static"' -o appTroubleshooting
Common Issues
“no such file or directory” errors:
# Ensure output directory exists
mkdir -p bin/
go build -o bin/app .CGO compilation errors:
# Check CGO status
go env CGO_ENABLED
# Disable CGO if possible
CGO_ENABLED=0 go buildArchitecture not supported:
# Check supported platforms
go tool dist listBuild cache issues:
# Clear build cache
go clean -cache
# Rebuild everything
go build -aDebugging Cross-Compilation
# Verbose build output
go build -x
# Show compiler version
go version
# Check environment
go env | grep -E "(GOOS|GOARCH|CGO)"Best Practices
- Use CGO_ENABLED=0 when possible - Easier cross-compilation
- Test on target platforms - Cross-compiled binaries should be tested
- Use consistent naming - Follow naming conventions for binaries
- Include version information - Embed build metadata
- Automate the process - Use scripts or CI/CD for regular builds
- Handle platform-specific code - Use build tags appropriately
- Optimize binary size - Strip debug info and compress when appropriate
- Document target platforms - Specify supported platforms in documentation
Performance Considerations
Architecture-Specific Optimizations
import "runtime"
// Architecture-specific code
func init() {
switch runtime.GOARCH {
case "amd64":
// AMD64 optimizations
case "arm64":
// ARM64 optimizations
}
}Memory Alignment
Different architectures have different alignment requirements:
// Structure padding may vary by architecture
type Data struct {
a bool // 1 byte
// padding may be added here
b int64 // 8 bytes
}External Resources:
Related Tutorials: