Automatické verzování AssemblyVersion a AssemblyFileVersion v .NET Core (csproj)

Jan Holan       14.04.2017             10890 zobrazení

V .NET Core projektu (v novém formátu csproj) již nelze v AssemblyVersion atributu použít hvězdičku (*) (např: AssemblyVersion("0.9.*")) pro automatické generování AssemblyVersion a AssemblyFileVersion - Chyba CS7034: The specified version string does not conform to the required format - major[.minor[.build[.revision]]]. Navíc atributy se nyní místo do souboru AssemblyInfo.cs ukládají primárně přímo do souboru .csproj jako MSBuild vlastnosti (MSBuild pak na pozadí soubor AssemblyInfo.cs generuje).

Hledal jsem způsob, jak číslo verze opět automaticky generovat (například proto, že číslo verze se aplikací zapisuje do různých trace a error interních logů). K tomuto tématu jsem našel tyto dva příspěvky na stackoverflow.com:
http://stackoverflow.com/questions/43274254/setting-the-version-number-for-net-core-projects-csproj-not-json-projects/43280282#43280282
http://stackoverflow.com/questions/43019832/auto-versioning-in-visual-studio-2017-net-core/43194610#43194610
Bohužel ani v jednom z nich jsem ale nenašel řešení, které by ně uspokojilo, protože potřebuji číslo verze generovat nejen při vytváření automatizovaného buildu na Build serveru, ale například i při ručním buildu nebo Publish z Visual Studia.

Neúspěšně jsem si prošel pokusem volat vlastní .NET Core tool, který mění soubory .csproj. Toto nefunguje, protože pří buildu je soubor .csproj hned nahrán do paměti a jakákoliv pozdější jeho modifikace se již neprojeví. Po zhruba dvou dnech jsem došel k následujícímu řešení.

Hodnoty pro AssemblyFileVersion a AssemblyVersion budeme mít zadány přímo v projektovém .csproj souboru a nebudeme pro ně používat AssemblyInfo.cs – v souboru .csproj je to MSBuild vlastnost FileVersion (z ní MSBuild generuje AssemblyFileVersionAttribute) a vlastnost AssemblyVersion (generuje AssemblyVersionAttribute). Pak v rámci MSBuild procesu ke změně čísla verze nebudeme měnit žádné soubory, ale pouze přenastavíme hodnoty těchto vlastností FileVersion a AssemblyVersion (Případně můžeme změnit jen některou z těchto vlastností, podle potřeby).

Vlastní MSBuild task

Vytvoříme vlastní MSBuild task, který bude pouze vracet generované číslo verze. Ke generování používá stejný algoritmus, jako bylo chování hvězdičky (*) v AssemblyVersion tj. počet dnů od 1.1.2000 (jako build číslo) a půlka počtu sekund od půlnoci (jako revision číslo).

Zdrojový kód MSBuild tasku je následující:

public class GetCurrentBuildVersion : Task
{
    [Output]
    public string Version { get; set; }

    public string BaseVersion { get; set; }

    public override bool Execute()
    {
        var originalVersion = System.Version.Parse(this.BaseVersion ?? "1.0.0");

        this.Version = GetCurrentBuildVersionString(originalVersion);

        return true;
    }

    private static string GetCurrentBuildVersionString(Version baseVersion)
    {
        DateTime d = DateTime.Now;
        return new Version(baseVersion.Major, baseVersion.Minor,
            (DateTime.Today - new DateTime(2000, 1, 1)).Days,
            ((int)new TimeSpan(d.Hour, d.Minute, d.Second).TotalSeconds) / 2).ToString();
    }
}

Task je ve třídě GetCurrentBuildVersion, která dědí ze třídy Microsoft.Build.Utilities.Task (z NuGet balíčku Microsoft.Build.Utilities.Core). Vstupem tasku je vlastnost BaseVersion (nepovinná), ze které se odvodí Major a Minor čísla verze. Výstupem pak je plné číslo verze ve vlastnosti Version.

Třídu umístíme například do .NET Standard 1.3 class library (ale můžeme použít i jiný druh projektu, který se kompiluje do dll). .csproj soubor vypadá takto:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard1.3</TargetFramework>
    <AssemblyName>DC.Build.Tasks</AssemblyName>
    <RootNamespace>DC.Build.Tasks</RootNamespace>
    <PackageId>DC.Build.Tasks</PackageId>
    <Product>DC.Build.Tasks</Product>
    <AssemblyTitle>DC.Build.Tasks</AssemblyTitle>
    <Description>MSBuild Tasks</Description>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Build.Framework" Version="15.1.1012" />
    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.1.1012" />
  </ItemGroup>

</Project>

Pokud nechcete tuto MSBuild Task library vytvářet sami, můžete použít mojí z GitHubu holajan/DC.Build.Tasks.

Použití MSBuild tasku pro nastavení FileVersion a AssemblyVersion

Vytvoření MSBuild tasku byla ta jednodušší část, teď zbývá nastavit jeho použití v MSBuild project .csproj souboru. Nejprve pomoci UsingTask importujeme náš task GetCurrentBuildVersion. Následující definice předpokládá, že dll s GetCurrentBuildVersion taskem (v mém případě DC.Build.Tasks.dll) leží o jeden nadřazený adresář víš než editovaný .csproj soubor:

<UsingTask TaskName="GetCurrentBuildVersion" AssemblyFile="$(MSBuildThisFileFullPath)\..\..\DC.Build.Tasks.dll" />

Dále potřebujeme zavést Target sekci, která se bude volat před buildem. Použijme k tomu nastavení BeforeTargets a napojíme se na  target BeforeBuild.

<Target Name="BeforeBuildActionsProject1" BeforeTargets="BeforeBuild">

Důležité je zde pojmenování target sekce, jméno může být libovolné (v příkladu BeforeBuildActionsProject1), ale musí být v každém projektu různé. Pokud tedy budeme v solution mít více projektů, ve kterých budeme chtít generovat číslo verze, v každém z nich tuto sekci pojmenujeme jinak, aby se při build procesu nevolala vícekrát stejná sekce.

V tomto target budeme pak volat task GetCurrentBuildVersion a vrácenou hodnotou (z vlastnosti tasku Version) přenastavíme číslo verze v MSBuild property FileVersion. Poté ještě tu samou hodnotu nastavíme i do property AssemblyVersion (pokud potřebujete automaticky generovat například pouze FileVersion, můžete toto nastavení vynechat).

Celé to pak v projektovém souboru .csproj vypadá takto:

<Project Sdk="Microsoft.NET.Sdk">
  <UsingTask TaskName="GetCurrentBuildVersion" AssemblyFile="$(MSBuildThisFileFullPath)\..\..\DC.Build.Tasks.dll" />

  <PropertyGroup>
    ...
    <AssemblyVersion>0.9.0.0</AssemblyVersion>
    <FileVersion>0.9.0.0</FileVersion>
  </PropertyGroup>

  ...

  <Target Name="BeforeBuildActionsProject1" BeforeTargets="BeforeBuild">
    <GetCurrentBuildVersion BaseVersion="$(FileVersion)">
      <Output TaskParameter="Version" PropertyName="FileVersion" />
    </GetCurrentBuildVersion>
    <PropertyGroup>
      <AssemblyVersion>$(FileVersion)</AssemblyVersion>
    </PropertyGroup>
  </Target>

</Project>

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback