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>