Besser bauen mit FAKE

Prolog

Mein Projekt “WPF over Data” ist umgezogen. Auf Github wurden unter Poseidon die Assemblies neu konzeptioniert, von Caliburn.Micro wurde die neuste Version eingespielt, damit einhergehend wird nun MEF anstatt Windsor.Castle eingesetzt. Ziel war es, das Projekt später unter einer CI-Umgebung wie TeamCity oder Hudson automatisch bauen zu können.

Voraussetzung hierfür ist ein Build-Tool, das alle Projekte compiliert, die darin enthaltenen Software-Tests aufruft und die verschiedenen Programme zu einer Setup-Routine zusammenpackt. Der erste Blick fällt dabei auf MSBuild, da es schon für die Solutions und Projekte eingesetzt wird. Allerdings ist die verwandte XML-Sprache sehr gesprächig, das behandeln der Dateien in Filesets erscheint umständlich. Es gibt noch andere Möglichkeiten, etwa Ant oder Rake – meine Wahl fiel auf Fake.

Alles auf Anfang

Spielt man den Zyklus eines Continuous Integration Servers durch, steht am Anfang das Abrufen der aktuellen Version, es folgt der Aufruf des Build-Scripts. Damit das funktionieren kann, müssen die Build-Tools, also Fake, in die Quellcodeverwaltung eingecheckt werden. Der Aufruf des Builds kann durch eine Batch-Datei simuliert werden. Da viel Trial-And-Error bei der Zusammensetzung des Build-Scripts mitspielt, sollte der Batch so lange aktiv sind, bis der Benutzer abbricht. Hier also die build.cmd:

@echo off

:Build
cls

SET TARGET="Default"

IF NOT [%1]==[] (set TARGET="%1")
  
"tools\Fake\Fake.exe" "build.fsx" "target=%TARGET%"

rem Bail if we're running a TeamCity build.
if defined TEAMCITY_PROJECT_NAME goto Quit

rem Loop the build script.
set CHOICE=nothing
echo (Q)uit, (Enter) runs the build again
set /P CHOICE= 
if /i "%CHOICE%"=="Q" goto :Quit

GOTO Build

:Quit
exit /b %errorlevel%

Zusätzlich prüft das Script, ob es von TeamCity aufgerufen wird und beendet sich nach einmaligem Durchlauf.

 

Bau-Plan

Als nächstes wird das im Build-Batch benutzte Build-Skript build.fsx erstellt. Unter Fake beginnt die Datei immer mit

#I @"tools\Fake"
#r "FakeLib.dll"

open Fake

Es folgen die Eigenschaften des Projekts, in unserem Fall

let projectName = "Poseidon"
let projectSummary = "Poseidon - a POS environment"
let authors = ["J. Preiss"]
let mail = "joerg.preiss@slesa.de"
let homepage = "http://github.com/Slesa/Poseidon"

Diese können zum Generieren der Einträge in der AssemblyInfo.cs benutzt werden. Zur Vereinfachung folgen die Definitionen der Verzeichnisse

let binDir = @".\bin\"
let buildDir = binDir @@ @"build\"
let testDir = binDir @@ @"test\"
let reportDir = binDir @@ @"report\"
let deployDir = binDir @@ @"deploy\"
let packagesDir = binDir @@ @"packages"
let mspecDir = packagesDir @@ "MSpec"
let setupDir = @"Setup\"

Um die verschiedenen Programme mit Daten zu füttern, folgt die Liste der zu verarbeitenden Dateien

let appReferences = !! @"src\**\*.*sproj"
let testReferences = !! @"src\**\*.Specs.*sproj"
let deployReferences = !! @"Setup\**\*.wixproj"

Fake setzt die Versionsnummer auf “LocalBuild”, falls keine angegeben wurde. Das führt mitunter zu Fehlern, da es sich aus Sicht von Visual Studio nicht um eine gültige Versionsnummer handelt. Aus diesem Grund wird eine eigene Versionsnummer generiert, falls das Skript nicht auf einem Build-Server ausgeführt wird

let currentVersion =
  if not isLocalBuild then buildVersion else
  "0.0.0.1"

 

Targets

Nachdem alle benötigten Informationen definiert wurden, können wir nun die zu erledigenden Ziele unseres Skripts angeben.

Zuerst das Löschen der temporären Dateien und Ergebnisse bisheriger Builds:

Target "Clean" (fun _ ->
  CleanDirs [buildDir; testDir; deployDir; reportDir; packagesDir]

  CreateDir mspecDir
  !! (@"src\Domain\packages\Machine.Specifications.*\**\*.*")
    |> CopyTo mspecDir
)

Nun das Target zum Erzeugen der AssemblyInfo.cs mit den Versionsinformationen. Im ursprünglichen Beispiel wurde pro Projekt eine eigene Datei mit allen Informationen angelegt. Hier werden die Daten aufgeteilt, die Versionsnummer wird in die Datei VersionInfo.cs geschrieben. Diese muß als Link zu jedem Projekt hinzugefügt werden. Durch diese Vorgehensweise müssen jedoch die Default-Parameter der AssemblyInfo-Funktion – Guid, ComVisible und CLSCompliant – überschrieben werden, da sie sonst doppelt im Projekt vorkommen.

