Versionamento Semantico (o SemVer in breve) è uno schema di versionamento del software che specifica numeri di versione a tre parti nella forma <principale>.<secondario>.<correzione>, come 1.0.2, con un suffisso optional di prerilascio nella forma -<prerilascio>, come in 1.0.2-beta.

Il SemVer è forse lo schema di versionamento più ampiamente utilizzato oggi. Ad esempio, sia Nuget che npm lo raccomandano e lo supportano, e VS Code lo utilizza anche.

Nella maggior parte dei repository GitHub che utilizzano la funzione GitHub Releases per pubblicare rilasci, vedresti un numero di versione SemVer nel badge dell’ultimo rilascio sulla pagina principale, come si può vedere nella schermata qui sotto:

Spesso devo impostare un numero di versione SemVer quando costruisco progetti API ASP.NET Core, e poi leggerlo o riportarlo durante l’esecuzione.

Per esempio, se costruisco un’API minimale con la sua versione impostata su 1.0.2-beta, questa sarebbe riportata da un endpoint /version esposto dall’API, come mostrato nella schermata sottostante da Hoppscotch (questo è uno strumento simile a Postman con la convenienza che funziona nel browser):

Controllare che la versione riportata dai servizi distribuiti, come le applicazioni web e le API, sia corretta è una parte cruciale del mio pipeline di CD e uno dei test di fumo che utilizzo per determinare se un deployment è riuscito.

Una lieve complicazione quando si imposta un numero di versione SemVer sugli assembly .NET è che .NET originariamente utilizzava numeri di versione a quattro parti come 1.0.3.212 e gli assembly hanno ancora questi (l’assembly è il termine .NET per unità di codice compilate a bytecode .NET, il più tipico dei quali sono i dll e gli exe).

L’altro è che .NET non ha una, ma molte versioni leggermente diverse, numeri di versione che sono presenti nello stesso assembly.

In questo articolo, ti mostrerò come aggirare queste particolarità e applicare un numero di versione SemVer su un assembly .NET durante la build. Ossia, su un compilato .exe o .dll, e come leggerlo in esecuzione.

Tabella dei Contenuti

Struttura di un Numero di Versione SemVer

Considera un numero di versione SemVer come 1.0.2 o 1.0.2-beta. Ha la forma <maggiore>.<minore>.<correzione><prerilascio>

Questo è cosa significano i vari componenti:

Il componente <major> della numerazione della versione dovrebbe essere incrementato solo se la nuova releasedovesse interrompere una release esistente (la più recente).

In caso di un’applicazione UI, i clienti possono essere intesi come clienti umani. Quindi, se la nuova release dovesse interrompere gli asset esistenti degli utenti, come le definizioni dei flussi di lavoro, ciò richiederebbe l’incremento della numerazione della versione principale. In questo evento, se la release precedente era 1.0.2, la nuova release dovrebbe essere 2.0.0 (tutti i componenti inferiori della numerazione della versione verranno ripristinati).

In caso di una libreria, come un pacchetto di libreria su Nuget o NPM, i clienti saranno altri codici. Quindi, se la nuova release dovesse interrompere il codice client esistente, ossia non fosse retrocompatibile con la sua versione precedente, allora nuovamente il componente <major> sarebbe incrementato.

<minor> viene incrementato se sono state aggiunte nuove funzionalità ma la nuova versione è ancora retrocompatibile. Quindi, da 1.0.2 si passerebbe a 1.1.0.

<patch> viene incrementato quando una nuova release deve essere rilasciata nonostante non ci siano cambiamenti di rottura e non siano state aggiunte nuove funzionalità. Questo potrebbe accadere, ad esempio, se fosse necessario rilasciare una correzione di bug.

-<prerelease> è un suffisso opzionale. È tipicamente aggiunto a una numerazione a tre parti quando il software deve essere reso disponibile durante le fasi di test prerelease, come alpha e beta. Ad esempio, prima di rilasciare generalmente la versione 1.0.2 del tuo software, puoi renderla disponibile ai tuoi beta tester come 1.0.2-beta.

Il componente <prerelease> può essere letteralmente qualsiasi stringa a vostra scelta e l’unica esigenza è che sia un identificatore alfanumerico come beta o 12 o alpha2 (nessun carattere diverso da numeri o lettere dell’alfabeto) o più identificatori alfanumerici separati da un punto(.) ad esempio development.version.

I molteplici numeri di versione di un’assembly .NET

COME spiegato nell’articolo di Andrew Lock su versioning .NET, un’assembly .NET ha non uno ma più numeri di versione:

  • AssemblyVersion: Questo è un numero di versione a quattro parti, ad esempio, 1.0.2.0. Viene utilizzato dal runtime durante il caricamento delle assembly collegate.

  • FileVersion: Questo è il numero di versione riportato per un .dll file in Windows File Explorer quando fate clic con il pulsante destro dell’assembly e selezionate Proprietà.

  • VersioneInformativa: Un altro numero di versione e, come FileVersion, può essere visualizzato nella finestra di dialogo Proprietà se fai clic con il tasto destro sull’assembly in Windows e selezioni Proprietà. Questo può contenere stringhe e non solo interi e punti come AssemblyVersion e FileVersion sono limitati.

  • VersioneDelPacchetto: Se il progetto è un pacchetto Nuget, questa sarà la numero di versione del pacchetto di cui fa parte l’assembly.

