Die Build soll einfach hochgezählt werden, die Revison soll aus Subversion übernommen werden.
Nach „langer“ Suche, unter anderem auch hier im Forum, habe ich unten beschriebene Lösung "zusammengefunden". (An der Stelle sei erwähnt, dass ein paar Tipps von JuyJuka kamen.)
Ich dachte, dass meine Sucherei vielleicht jemandem noch nützt. in Form eines FAQ Beitrages, o.ä.
Die Beschreibung ist etwas größer geworden als ich dachte. Also Korrekturlesen wäre ganz angebracht.
Meine Umgebung:
- VS 2008/VS 2010 RC
- Subverion 1.6.9 (im Netzwerk, keine Lokale Installation)
Subversion Version soll dabei eigentlich keine Rolle spielen. Zumindest habe ich bei meiner Recherche diese gar nicht beachtet.
Versionsnummer soll im Format Major.Minor.Revision.Build erzeugt werden.
(nicht MS Format: Major.Minor.Build.Revision )
Als erstes muss man MSBuild Community Tasks installieren. Diese kann man von http://msbuildtasks.tigris.org/ runterladen. MSBuild Community Tasks ist Ansammlung von verschiedenen Tools, welche man in den Build Prozess einbinden kann.
Uns interessieren dabei nur drei der vielen nützlichen Tasks: Version, SvnVersion und AssemblyInfo
Version-Task hilft die Buildnummer des Projektes zu zählen, mit Hilfe von SvnVersion-Tasks werden wir die Revision des Projektes aus Subverion auslesen. AssemblyInfo erstellt dann eine AssemblyInvo.cs Datei mit den Versionsinformationen.
Nachdem die MSBuild Community Tasks installiert sind, muss man prüfen, ob ein SvnClient auf dem Rechner vorhanden ist. Diesen braucht man wegen der svnversion.exe. svnversion.exe wird von SvnVersion-Task benutzt, um wie oben bereits beschrieben, die Revision zu ermitteln. Wenn Subversion lokal installiert wurde, dann ist die svnversion.exe (ohne Gewähr ;) ) bereits vorhanden. Wenn nicht dann soll jetzt ein SvnClient drauf. Ich habe z.B. Silk Subverion Client benutzt (http://www.sliksvn.com/)
Nun wenn alle Voraussetzungen geschaffen sind, kommen wir zum nächsten Abschnitt: Anpassen der Projekt-Datei.
Man öffnet die C#-Projekt-Datei (*.csproj) in einem Text-Editor und geht ans Ende. Sieht etwa so aus (VS 2010)
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
Zuerst muss man VisualStudio anweisen, die MSBuild Community Tasks zu benutzen. Dafür fügt man folgende „Anweisung“ ein
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"
Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')"/>
gleich unter der Zeile
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Attribut “Project” zeigt, wo die Community Tasks sich befinden (Standardinstallation)
“Condition” sorgt dafür, dass Projekt geladen werden kann, auch wenn die Community Tasks nicht installiert sind.
Im nächsten Schritt sorgen wir dafür, dass die Build-Nummer automatisch um eins erhöht wird.
Dafür ist Version-Task zuständig. Version-Task speichert in einer Datei die letzte Versionsnummer und erhöht die Build-Nummer bei jedem Aufruf um eins.
<Target Name="Version">
<Version VersionFile="version.txt" RevisionType="Increment">
<Output TaskParameter="Major" PropertyName="Major" />
<Output TaskParameter="Minor" PropertyName="Minor" />
<Output TaskParameter="Build" PropertyName="Build" />
<Output TaskParameter="Revision" PropertyName="Revision" />
</Version>
</Target>
VersionFile gibt den Namen der Datei an, in der die Version gespeichert wird. Beim ersten Ausführen, wird eine neue Datei erstellt und Version 1.0.0.0 gespeichert. Zweite Ausgabe wäre dann 1.0.0.1, dann 1.0.0.2 usw.
RevisionType=“Increment“ bedeutet, dass die letzte Stelle (Revision nach MS Standard) der Versionsnummer inkrementiert wird.
Damit Projekt beim Kompilieren nicht angemeckert wird (wegen fehlender CommunityTasks), soll man eine Bedingung auch hier eingeben
Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')"
Nach dem Ausführen des Version-Tasks sind Major, Minor, Build und Revision Variablen gesetzt und können weiter, z.B. durch andere Tasks, benutzt werden.Nächster Part sorgt dafür, dass die Revisionsnummer aus SVN ausgelesen wird und erstellt eine AssemblyInfo.cs Datei.
<Target Name="AssemblyInfo"
Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')">
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="$(ProgramFiles)\SlikSvn\bin"
Condition="Exists('$(ProgramFiles)\SlikSvn\bin\svnversion.exe')">
<Output TaskParameter="Revision" PropertyName="Build" />
</SvnVersion>
<AssemblyInfo
CodeLanguage="CS"
OutputFile="Properties\AssemblyInfo.cs"
AssemblyTitle="Title"
AssemblyDescription="Description"
AssemblyCompany="Company"
AssemblyProduct="Product"
AssemblyCopyright="Copyright © 2009"
ComVisible="false"
Guid="888ff638-9547-4480-9bf4-4fe25103b35c"
AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)"
AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)"
Condition="$(Revision) != '0'"/>
</Target>
Bei dem SvnVersion-Task muss Pfad zu dem Verzeichnis mit der svnversion.exe Datei eigegeben werden. Condition sollte selbst erklärend sein.
Interessant ist der Output-Tag. Der besagt, dass der Wert der Eigenschaft „Revision“ des SvnVersion-Tasks in der Variable „Build“ gespeichert werden soll.
Nun kommt der AssemblyInfo-Task endlich ins Spiel. Dieser Tasks erstellt aus den gegebenen Informationen eine Assemblyinfo.cs. Die Felder sind soweit selbsterklärend. Wichtig ist, dass die GUID durch eine eigene ersetzt wird und sich bei den einzelnen Projekten NICHT wiederholt.
Interessante Stellen sind AssemblyVersion/AssemblyFileVersion Eingaben. Die Version wird durch Variablen, welche Version und SvnVersion-Tasks vorbereitet haben, zusammen gesetzt.
Nach dem alle Tasks definiert wurde, bleibt noch zu sagen, wann diese aufgerufen werden sollen. Die AssemblyIfo.cs Datei soll vor dem Erstellen des Projektes erzeugt werden. Deshalb wird der Aufruf der Tasks folgendermaßen angegeben.
<Target Name="BeforeBuild">
<CallTarget Targets="Version" />
<CallTarget Targets="AssemblyInfo" />
</Target>
Nun die vollständige Projektdatei könnte dann so aussehen
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.21022</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{ACF93EAD-62EF-4614-AEF4-45B6971957EF}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>WindowsFormsApplication2</RootNamespace>
<AssemblyName>WindowsFormsApplication2</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>3.5</OldToolsVersion>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Xml.Linq">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data.DataSetExtensions">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Class1.cs" />
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
<Visible>False</Visible>
<ProductName>Windows Installer 3.1</ProductName>
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- import MSBuildCommunityTasks -->
<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"
Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')"/>
<!-- to manage version number -->
<Target Name="Version"
Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')">
<Version VersionFile="version.txt" RevisionType="Increment">
<Output TaskParameter="Major" PropertyName="Major" />
<Output TaskParameter="Minor" PropertyName="Minor" />
<Output TaskParameter="Revision" PropertyName="Revision" />
<Output TaskParameter="Build" PropertyName="Build" />
</Version>
</Target>
<!-- to generate our personnal version info -->
<Target Name="AssemblyInfo" Condition="Exists('$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets')">
<SvnVersion LocalPath="$(MSBuildProjectDirectory)" ToolPath="$(ProgramFiles)\SlikSvn\bin"
Condition="Exists('$(ProgramFiles)\SlikSvn\bin\svnversion.exe')">
<Output TaskParameter="Revision" PropertyName="Build" />
</SvnVersion>
<AssemblyInfo
CodeLanguage="CS"
OutputFile="Properties\AssemblyInfo.cs"
AssemblyTitle="Test-WindowsFormsApplication"
AssemblyDescription="Beispiel für Verwendung der MSBuild Tasks"
AssemblyCompany="Ich"
AssemblyProduct="WindowsFormsApplication2"
AssemblyCopyright="Copyright © 2010"
ComVisible="false"
Guid="3AEEC92E-C260-4DD2-8F6D-790182F14CFE"
AssemblyVersion="$(Major).$(Minor).$(Build).$(Revision)"
AssemblyFileVersion="$(Major).$(Minor).$(Build).$(Revision)"
Condition="$(Revision) != '0' "/>
</Target>
<Target Name="BeforeBuild">
<CallTarget Targets="Version" />
<CallTarget Targets="AssemblyInfo" />
</Target>
<Target Name="AfterBuild">
</Target>
</Project>
Die erzeugte Datei sieht dann, wie im Beispiel unten aus. Wictig ist, dass man die erzeugte AssemblyInfo Datei nicht manuell bearbeitet, denn sie wird bei jedem Build-Vorgang überschrieben.
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30128.1
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("Test-WindowsFormsApplication")]
[assembly: AssemblyDescription("Beispiel für Verwendung der MSBuild Tasks")]
[assembly: AssemblyCompany("Ich")]
[assembly: AssemblyProduct("WindowsFormsApplication2")]
[assembly: AssemblyCopyright("Copyright © 2010")]
[assembly: ComVisible(false)]
[assembly: Guid("3AEEC92E-C260-4DD2-8F6D-790182F14CFE")]
[assembly: AssemblyVersion("1.0.1.12")]
[assembly: AssemblyFileVersion("1.0.1.12")]
Als letzter Strich, sollte man die entstandene "version.txt"-Datei auch zum Projekt hinzufügen. Damit wird die Datei auch in die Subversion Datenbank aufgenommen. Das ist wichtig, damit beim öffnen des Projektes auf einem anderen Rechner, die Build-Zählung nicht wieder von Vorne beginnt, sondern fortgesetzt wird.