Cross-compilation: binários para qualquer plataforma
1. Introdução à Cross-compilation em Go
Cross-compilation é a capacidade de gerar binários executáveis para um sistema operacional e arquitetura diferentes da máquina onde o código está sendo compilado. Em muitas linguagens, isso exige configurações complexas, toolchains específicas e bibliotecas compiladas para cada alvo. Em Go, essa tarefa é surpreendentemente simples e nativa.
O compilador Go é único — ele não depende de um runtime externo como a JVM ou o .NET Framework. Todo o runtime necessário (scheduler, garbage collector, gerenciamento de goroutines) é embutido no binário final. Isso significa que um binário Go compilado para Linux rodará em qualquer distribuição Linux sem precisar instalar dependências adicionais.
Os cenários de uso são vastos: desenvolvedores em macOS que precisam gerar binários para servidores Linux, equipes que trabalham com dispositivos IoT como Raspberry Pi (arquitetura ARM), ou a criação de imagens Docker minimalistas (scratch images). Com Go, você compila uma vez e executa em qualquer lugar — literalmente.
2. Configurando o Ambiente: Variáveis GOOS e GOARCH
O segredo da cross-compilation em Go está em duas variáveis de ambiente:
GOOS: define o sistema operacional alvo (ex:linux,darwin,windows,freebsd)GOARCH: define a arquitetura de CPU alvo (ex:amd64,arm64,386,arm)
Para descobrir todas as combinações suportadas pela sua versão do Go, execute:
go tool dist list
Isso exibirá uma lista completa como:
linux/amd64
linux/arm64
linux/arm
darwin/amd64
darwin/arm64
windows/amd64
windows/386
freebsd/amd64
openbsd/amd64
...
Cada linha representa uma combinação válida de sistema operacional e arquitetura que o compilador Go pode gerar.
3. Comandos Práticos para Compilação Cruzada
A sintaxe básica para cross-compilation é simples. Basta definir as variáveis antes do comando go build:
GOOS=linux GOARCH=amd64 go build -o meu-programa
Para Windows, lembre-se de adicionar a extensão .exe:
GOOS=windows GOARCH=amd64 go build -o app.exe
Compilando para Raspberry Pi (ARM 32 bits com suporte a hardware floating point):
GOOS=linux GOARCH=arm GOARM=7 go build -o app-arm
Para Apple Silicon (M1, M2):
GOOS=darwin GOARCH=arm64 go build -o app-macos-arm64
Você pode compilar para qualquer combinação sem precisar instalar nada adicional — o compilador Go já inclui tudo que precisa.
4. Lidando com Dependências e CGO
Aqui mora o principal desafio da cross-compilation em Go: o CGO. Quando seu código utiliza bibliotecas escritas em C (via import "C"), ou depende de pacotes que usam CGO (como mattn/go-sqlite3 ou gocv), a compilação cruzada se complica.
A solução mais comum é desabilitar o CGO:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o app
Isso gera um binário estático, sem dependências de bibliotecas C do sistema. O binário será maior, mas executará em qualquer distribuição Linux, mesmo em sistemas minimalistas como Alpine Linux (que usa musl em vez de glibc).
Se você precisa de CGO, terá que instalar toolchains específicas para cada plataforma alvo. Por exemplo:
# Para Windows a partir do Linux
GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build
# Para ARM Linux
GOOS=linux GOARCH=arm GOARM=7 CGO_ENABLED=1 CC=arm-linux-gnueabihf-gcc go build
Sempre que possível, prefira bibliotecas puras Go para evitar essa complexidade.
5. Otimizações e Boas Práticas
Binários Go podem ser grandes. Para reduzi-los significativamente:
go build -ldflags="-s -w" -trimpath -o app
-s -w: remove informações de debug e símbolos (redução de ~30% no tamanho)-trimpath: remove caminhos absolutos do sistema de arquivos do desenvolvedor do binário
Para debugging (apenas em desenvolvimento), use flags que preservam informações:
go build -gcflags="all=-N -l" -o app-debug
Para automação, go generate permite embedar comandos de build no código fonte:
//go:generate GOOS=linux GOARCH=amd64 go build -o bin/linux/meuapp
//go:generate GOOS=windows GOARCH=amd64 go build -o bin/windows/meuapp.exe
Execute go generate ./... para compilar tudo de uma vez.
6. Automatizando Builds com Scripts e Makefiles
Um Makefile prático para compilar para as três plataformas principais:
APP_NAME = meuapp
VERSION = 1.0.0
.PHONY: build-all build-linux build-darwin build-windows clean
build-all: build-linux build-darwin build-windows
build-linux:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
go build -ldflags="-s -w" -trimpath \
-o dist/$(APP_NAME)-$(VERSION)-linux-amd64
build-darwin:
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 \
go build -ldflags="-s -w" -trimpath \
-o dist/$(APP_NAME)-$(VERSION)-darwin-amd64
build-windows:
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 \
go build -ldflags="-s -w" -trimpath \
-o dist/$(APP_NAME)-$(VERSION)-windows-amd64.exe
clean:
rm -rf dist/
Para CI/CD com GitHub Actions, um workflow simples:
jobs:
build:
strategy:
matrix:
goos: [linux, darwin, windows]
goarch: [amd64]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- run: |
GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} \
CGO_ENABLED=0 go build -ldflags="-s -w" -o app
- uses: actions/upload-artifact@v4
with:
name: app-${{ matrix.goos }}-${{ matrix.goarch }}
path: app
7. Testando e Validando Binários Compilados
Para verificar se o binário foi compilado corretamente, use o comando file (Linux/macOS):
file meuapp
# Saída: meuapp: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked
Para testar binários ARM sem ter um dispositivo físico, use Docker:
# Testar binário ARM64
docker run --platform linux/arm64 --rm -v $(pwd):/app alpine /app/meuapp-arm64
# Testar binário ARM 32 bits
docker run --platform linux/arm/v7 --rm -v $(pwd):/app alpine /app/meuapp-arm
Para inspeção mais detalhada, use as ferramentas do próprio Go:
go tool objdump -s main.main meuapp | head -20
8. Casos Avançados e Limitações
Go suporta plataformas menos comuns como FreeBSD, OpenBSD, Plan 9 e até Solaris:
GOOS=freebsd GOARCH=amd64 go build -o app-freebsd
GOOS=plan9 GOARCH=amd64 go build -o app-plan9
Limitações importantes:
- CGO: nem todas as combinações de GOOS/GOARCH suportam CGO. Verifique com
go tool dist list -json - Syscalls: algumas syscalls específicas do Linux (como
epoll) não existem em outros sistemas. Use a biblioteca padrão que abstrai essas diferenças - Build tags: para código específico de plataforma, use tags de compilação:
//go:build linux
package main
func init() {
// Código específico para Linux
}
//go:build darwin
package main
func init() {
// Código específico para macOS
}
A cross-compilation em Go é uma das features mais poderosas da linguagem. Com poucas linhas de comando, você pode gerar binários para praticamente qualquer plataforma existente, mantendo a simplicidade e eficiência que tornam Go uma escolha excepcional para desenvolvimento de sistemas e ferramentas de linha de comando.
Referências
- Documentação oficial de cross-compilation Go — Lista completa de variáveis GOOS e GOARCH suportadas
- Go Wiki: Windows cross-compilation — Guia detalhado para compilar binários Windows a partir de outros sistemas
- Reduzindo tamanho de binários Go — Técnicas oficiais para otimizar o tamanho dos executáveis
- Go Build Modes — Documentação sobre diferentes modos de compilação (
-buildmode) - Docker multi-platform builds com Go — Como criar imagens Docker para múltiplas arquiteturas usando Go
- Go by Example: Cross-compilation — Exemplos práticos de cross-compilation com explicações passo a passo