Tutti questi numeri di versione vengono emessi nell’assembly durante la compilazione come metadati. Puoi vederli se ispezioni l’assembly con JetBrains dotPeek (gratuito) o Red gate Reflector (non gratuito) o simili.

FileVersion e VersioneInformativa possono anche essere visti nella scheda Dettagli della finestra di dialogo Proprietà che appare quando fai clic con il tasto destro sul file dell’assembly in Windows File Explorer e selezioni Proprietà:

Nella schermata above, “Product version” è la didascalia per InformationalVersion mentre “File version” è la didascalia per FIleVersion.

Delle quattro tipi di numeri di versione descritte above, solo le prime tre si applicano a qualsiasi assembly (ovvero indipendentemente dal fatto che l’assembly faccia parte di un pacchetto Nuget).

Di queste tre, AssemblyVersion aggiunge sempre un 0 nella quarta posizione se si tenta di impostare una versione SemVer che ha solo tre numeri (più un eventuale suffisso prerelease). Per esempio, se si tenta di impostare una versione SemVer di 1.0.2-beta durante la costruzione e poi si legge il valore di AssemblyVersion all’ runtime nell’assembly, questo sarà 1.0.2.0.

FileVersion fa lo stesso, come mostrato nella schermata above.

InformationalVersion è l’unico numero di versione che verrebbe impostato esattamente alla versione del server che si imposta durante la costruzione, come mostra la schermata above.

Quindi, InformationalVersion è la versione che dovrebbe essere letta all’ runtime per recuperare la versione SemVer dell’assembly.

Come impostare un numero di versione SemVer

Ci sono due cose che devi fare per impostare un numero di versione SemVer su un’assembly durante la costruzione.

Prima, in un elemento <PropertyGroup> nel file csproj del progetto, aggiungi l’elemento <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>:

<PropertyGroup>
 ...
 <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> 
</PropertyGroup>

Come descritto in questa problematica, questo garantisce che InformationalVersion sia impostato esattamente sul numero di versione SemVer specificato e non abbia un +<hash code> aggiunto alla fine.

Secondo, passare il numero di versione come valore della proprietà Version trasmessa al comando dotnet build ad esempio:

dotnet build --configuration Release -p Version=1.0.2-beta

Questo imposterà InformationalVersion nell’assembly compilato (.exe o .dll) a 1.0.2-beta.

Incidentalmente, imposterebbe anche AssemblyVersion e FileVersion (verrebbe aggiunto un extra 0 alla fine di 1.0.2) ma non ci interessano.

Nota che invece di passare l’argomento Version sulla riga di comando, è possibile impostare la proprietà MS Build <Version>1.0.2-beta</Version> in un elemento <PropertyGroup> nel file csproj. Tuttavia, passare un valore del parametro Version a dotnet build è più semplice perché il file csproj non deve essere modificato ogni volta che il numero di versione viene incrementato. Questo è utile nelle pipeline CD. Inoltre, per impostazione predefinita, i file csproj non hanno alcuna proprietà relativa alla versionatura.

Come Leggere la Versione SemVer di un’Assembly in Tempo di Esecuzione

Il codice che legge InformationalVersion in tempo di esecuzione è come segue:

string? version = Assembly.GetEntryAssembly()?.
  GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.
  InformationalVersion;

Nei miei API minimali, per aggiungere un endpoint /version come mostrato nella sezione Introduzione sopra, inserisco il frammento sopra in Program.cs, poi aggiungo il frammento seguente immediatamente dopo. Nota che l’intero contenuto dovrebbe apparire prima che builder.Build() venga chiamato:

//questo oggetto di tipo anonimo sarà 
//serializzato come JSON nel corpo della risposta
//quando restituito da un handler
var objVersion = new { Version = version ?? "" };

//ALTRO CODICE
//var app = builder.Build()

Dopo che builder.Build() è chiamato, creo l’handler per l’endpoint /version:

app.MapGet("/version", () => objVersion);

Quando eseguo il progetto API e chiamo l’endpoint /version, ottengo il numero di versione indietro in un oggetto JSON nel corpo della risposta HTTP:

{
  "version": "1.0.2-beta"
}

Questo è ciò che lo screenshot di Hoppscotch nell’Introduzione mostrava.

Conclusione

Questo articolo ti ha mostrato come impostare un numero di versione SemVer nei tuoi assembly .NET, librerie o app.

Ha anche mostrato come leggere il numero di versione durante l’esecuzione.