Dienstag, 22. August 2017

Orckestra C1 CMS Add-on mit Cake (C# Make) paketieren

Oftmals benötigen Online-Plattformen eine Managementoberfläche für die Bewirtschaftung der Inhalte (Content).

Wir setzen dazu vorzugsweise die Open Source Microsoft .NET Lösung Orckestra C1 CMS ein. Orckestra C1 CMS bietet eine Vielzahl an Basisfunktionen. Trotzdem gibt es natürlich immer wieder projektspezifische Anforderungen, die Erweiterungen erfordern. In Orckestra C1 CMS können solche Erweiterungen via Add-ons bewerkstelligt werden. Wir verwenden für deren Entwicklung den Ansatz, dass wir jedes Add-on als eigenständige, installierbare Einheit behandeln. Dies hat den Vorteil, dass wir die Entwicklungsarbeiten im Projekt besser parallelisieren und einmal entwickelte Add-ons in verschiedenen Kundenprojekten wiederverwenden können.

In unserem Blogpost nopcommerce WebDeploy Package erstellen mit Cake (C# Make) haben wir aufgezeigt, wie man Cake (C# Make) verwenden kann, um den nopCommerce Sourcecode zu kompilieren. Seit diesem Blogpost sind wir aktiv daran unsere gesamte Build-Infrastruktur auf Cake (C# Make) umzustellen. Natürlich betrifft dies nun auch die Kompilierung und Paketierung von Orckestra C1 CMS Add-ons. Gerne teilen wir in diesem Blogpost unser gewonnenes Wissen.

Anatomie eines Orckestra C1 CMS Add-on

Wir haben uns für einen immer gleichen Aufbau unserer Orckestra C1 CMS Add-on's entschieden. Dieser lässt sich wie folgt darstellen:

bambit Standardstruktur für Orckestra C1 CMS Add-on
  • Projekt A ist ein .dll-Projekt und endet auf .Core (z.B. Bambit.Orckestra.Seo.RedirectManager.Core). Innerhalb dieses Projektes platzieren wir alle Elemente, die kompiliert werden müssen. Zum Beispiel Static DataTypes
  • Projekt B ist ein Web-Projekt und endet auf .Package (z.B. Bambit.Orckestra.Seo.RedirectManager.Package). Innerhalb dieses Projektes platzieren wir alle Elemente, die zum Add-on gehören, aber nicht kompiliert werden müssen (in dem Ordner Package). Dies sind zum Beispiel Views, .resx-Dateien oder die install.xml

Add-on mit Cake kompilieren

Bevor man sich um die Paketierung des Add-Ons kümmern kann, gilt es den SourceCode zu kompilieren. Um sicherzustellen, dass immer nur der aktuellste SourceCode den Weg in das Orckestra C1 CMS Add-on findet, gestalten wir die ersten beiden Build-Schritte wie folgt:

  1. Alle alten Build-Artefakte (dll's usw.) aus vorhergehenden Builddurchführungen entfernen.
  2. Alle notwendigen Visual Studio Projekte kompilieren.

Schritt 1: Alte Build-Artefakte entfernen

Schritt 1 lässt sich mit folgender Cake-Anweisung erledigen:

var packageOutputDirectory = "../_Package";
 
Task("clean").Does(() => {
    CleanDirectories("../**/bin/debug");
    CleanDirectories("../**/obj/debug");
    CleanDirectories("../**/obj/release");
    CleanDirectories("../**/bin/release");
    CleanDirectories(packageOutputDirectory);
});

Dabei werden alle debug und release Verzeichnisse gelöscht.

Schritt 2: Visual Studio Projekt mit Cake kompilieren

Für Schritt 2 verwenden wir den Build-Befehl von Cake (C# Make) und übergeben als Parameter unser Solution-File (.sln).

Task("build").IsDependentOn("clean").Does(() => {
    DotNetBuild("../Bambit.Orckestra.Seo.RedirectManager.sln",settings=>settings.SetConfiguration(buildConfiguration));
});

Add-on mit Cake paketieren

Nebst den vorangehend kompilierten DLL-Dateien werden für das Orckestra C1 CMS Add-on oftmals noch mehr Dateien benötigt (z.B. Views, install.xml etc.). In unserer Struktur befinden sich diese Dateien alle im Ordner Package des Projektes B. Um alle Daten in die richtige Struktur zu bekommen, kopieren wir alle notwendigen Artefakte in den Ordner _Package.

Zielordner in den alle Dateien des Orckestra C1 CMS Add-on's kopiert werden

Mittels nachfolgendem Cake-Task werden die DLL Dateien, Views, Übersetzungen und auch die install.xml Datei in den _Package Ordner kopiert.

var packageOutputDirectory = "../_Package";
var dllOutputDirectory = "../_Package/Bin";
 
Task("prepare-package").IsDependentOn("build").Does(() => {
    CreateDirectory(dllOutputDirectory);
     
    CopyDirectory("../Bambit.Orckestra.Seo.RedirectManager.Package/Package", packageOutputDirectory);
     
    var dllFiles = GetFiles("../Bambit.Orckestra.Seo.RedirectManager.Core/bin/"+buildConfiguration+"/Bambit.Orckestra.Seo.RedirectManager.Core.dll");
    CopyFiles(dllFiles, packageOutputDirectory+"/Bin");
});

Versionierung mit Cake setzen

Damit eine Orckestra C1 CMS Installation dein Add-on richtig behandeln kann braucht das Add-on noch eine saubere Versionsnummer. Diese wird von Release zu Release erhöht und muss an verschiedenen Orden eingebettet werden. (Wir verwenden für unsere Versionierungen konstant das Versionspattern Semantic Versioning):

  • AssemblyInfo (Damit die Versionsnummern auch in den DLL-Dateien richtig gesetzt wird)
  • Install.xml (Damit Orckestra C1 CMS das Add-on richtig verwalten kann)
  • Im Name der Add-on .zip-Datei (für Systemadministratoren)

Während des Buildprozesses wird die Versionsnummer typischerweise von einem Buildserver (z.B. Jenkins oder Bamboo) vergeben. Damit das funktioniert, braucht das Cake-Script zwei Parameter / Argumente um die Informationen von Aussen übergeben zu können.

var version = Argument("version", "1.0.0");
var buildNumber = Argument("buildNumber", "354");

Aus diesen beiden Argumenten kreieren wir die Variable semVersion, welche die ganze Buildnummer (z.B. 1.2.6.3455) beinhaltet. Diese Versionsnummer verwenden wir anschliessend an mehreren Stellen.

var semVersion = string.Concat(version + "." + buildNumber);

Schritt 1: Mit Cake die AssemlbyVersion setzen

Mit folgender Anweisung kannst du Cake (C# Make) verwenden, um ein AssemblyInfo dynamisch zu generieren.

Task("set-version-in-assembly").IsDependentOn("clean").Does(() => {
    var file = new FilePath("../AssemblyInfoVersion.cs");
 
    var assemblyInfoSettings = new AssemblyInfoSettings 
    {
        Version = semVersion,
        FileVersion = semVersion,
        InformationalVersion = semVersion,
        Copyright = string.Format("Copyright (c) 2016 - {0}", DateTime.Now.Year)
    };
 
    CreateAssemblyInfo(file, assemblyInfoSettings);
});

Zudem musst du natürlich noch sicherstellen, dass die AssemblyInfo-Datei auch in allen deinen relevanten Visual Studio Projekten als Link hinzugefügt wird.

Visual Studio AssemblyVersion als Link hinzufügen

Schritt 2: Version in install.xml ersetzen

Um die Version im install.xml korrekt zu übernehmen verwenden wir XmlPoke. Mittels einem Xpath-Ausdruck,lässt sich der Wert des XML-Attributes Version in der Datei install.xml sehr einfach übersteuern.

Task("set-version-in-orckestrac1-package").IsDependentOn("prepare-package").Does(() => {
    XmlPokeSettings xmlPokeSettings =  new XmlPokeSettings 
    {
        Namespaces = new Dictionary<string, string> 
        {
            { "mi", "http://www.composite.net/ns/management/packageinstaller/1.0" }
        }
    };
 
    XmlPoke(packageInstallFile, "/mi:PackageInstaller/mi:PackageInformation/@version", semVersion, xmlPokeSettings);
});

Schritt 3: Version im Name der Zip-Datei setzen

Diese Aufgabe ist ganz leicht zu lösen: Alles was wir tun müssen, ist der Output-Path der Zip-Datei entsprechend zu ergänzen.

Task("zip-package").IsDependentOn("set-version-in-orckestrac1-package").Does(() => {
    Zip(packageOutputDirectory, packageOutputDirectory + "/" + packageName + "." + semVersion + ".zip");
});

Das finale Cake-Script

Das vollständige Cake-Script für die Paketierung unseres Orckestra C1 CMS Add-on's sieht nun wie folgt aus:

var target = Argument("target", "create-orckestrac1-package");
var buildConfiguration = Argument("configuration", "Release");
var version = Argument("version", "1.0.0");
var buildNumber = Argument("buildNumber", "354");
 
var packageOutputDirectory = "../_Package";
var packageInstallFile = "../_Package/install.xml";
var dllOutputDirectory = "../_Package/Bin";
var packageName = "bambit-orckestra-seo-redirectmanager";
var semVersion = string.Concat(version + "." + buildNumber);
 
Setup(context =>
{
    Information("Start with Build-Process");
});
 
Teardown(context =>
{
    Information("Build-Process finished");
});
 
Task("clean").Does(() => {
    CleanDirectories("../**/bin/debug");
    CleanDirectories("../**/obj/debug");
    CleanDirectories("../**/obj/release");
    CleanDirectories("../**/bin/release");
    CleanDirectories(packageOutputDirectory);
});
 
Task("set-version-in-assembly").IsDependentOn("clean").Does(() => {
    var file = new FilePath("../AssemblyInfoVersion.cs");
 
    var assemblyInfoSettings = new AssemblyInfoSettings 
    {
        Version = semVersion,
        FileVersion = semVersion,
        InformationalVersion = semVersion,
        Copyright = string.Format("Copyright (c) 2016 - {0}", DateTime.Now.Year)
    };
 
    CreateAssemblyInfo(file, assemblyInfoSettings);
});
 
Task("build").IsDependentOn("set-version-in-assembly").Does(() => {
    DotNetBuild("../Bambit.Orckestra.Seo.RedirectManager.sln",settings=>settings.SetConfiguration(buildConfiguration));
});
 
Task("prepare-package").IsDependentOn("build").Does(() => {
    CreateDirectory(dllOutputDirectory);
     
    CopyDirectory("../Bambit.Orckestra.Seo.RedirectManager.Package/Package", packageOutputDirectory);
     
    var dllFiles = GetFiles("../Bambit.Orckestra.Seo.RedirectManager.Core/bin/"+buildConfiguration+"/Bambit.Orckestra.Seo.RedirectManager.Core.dll");
    CopyFiles(dllFiles, packageOutputDirectory+"/Bin");
});
 
Task("set-version-in-orckestrac1-package").IsDependentOn("prepare-package").Does(() => {
    XmlPokeSettings xmlPokeSettings =  new XmlPokeSettings 
    {
        Namespaces = new Dictionary<string, string> 
        {
            { "mi", "http://www.composite.net/ns/management/packageinstaller/1.0" }
        }
    };
 
    XmlPoke(packageInstallFile, "/mi:PackageInstaller/mi:PackageInformation/@version", semVersion, xmlPokeSettings);
});
 
Task("zip-package").IsDependentOn("set-version-in-orckestrac1-package").Does(() => {
    Zip(packageOutputDirectory, packageOutputDirectory + "/" + packageName + "." + semVersion + ".zip");
});
 
Task("create-orckestrac1-package").IsDependentOn("zip-package").Does(() => {
     
});
 
RunTarget(target);
Autor

Reto Gurtner

Orckestra C1 CMS Partner aus der Schweiz

Suchst du Orckestra C1 CMS Experten? Brauchst du Hilfe mit der Umsetzung einer Online-Plattform? Möchtest du dich einfach mal austauschen?

Angebot ansehen