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, 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:
[Hard Coded].[Hard Coded].[Hard Coded]
1.2.3
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:
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:
[Hard Coded].[Hard Coded].[CI Build Number]
1.2.64 (o pipeline já rodou 64x)
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:
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:
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:
dotnet tool install --global minver-cli --version 2.5.0
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, que está atualmente na versão v1.1.0, temos essa resposta:
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
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 comoalpha
,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, publiquei essa versão no NuGet. 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, 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:
<PackageReference Include="MinVer" Version="2.5.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
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 é 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 *.*.*
.
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
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.
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.
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.