Versionamento semântico simplificado com MinVer
Neste artigo, mostro como usar o MinVer para versionar os assemblies de projetos .NET. Para ilustrar, apresento como estruturei o fluxo de publicação da minha biblioteca open-source ForeverFactory com versionamento baseado no MinVer.

Dyego Maas
6 min de leitura
Erro ao carregar conteúdo:
147:68: Could not parse expression with acorn
Conteúdo bruto
import { Code } from '../../../components/mdx'
**[MinVer](https://github.com/adamralph/minver) é uma ferramenta extremamente útil para facilitar o versionamento semântico de assemblies .NET usando Git tags.**
O versionamento semântico, de acordo com o [SemVer 2.0.0](https://semver.org/spec/v2.0.0.html), segue a seguinte estrutura: `[Major].[Minor].[Patch]`. Resumindo:
- Quando a **Major** sobe, isso indica que ouve uma grande mudança, e possível quebra de compatibilidade com as versões anteriores
- Quando a **Minor** sobe, isso indica adição de funcionalidade, sem perda de compatibilidade
- Quando o **Patch** sobe, isso indica correções ou melhorias
## Versionamento semântico mal feito
Tradicionalmente, a forma como controlamos a versão de um assembly é através da tag `<PackageVersion>1.2.3</PackageVersion>` no arquivo *.csproj* de um projeto (ou nos arquivos `AssemblyInfo.cs` em projetos mais antigos).
No exemplo `<PackageVersion>1.2.3</PackageVersion>`, estamos usando a versão fixa no código:
<Code lang="markdown">
{`[Hard Coded].[Hard Coded].[Hard Coded]
1.2.3`}
</Code>
Mas se esse processo for feito sempre manualmente, as chances são grandes de esquecermos de trocar a versão em alguns dos assemblies do projeto, e esses assemblies serão compilados com a versão errada, ou até mesmo, ficarem eternamente na versão 1.0.0:
<figure>
<img src="/img/posts/artigo-versionamento-semantico-com-minver/versao_1.0.0.0.jpg" alt="Detalhes do arquivo ForeverFactory.dll, com a versão 1.0.0.0" />
</figure>
Uma alternativa bastante comum e fácil de implementar, é ajustar a Major e Minor manualmente, e subir o Patch de acordo com o *número do build* do pipeline de integração contínua:
<Code lang="markdown">
{`[Hard Coded].[Hard Coded].[CI Build Number]
1.2.64 (o pipeline já rodou 64x)`}
</Code>
> Esse processo pode funcionar no começo de um projeto, mas não é sustentável a longo prazo. *O principal motivo é que não é possível identificar facilmente a versão do programa através da árvore de commits do Git.*
**E é aí que entram as *tags* do Git.** Com elas, é fácil identificar em qual versão um software se encontra:
<figure>
<img src="/img/posts/artigo-versionamento-semantico-com-minver/git-tree_1.1.0.jpg" alt="Árvore de commits do repositório Git do repositório ForeverFactory, com a tag versão v1.1.0 facilmente visível" />
</figure>
Há ainda o risco de as tags do git não refletirem a versão dos assemblies nos commits que elas representam. Um exemplo simples dessa situação seria um commit com a tag `1.2.0` em que a versão do projeto no código continua com a versão `1.0.0`. Se fizermos o build do código deste exato commit, a versão do assembly será `1.0.0`, e não `1.2.0`, como esperaríamos a partir da tag de versão.
Essa situação pode ser facilmente corrigida com o auxílio do **MinVer**.
## Como o MinVer pode ajudar
Em primeiro lugar, o uso do MinVer é muito simples. Basta referenciá-lo no projeto, e sem necessidade de mais configurações, **a versão dos assemblies passará a ser baseada na última tag de versão encontrada no histórico do Git.**
Por padrão, o MinVer utiliza as seguintes configurações, satisfazendo o [guia oficial de versionamento para bibliotecas open-source .NET](https://docs.microsoft.com/en-ca/dotnet/standard/library-guidance/versioning#version-numbers):
| Property | Value |
|-------------------|-----------------------------------------------|
| `AssemblyVersion` | `{MinVerMajor}.0.0.0` |
| `FileVersion` | `{MinVerMajor}.{MinVerMinor}.{MinVerPatch}.0` |
| `PackageVersion` | `{MinVerVersion}` |
| `Version` | `{MinVerVersion}` |
Essas configurações podem ser editadas livremente no arquivo do projeto.
### MinVer CLI
Também existe uma interface de linha de comando que pode nos auxiliar com algumas configurações. Para instalá-la, basta rodar o comando abaixo:
<Code lang="bash">
{`dotnet tool install --global minver-cli --version 2.5.0`}
</Code>
Com a CLI instalada, podemos pedir para o MinVer calcular a próxima versão semântica informando a parte do que queremos incrementar, podendo ser *major*, *minor* ou *patch* (padrão).
Por exemplo, ao rodar o comando `minver --auto-increment minor --tag-prefix=v` na raiz do repositório da biblioteca [ForeverFactory](https://github.com/DyegoMaas/ForeverFactory), que está atualmente na versão *v1.1.0*, temos essa resposta:
<Code lang="markdown">
{`MinVer: Using { Commit: 3450a1e, Tag: 'v1.1.0', Version: 1.1.0, Height: 2 }.
MinVer: Calculated version 1.2.0-alpha.0.2.
1.2.0-alpha.0.2`}
</Code>
Usei o parâmetro `--tag-prefix=v` apenas porque decidi prefixar todas as tags de versão com o caractere **v**, como em `v1.1.0`.
Olhando para o resultado, vale notar que além da versão que foi incrementada para `1.2.0`, o MinVer também traz uma segunda parte `alpha.0.2`. Vamos decompô-la para entendermos o que significa:
- `alpha` é valor padrão da fase de pré-release. Este valor pode ser trocado com o parâmetro `-d|--default-pre-release-phase <PHASE>`, onde fase pode ser algo como `alpha`, `beta`, `preview`, `rc`, etc;
- O segundo valor, `0`, indica o intervalo de versões de pré-releases lançadas até agora. Este valor vai subir só depois de publicarmos uma versão de pré-release;
- O último valor, `2`, é uma métrica chamada *altura*, calculada em número de commits desde o último release.
> Uma premissa importante do MinVer, é que o tageamento de versão é feito antes da publicação. Logo, a expectativa é que a versão compilada será baseada na tag de versão semântica mais recente encontrada no histórico do Git.
## A estratégia de versionamento da ForeverFactory
Logo após concluir a primeira versão da biblioteca [ForeverFactory](https://github.com/DyegoMaas/ForeverFactory), publiquei essa versão no [NuGet](https://www.nuget.org/packages/ForeverFactory/). O versionamento era 100% manual através da propriedade `<PackageVersion>`.
Mas logo depois, encontrei alguns problemas, e precisei lançar algumas versões corretivas. Depois de meia dúzia de versões corretivas, ficou claro que versionar dessa forma não seria prático, nem o processo seria natural.
Consultei então o repositório do [MediatR](https://github.com/jbogard/MediatR), uma das minhas bibliotecas preferidas para .NET, a fim de verificar como é feito o versionamento. E foi aí que me deparei com o **MinVer**.
### Usando o MinVer no projeto
Essa é a parte fácil. Basta adicionar uma referência NuGet para o MinVer com o comando `dotnet add package MinVer`.
Feito isso, para garantir que o MinVer vai funcionar corretamente, é importante conferir no arquivo `.csproj` se a referência inclui a propriedade `PrivateAssets="All"`. Deve ficar assim:
<Code lang="xml">
{`<PackageReference Include="MinVer" Version="2.5.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>`}
</Code>
A partir desse momento, o MinVer vai se encarregar de executar um processo conhecido como *assembly patching*, e cada build utilizará a versão da última tag de versão. Simples assim.
**Obs.: se não houver tag nenhuma, a versão provavelmente será `0.0.0-alpha.0`.**
No meu caso, como já estava tageando as versões com o prefixo `v`, adicionei também a propriedade `<MinVerTagPrefix>v</MinVerTagPrefix>` nas propriedades do projeto, para o MinVer considerar minhas tags de versão.
### Como fazer releases baseado em tags
A abordagem de versionamento do repositório [jbogard/MediatR](https://github.com/jbogard/MediatR) é bem minimalista, e foi muito fácil de adotá-la.
Se conferirmos o *workflow do GitHub* abaixo, veremos que o processo de release é disparado a partir do *push* de novas tags de versão, que naturalmente seguem o padrão `*.*.*`.
<Code lang="yaml">
{`name: Release
on:
push:
tags:
- '*.*.*'
jobs:
release:
strategy:
fail-fast: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup dotnet 5.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: 5.0.x
- name: Clean
run: dotnet clean -c Release
- name: Build
run: dotnet build -c Release
- name: Test
run: dotnet test -c Release -r nuget-package --no-build -l trx --verbosity=normal
- name: Pack
run: dotnet pack src/ForeverFactory/ForeverFactory.csproj -c Release --no-build -o nuget-package
- name: Publish to Nuget.org
run: dotnet nuget push nuget-package/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json`}
</Code>
Assim, quando chegar a hora de liberar uma nova versão, a única coisa que preciso fazer é adicionar uma tag de versão no commit escolhido, e a mágica acontece: a biblioteca é compilada, testada, empacotada e publicada no Nuget, com a versão da tag que disparou o pipeline.
Você pode conferir o resultado lá no Nuget [neste link](https://nuget.org/packages/ForeverFactory/).
## Pensamentos finais
O MinVer é perfeito para projetos pequenos e times reduzidos. Ele elimina toda burocracia que envolve o versionamento de um projeto, ao mesmo tempo que nos ajuda a seguir as melhores práticas de versionamento semântico.
Mas para workflows mais complexos, produtos maiores e equipes grandes, com múltiplos squads, é mais apropriado considerar ferramentas mais robustas, como o [GitVersion](https://github.com/GitTools/GitVersion).
Espero que este post se mostre útil para você. Para mim, ele já vem funcionando, e trouxe simplicidade e consistência ao versionamento da biblioteca. Certamente usarei esse mesmo fluxo em futuros projetos também.