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.

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.