Aller au contenu
Développement medium

Compiler et versionner un binaire Go

8 min de lecture

Go

L'un des grands atouts de Go est de produire un binaire unique, sans dépendance à installer chez l'utilisateur. Ce guide montre comment en tirer parti pour distribuer un outil : compiler avec go build, injecter la version et le commit dans le binaire via -ldflags -X, réduire sa taille, et le cross-compiler pour Linux, macOS et Windows depuis une seule machine. Public visé : intermédiaire en Go. Tous les exemples sont testés avec Go 1.26.

  • Compiler un binaire avec go build et l'installer avec go install.
  • Injecter la version et le commit avec -ldflags -X.
  • Réduire la taille du binaire avec -s -w.
  • Cross-compiler pour plusieurs OS avec GOOS et GOARCH.

go build compile le paquet courant en un exécutable. Prenons un petit outil qui affiche sa version :

main.go
package main
import "fmt"
// Ces variables sont injectées au build via -ldflags -X.
var (
version = "dev"
commit = "none"
)
func main() {
fmt.Printf("monoutil %s (commit %s)\n", version, commit)
}
Fenêtre de terminal
go build -o monoutil .
./monoutil
monoutil dev (commit none)

Les valeurs par défaut (dev, none) s'affichent : on ne les a pas encore renseignées. go install fait la même compilation mais place le binaire dans $GOBIN (ou $GOPATH/bin), pratique pour un outil que vous utilisez localement.

Coder la version en dur dans le fichier obligerait à modifier le code à chaque release. Go propose mieux : -ldflags -X écrit une valeur dans une variable de type chaîne au moment du link, sans toucher au source.

Fenêtre de terminal
go build -ldflags "-X main.version=1.2.0 -X main.commit=a1b2c3d" -o monoutil .
./monoutil
monoutil 1.2.0 (commit a1b2c3d)

La syntaxe est -X chemin/du/paquet.Variable=valeur. Ici main.version cible la variable version du paquet main. En pratique, on alimente ces valeurs depuis Git :

Fenêtre de terminal
go build -ldflags "-X main.version=$(git describe --tags --always) \
-X main.commit=$(git rev-parse --short HEAD)" -o monoutil .

Par défaut, le binaire embarque la table des symboles et les informations de debug (DWARF), utiles pour le débogage mais superflues en distribution. Les options -s (retire la table des symboles) et -w (retire le DWARF) allègent le binaire.

Fenêtre de terminal
go build -o monoutil-big .
go build -ldflags "-s -w" -o monoutil-small .
ls -la monoutil-big monoutil-small
2409844 monoutil-big
1597602 monoutil-small

Le gain est net : environ 2,3 Mo tombent à 1,5 Mo, soit un tiers de moins, ici sans dépendance externe. En contrepartie, vous perdez le débogage symbolique sur ce binaire ; gardez une version non allégée si vous devez analyser un crash.

C'est là que Go brille : depuis une seule machine, vous compilez pour n'importe quelle cible en fixant deux variables d'environnement, GOOS (système) et GOARCH (architecture). Aucun toolchain croisé à installer.

Fenêtre de terminal
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o dist/monoutil-linux-amd64 .
GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -o dist/monoutil-linux-arm64 .
GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -o dist/monoutil-darwin-arm64 .
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -o dist/monoutil-windows-amd64.exe .
ls -la dist/
1657858 monoutil-darwin-arm64
1597602 monoutil-linux-amd64
1573026 monoutil-linux-arm64
1680384 monoutil-windows-amd64.exe

Les quatre cibles sont produites en quelques secondes. Les couples GOOS/GOARCH valides sont listés par go tool dist list. Notez le suffixe .exe ajouté à la main pour Windows.

Deux compilations du même code devraient produire le même binaire. Par défaut, Go inscrit les chemins absolus de votre machine dans le binaire, ce qui casse cette propriété. L'option -trimpath remplace ces chemins par des références relatives au module :

Fenêtre de terminal
CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=1.2.0" -o monoutil .

Combinée à des versions de dépendances figées par go.sum et à une version de Go fixée, -trimpath rapproche votre build de la reproductibilité : un audit peut recompiler et vérifier qu'il obtient le même binaire que celui distribué.

  • Injectez la version depuis Git (git describe) plutôt que de la coder en dur.
  • Allégez avec -s -w les binaires distribués, gardez une version complète pour le débogage.
  • Ajoutez -trimpath sur les builds de release pour ne pas fuiter vos chemins locaux.
  • Utilisez CGO_ENABLED=0 pour un binaire statique destiné à un conteneur minimal.
  • Scriptez le build (Makefile ou tâche CI) : la commande complète est trop longue pour être retapée à la main.
  • go build produit un binaire autonome, sans dépendance à installer.
  • -ldflags -X injecte version et commit dans des variables string au link.
  • -s -w réduit la taille d'environ un tiers en retirant symboles et debug.
  • GOOS/GOARCH cross-compilent pour tout OS depuis une seule machine.
  • -trimpath et CGO_ENABLED=0 rapprochent le build de la reproductibilité et du statique.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn