Aller au contenu

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 }}