Por que o versionamento manual é uma dor para todo desenvolvedor Go
\nVocê já se pegou pensando: "Qual versão está rodando em produção agora?" ou perdeu uma hora para descobrir qual commit causou um bug em um binário compilado há um mês? Se sim, você está no lugar certo. A pergunta "Application auto build versioning" é uma das mais populares no Stack Overflow (milhares de visualizações e dezenas de soluções). E não é à toa: no desenvolvimento moderno, onde pipelines CI/CD geram builds a cada 10 minutos, definir versões manualmente é o caminho para o caos.
\nNeste artigo, vamos abordar o versionamento automático de builds em Go do início ao fim. Você aprenderá como implementar um sistema que atribui automaticamente o número da versão, o hash do commit, a data do build e até mesmo um sinalizador de repositório "sujo". O código funcionará tanto no desenvolvimento local quanto no GitHub Actions, GitLab CI ou Jenkins.
\n\nO problema: o que há de errado com o version.go manual?
\nMuitos desenvolvedores iniciantes criam um arquivo version.go e escrevem:
package main\n\nvar Version = "1.0.0"\nE isso funciona até o primeiro commit. Depois, começa:
\n- \n
- "Esqueci de atualizar a versão antes do build" \n
- "Em produção está uma versão, no código está outra" \n
- "Qual commit corresponde a este build?" \n
O problema é que a versão são metadados do build, e não parte do código-fonte. Ela deve ser gerada automaticamente na etapa de compilação.
\n\nA solução: ldflags — a arma secreta do Go
\nO compilador Go permite "injetar" valores de variáveis durante o build através da flag -ldflags. Isso é o auto build versioning em sua forma clássica. Não armazenamos a versão no código — nós a passamos para o compilador.
Passo 1: Definindo a estrutura da versão
\nVamos criar o pacote version com variáveis que serão preenchidas na etapa de build:
// version/version.go\npackage version\n\nvar (\n // Version — versão semântica (definida manualmente ou via git tag)\n Version = "dev"\n\n // Commit — hash curto do commit\n Commit = "none"\n\n // BuildTime — hora do build no formato ISO 8601\n BuildTime = "unknown"\n\n // IsDirty — sinalizador de que o repositório contém alterações não commitadas\n IsDirty = "false"\n)\n\nfunc String() string {\n return "v" + Version + " (commit: " + Commit + ", built: " + BuildTime + ")"\n}\n\nPasso 2: Compilando com ldflags
\nAgora a parte interessante. Em vez de go build, usaremos um script ou Makefile que calcula os valores necessários e os passa para o compilador:
#!/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 .\nImportante: O caminho main/version.Version depende da estrutura do seu projeto. Se o pacote version estiver na raiz — escreva main/version.Version. Se estiver em um diretório separado — github.com/seu-usuario/projeto/version.Version.
Passo 3: Usando no aplicativo
\nAgora, em qualquer lugar do programa, você pode exibir a versão:
\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}\nCompile e execute:
\n$ ./build.sh\n$ ./myapp\nApp version: v1.2.3 (commit: a1b2c3d, built: 2024-03-15T10:30:00Z)\nIs dirty repo: false\n\n