Target "SetAssemblyInfo" (fun _ ->
  AssemblyInfo
    (fun p ->
    {p with
      CodeLanguage = CSharp;
      Guid = "";
      ComVisible = None;
      CLSCompliant = None;
      AssemblyCompany = "Slesa Solutions";
      AssemblyProduct = "Poseidon";
      AssemblyCopyright = "Copyright ©  2012";
      AssemblyTrademark = "GPL V2";
      AssemblyVersion = currentVersion;
      OutputFileName = @".\src\VersionInfo.cs"})
)

Das Erstellen der Programme und Tests wird an MSBuild delegiert:

Target "BuildApp" (fun _ ->
  MSBuildRelease buildDir "Build" appReferences
    |> Log "AppBuild-Output: "
)

Target "BuildTest" (fun _ ->
  MSBuildDebug testDir "Build" testReferences
    |> Log "TestBuildOutput: "
)

Die Tests sind mit Hilfe von Machine.Specifications  implementiert, der Konsolenrunner von MSpec kann von Fake auf die folgende Weise angesprochen werden:

Target "Test" (fun _ ->
  let mspecTool = mspecDir @@ "mspec-clr4.exe"

  !! (testDir @@ "*.Specs.dll")
    |> MSpec (fun p ->
      {p with
        ToolPath = mspecTool
        HtmlOutputDir = reportDir})
)

Nach den Tests soll das Setup-Paket geschnürt werden. Die in Fake integrierten Funktionen des WiX-Installers dienen mehr dazu, Dateien ähnlich den Projektdateien zu sammeln und zu einem einfachen Setup-Programm zu packen. In unserem Fall genügt das nicht, da ein umfangreicheres Setup-Programm geplant ist. Dort sollen die verschiedenen Module selektiert, sowie generelle Einstellungen wie Connect-Strings konfiguriert werden. Deshalb wird hier wiederum MSBuild bemüht, um ein komplettes WiX-Projekt zu erstellen. In der aktuellen Skript-Variante bedeutet dies, daß WiX auf dem Build-Server installiert sein muß. Geplant ist, das Tool-Verzeichnis ebenfalls an MSBuild zu übergeben, sodaß auch die WiX-Binaries der Quellcodeverwaltung anvertraut werden können.

Ein weiteres Problem stellt die Versionsnummer dar. Das hier benutzte MSBuildReleaseExt existiert im Original-Fake nicht. Ich habe es wie folgt implementiert

let MSBuildReleaseExt outputPath properties targets = 
    let properties = ("Configuration", "Release") :: properties; 
    MSBuild outputPath targets properties

Vielleicht findet sich ja noch eine elegantere Möglichkeit, MSBuild weitere Parameter zu übergeben. Das Setup-Target sieht nunmehr so aus:

Target "Deploy" (fun _ ->
  MSBuildReleaseExt deployDir ["Version", currentVersion] "Build" deployReferences
    |> Log "DeployBuildOutput: "
)

Es fehlt noch das Default-Target, das einfach nichts macht:

Target "Default" DoNothing

 

Und ab dafür

Jetzt müssen noch die Abhängigkeiten der verschiedenen Targets definiert werden. Nach dem Aufräumen werden die Assembly-Infos erzeugt, die Programme und Tests werden erzeugt, die Tests werden aufgerufen und das Setup erzeugt. Falls es an irgendeiner Stelle ein Problem gab, bricht der Build-Vorgang ab. Oder anders ausgedrückt:

// Dependencies
"Clean"
  ==> "SetAssemblyInfo"
  ==> "BuildApp" <=> "BuildTest"
  ==> "Test"
  ==> "Deploy"
  ==> "Default"

Es fehlt noch der Aufruf des eigentlichen Builds

RunParameterTargetOrDefault "target" "Default"

 

Fazit

In meinen Augen beschränkt sich Fake beim Anlegen des Build-Skripts auf das wesentliche. Im Gegensatz dazu stehen die XML-basierten Tools, die durch ihre Syntax sehr viel Overhead erzeugen. Neben Fake stehen natürlich noch weitere Alternativen zur Verfügung, etwa Rake oder PSake. In jedem Fall sollte das Erstellen der Software genau so einfach sein: auschecken, Doppelklick auf den Build-Batch – fertig.

Advertisements
Dieser Beitrag wurde unter .NET, Build-Tools, F# veröffentlicht. Setze ein Lesezeichen auf den Permalink.

3 Antworten zu Besser bauen mit FAKE

  1. „In jedem Fall sollte das Erstellen der Software genau so einfach sein: auschecken, Doppelklick auf den Build-Batch – fertig.“ – well said!

  2. Frank schreibt:

    Interessanter Beitrag. Schadet wohl nicht, sich mit der Thematik detailierter zu befassen. Ich werde bestimmt die weiteren Posts im Auge behalten.

  3. Pingback: Ein eigenes Setup mit WiX | Slesa's Blog

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s