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.

Versionamento semântico simplificado com MinVer
5 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. 

Gostou do artigo? Compartilhe nas redes sociais ou deixe um comentário abaixo!

Comentários