Pourquoi le versionnage manuel est un calvaire pour chaque développeur Go
\nAvez-vous déjà pensé : « Quelle version est actuellement en production ? » ou passé une heure à comprendre quel commit a causé un bug dans un binaire compilé il y a un mois ? Si oui, vous êtes au bon endroit. La question « Application auto build versioning » est l'une des plus populaires sur Stack Overflow (des milliers de vues et des dizaines de solutions). Et ce n'est pas pour rien : dans le développement moderne, où les pipelines CI/CD lancent des builds toutes les 10 minutes, l'attribution manuelle des versions est une voie vers le chaos.
\nDans cet article, nous allons détailler le versionnage automatique des builds en Go de A à Z. Vous apprendrez à mettre en place un système qui attribue automatiquement le numéro de version, le hash du commit, la date de build et même un indicateur de dépôt « sale ». Le code fonctionnera aussi bien en développement local que dans GitHub Actions, GitLab CI ou Jenkins.
\n\nLe problème : qu'est-ce qui ne va pas avec le version.go manuel ?
\nDe nombreux développeurs débutants créent un fichier version.go et écrivent :
package main\n\nvar Version = "1.0.0"\nEt cela fonctionne jusqu'au premier commit. Ensuite, ça commence :
\n- \n
- « J'ai oublié de mettre à jour la version avant le build » \n
- « En production, une version, dans le code, une autre » \n
- « Quel commit correspond à ce build ? » \n
Le problème est que la version est une métadonnée de build, pas une partie du code source. Elle doit être générée automatiquement lors de la compilation.
\n\nLa solution : ldflags, l'arme secrète de Go
\nLe compilateur Go permet d'« injecter » des valeurs de variables lors du build via le flag -ldflags. C'est ce qu'on appelle l'auto build versioning dans sa forme classique. Nous ne stockons pas la version dans le code – nous la transmettons au compilateur.
Étape 1 : Définir la structure de version
\nCréons un package version avec des variables qui seront renseignées lors du build :
// version/version.go\npackage version\n\nvar (\n // Version — version sémantique (définie manuellement ou via un tag git)\n Version = "dev"\n\n // Commit — hash court du commit\n Commit = "none"\n\n // BuildTime — heure du build au format ISO 8601\n BuildTime = "unknown"\n\n // IsDirty — indicateur que le dépôt contient des modifications non commitées\n IsDirty = "false"\n)\n\nfunc String() string {\n return "v" + Version + " (commit: " + Commit + ", built: " + BuildTime + ")"\n}\n\nÉtape 2 : Builder avec ldflags
\nMaintenant, la partie intéressante. Au lieu de go build, nous allons utiliser un script ou un Makefile qui calcule les valeurs nécessaires et les transmet au compilateur :
#!/bin/bash\n# build.sh\n\nVERSION=$(git describe --tags --always --dirty 2>/dev/null || echo "dev")\nCOMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "none")\nBUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")\nIS_DIRTY=$(git diff --quiet || echo "true")\n\ngo build -ldflags="\\\n -X 'main/version.Version=$VERSION' \\\n -X 'main/version.Commit=$COMMIT' \\\n -X 'main/version.BuildTime=$BUILD_TIME' \\\n -X 'main/version.IsDirty=$IS_DIRTY'" \\\n -o myapp .\nImportant : Le chemin main/version.Version dépend de la structure de votre projet. Si le package version se trouve à la racine – écrivez main/version.Version. S'il est dans un répertoire séparé – github.com/votre-username/projet/version.Version.
Étape 3 : Utiliser dans l'application
\nMaintenant, n'importe où dans le programme, vous pouvez afficher la version :
\n// main.go\npackage main\n\nimport (\n "fmt"\n "main/version"\n)\n\nfunc main() {\n fmt.Println("App version:", version.String())\n fmt.Println("Is dirty repo:", version.IsDirty)\n}\nCompilez et exécutez :
\n$ ./build.sh\n$ ./myapp\nApp version: v1.2.3 (commit: a1b2c3d, built: 2024-03-15T10:30:00Z)\nIs dirty repo: false\n\n