Construction et packaging¶
Go dispose d'un système de build intégré et autonome. go build produit un binaire statique sans dépendance externe, go mod géré les dépendances de façon reproductible, et la cross-compilation vers n'importe quelle plateforme est native. Cette section couvre le cycle de vie complet : modules, compilation, conteneurisation et distribution.
Go modules¶
Go modules est le système de gestion de dépendances officiel depuis Go 1.16. Un module est défini par un fichier go.mod à la racine du projet.
# Initialiser un nouveau module
go mod init example.com/monprojet
# Ajouter une dependance
go get github.com/gin-gonic/gin@v1.10.0
# Mettre a jour une dependance vers la derniere version mineure compatible
go get github.com/gin-gonic/gin@latest
# Nettoyer les dependances inutilisees et completer les manquantes
go mod tidy
# Telecharger toutes les dependances dans le cache local
go mod download
# Vendoriser les dependances (copie dans vendor/)
go mod vendor
go.mod — Anatomie¶
module example.com/items-api
go 1.22 // Version minimale requise du compilateur
require (
github.com/gin-gonic/gin v1.10.0
gorm.io/driver/sqlite v1.5.5
gorm.io/gorm v1.25.10
)
require (
// Dependances indirectes (gerees automatiquement par go mod tidy)
github.com/bytedance/sonic v1.11.6 // indirect
github.com/mattn/go-sqlite3 v1.14.22 // indirect
)
go.sum — Intégrité¶
go.sum contient les hashes cryptographiques de chaque dépendance. Il ne doit jamais être edite manuellement et doit être commite dans le dépôt.
# Verifier l'integrite des dependances
go mod verify
# Inspecter le graphe de dependances
go mod graph
# Voir pourquoi une dependance est incluse
go mod why github.com/gin-gonic/gin
Ne jamais supprimer go.sum
go.sum garantit la reproductibilite des builds et la sécurité de la chaîne d'approvisionnement. Sa suppression expose a des attaques de type dependency confusion.
Cross-compilation¶
Go compile nativement pour toutes les plateformes cibles via les variables d'environnement GOOS et GOARCH.
# Lister les combinaisons supportees
go tool dist list
# Compilation pour Linux AMD64 depuis n'importe quelle plateforme
GOOS=linux GOARCH=amd64 go build -o myapp-linux-amd64 .
# Compilation pour Windows
GOOS=windows GOARCH=amd64 go build -o myapp.exe .
# Compilation pour macOS ARM (Apple Silicon)
GOOS=darwin GOARCH=arm64 go build -o myapp-darwin-arm64 .
# Compilation pour Linux ARM (Raspberry Pi)
GOOS=linux GOARCH=arm GOARM=7 go build -o myapp-linux-arm .
Tableau des cibles courantes¶
| GOOS | GOARCH | Cible |
|---|---|---|
| linux | amd64 | Serveurs x86-64 (cloud, CI) |
| linux | arm64 | AWS Graviton, Raspberry Pi 4+ |
| linux | arm | Raspberry Pi 3 et anciens |
| darwin | amd64 | Mac Intel |
| darwin | arm64 | Mac Apple Silicon (M1/M2/M3) |
| windows | amd64 | Windows 64 bits |
| js | wasm | WebAssembly dans le navigateur |
CGO et ses contraintes¶
CGO permet d'appeler du code C depuis Go, mais complique la cross-compilation.
// Fichier avec CGO — ne peut pas etre cross-compile simplement
package main
/*
#include <stdio.h>
void hello_c() { printf("Bonjour depuis C\n"); }
*/
import "C" // Import special — active CGO
func main() {
C.hello_c()
}
# Desactiver CGO pour un binaire 100% statique
CGO_ENABLED=0 GOOS=linux go build -o myapp-static .
# Verifier qu'un binaire est statique
file myapp-static
# myapp-static: ELF 64-bit LSB executable, statically linked
ldd myapp-static
# not a dynamic executable
CGO_ENABLED=0 en production
Pour les conteneurs scratch ou distroless, CGO_ENABLED=0 est obligatoire. Les drivers SQLite (mattn/go-sqlite3) requierent CGO — utilisez modernc.org/sqlite (pure Go) comme alternative compatible scratch.
Build tags¶
Les build tags permettent de conditionner la compilation selon la plateforme, les tags personnalises ou la version Go.
//go:build linux && amd64
// +build linux,amd64 (syntaxe ancienne, maintenue pour compatibilite)
// Ce fichier n'est compile que sur Linux AMD64
package main
import "fmt"
func platformInfo() string {
return "Linux AMD64"
}
//go:build !cgo
// Ce fichier est utilise quand CGO est desactive
package sqlite
import _ "modernc.org/sqlite" // Pure Go SQLite
# Compiler avec un tag personnalise
go build -tags production .
# Tester sans un tag
go test -tags '!integration' ./...
Ldflags — injection de version à la compilation¶
// version.go
package main
import "fmt"
// Ces variables sont injectees par -ldflags lors du build
var (
Version = "dev"
BuildTime = "unknown"
GitCommit = "unknown"
)
func printVersion() {
fmt.Printf("Version: %s\n", Version)
fmt.Printf("Build time: %s\n", BuildTime)
fmt.Printf("Git commit: %s\n", GitCommit)
}
# Injection des valeurs au moment du build
go build \
-ldflags="-X main.Version=1.2.3 \
-X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
-X main.GitCommit=$(git rev-parse --short HEAD)" \
-o myapp .
Dockerfile multi-stage¶
Le pattern multi-stage produit une image finale minimale contenant uniquement le binaire.
# Stage 1 : compilation
FROM golang:1.22-alpine AS builder
WORKDIR /app
# Copier les fichiers de modules d'abord pour le cache Docker
COPY go.mod go.sum ./
RUN go mod download
# Copier le code source et compiler
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w" \
-o /app/server .
# Stage 2 : image finale minimale (scratch = vide)
FROM scratch
# Certificats TLS necessaires pour les appels HTTPS
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# Copier uniquement le binaire compile
COPY --from=builder /app/server /server
EXPOSE 8080
ENTRYPOINT ["/server"]
# Build et lancement
docker build -t myapp:latest .
docker run -p 8080:8080 myapp:latest
# Taille de l'image finale (exemple)
docker image ls myapp
# myapp latest a1b2c3d4e5f6 2 minutes ago 8.2MB
distroless comme alternative a scratch
gcr.io/distroless/static-debian12 est plus sécurisé que scratch car il inclut les certificats TLS, le fichier /etc/passwd et les timezone data, sans shell ni package manager.
GoReleaser¶
GoReleaser automatise la création de releases multi-plateformes, la génération de checksums et la publication sur GitHub Releases.
# .goreleaser.yaml
version: 2
builds:
- id: myapp
main: .
binary: myapp
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- amd64
- arm64
ldflags:
- -s -w
- -X main.Version={{.Version}}
- -X main.GitCommit={{.Commit}}
archives:
- format: tar.gz
format_overrides:
- goos: windows
format: zip
checksum:
name_template: "checksums.txt"
release:
github:
owner: mon-org
name: mon-projet
# Test local sans publier
goreleaser release --snapshot --clean
# Release officielle (depuis CI, apres git tag)
goreleaser release --clean
Pipeline CI/CD — GitHub Actions¶
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
cache: true # Cache automatique du module cache
- name: Verifier le formatage
run: test -z "$(gofmt -l .)"
- name: Linter
uses: golangci/golangci-lint-action@v6
with:
version: latest
- name: Tests avec couverture
run: go test -race -coverprofile=coverage.out -covermode=atomic ./...
- name: Upload couverture
uses: codecov/codecov-action@v4
with:
file: ./coverage.out
release:
needs: test
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # GoReleaser a besoin de l'historique complet
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- uses: goreleaser/goreleaser-action@v6
with:
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}