Posts mit dem Label Msbuild werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Msbuild werden angezeigt. Alle Posts anzeigen

Sonntag, August 21, 2011

Nuget package für verschiede Frameworks

Eigentlich sollten in der Zwischenzeit alle .NET Entwickler von Nuget gehört haben. Nuget ist wirklich ein sehr schönes Mittel Bibliotheken, Komponenten und Artefakte zu verteilen und für anderen zur Verfügung zu stellen. Für Nuget gibt es eine gute Integration ins Visual Studio 2010, leider nicht in die älteren Versionen, zusätzlich eine Kommandozeilen und Powershell Version. Da ich viel auf Reisen bin, habe ich mir die wichtigsten Pakete, die ich immer wieder brauche in ein lokales Repository gezogen.

nuget install <packagename>

Aus dem heruntergeladenen und installierten Paketen habe ich anschließend alle nupkg-files in einen Ordner kopiert, fertig war mein Repo. Wer mag kann noch eine Nuget-Server (http) daraus machen, dazu muss man nur das Paket “nuget server” 
Im Package Manager im VS kann man mittels Add superleicht seine eigenen Quellen hinzufügen.

Visual StudioNuget Manager Konfiguration

Ein Paket kann man superleicht aus jedem Projekt erzeugen.

Nuget spec <packageName>
Nuget pack <projectFile>

Erzeugt ein Spec file. Wenn es zusätzlich im Verzeichnis der SLN ausgeführt wird, dann werden die Parameter als Platzhalter übernommen. mit dem 2Kommando wird das nupkg erzeugt und der Output des Projektes hinzugefügt.

Der Teil war einfach. Nun sollte das Paket allerdings mehrere FX-Versionen unterstützen. Dazu muss man im SPEC-file den Output entsprechend in verschiedenen Ordnern einsortieren. Im file-Element kann man mittels des target-Attributes das Verzeichnis im Package definieren. Im Beispiel unten werden die Assemblies für Framework 2.0 in den Ordner “lib\net20” einsortiert.

lib\net20 .NET Framework 2.0
lib\net40 .NET Framework 4.0
lib\sl40 Silverlight 4.0

Nach dem editieren sah mein Spec File so (oder so ähnlich aus):

1 <?xml version="1.0"?> 2 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> 3 <metadata> 4 <id>MyPackageName</id> 5 <version>$version$</version> 6 <authors>Jan Zieschang</authors> 7 <owners>Jan Zieschang</owners> 8 <!--<licenseUrl>http://LICENSE_URL_HERE_OR_DELETE_THIS_LINE</licenseUrl> 9 <projectUrl>http://PROJECT_URL_HERE_OR_DELETE_THIS_LINE</projectUrl> 10 <iconUrl>http://ICON_URL_HERE_OR_DELETE_THIS_LINE</iconUrl>--> 11 <requireLicenseAcceptance>false</requireLicenseAcceptance> 12 <description>Here I should describe my package a bit</description> 13 <tags>Tags For Grouping And Finding</tags> 14 <dependencies> 15 <dependency id="PostSharp" version="2.1"/> 16 <dependency id="log4net" version="1.2" /> 17 </dependencies> 18 </metadata> 19 <files> 20 <file src="bin\Release\myPackageLib.dll" target="lib\net40"/> 21 <file src="bin\Release\myPackageLib.xml" target="lib\net40"/> 22 <file src="bin\Release\myPackageLib.pdb" target="lib\net40"/> 23 <file src="bin\Release2\myPackageLib.dll" target="lib\net20"/> 24 <file src="bin\Release2\myPackageLib.xml" target="lib\net20"/> 25 <file src="bin\Release2\myPackageLib.pdb" target="lib\net20"/> 26 </files> 27 </package>

Nun schlug aber der “Nuget pack” Aufruf fehlt. Nach etwas probieren musste ich feststellen, dass der Aufruf nun das Spec file direkt benötigt.
1 "%nugetPath%" pack "MyPackageName.nuspec" -Version %1 -OutputDirectory "%output%" -symbols -Properties Configuration=Release

Wie man ein Package erstellt kann man noch viel detaillierter unter http://docs.nuget.org/docs/creating-packages/creating-and-publishing-a-package nachlesen. Hier noch einige Tipps für die Paketierung - http://lostechies.com/joshuaflanagan/2011/06/23/tips-for-building-nuget-packages/. Publishen von Packages ist mehr als simpel. Paket in den Ordner kopieren und oder mittels nuget push auf den Paket Server schieben. Ich kam nun aber zu meinem eigentlich Problem.

Da ich für mein Projekt mehrere Ausgaben für unterschiedliche Frameworks erzeugen wollte, musste ich irgendwie meine Projekt-Referenzen austauschen. Letztendlich habe ich mein Project files editiert und die Hint-Paths für die Assemblies verändert.

1 <Reference Include="log4net"> 2 <HintPath>..\packages\log4net.1.2.10\$(Log4NetHintPath)\log4net.dll</HintPath> 3 </Reference> 4 <Reference Include="PostSharp, Version=2.1.0.0, Culture=neutral, PublicKeyToken=b13fd38b8f9c99d7, processorArchitecture=MSIL"> 5 <HintPath>..\packages\PostSharp.2.1.2.8\$(PostSharpHintPath)\PostSharp.dll</HintPath> 6 </Reference>

Die beiden Hint-Paths für die Referenzen PostSharp und Nunit werden mittels einer Variable dynamisiert. In einer PropertyGroup mit einem Condition-Element können die Referenzen ausgetauscht werden.
1 <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release3.5|AnyCPU'"> 2 <PostSharpHintPath>lib\net20</PostSharpHintPath> 3 <Log4NetHintPath>lib\2.0</Log4NetHintPath> 4 <TargetFrameworkVersion>v3.5</TargetFrameworkVersion> 5 </PropertyGroup>

Achtung: Sollte man die Referenzen aktualisieren, zum Beispiel weil es neue Versionen als Nuget-Package gibt, muss ggf. der Hint-Pfad erneut korrigiert werden.

Außer dem Hint-Path muss die Property “TargetFrameworkVersion” gesetzt werden. Dadurch kann man mit MSBuild 4.0 alle Fx Versionen bauen. (Ich glaube, es gebe auch schon in MSBuild 3.5)

Das war die minimal Version. Aber ich habe schon seit Wochen TeamCity auf meinem Rechner gehabt und CI Builds von meinem Paket zu erzeugen. Außerdem wollte ich auch eine minimal Validierung meines Setups haben.

TeamCity Build Steps for my build

Aus bauen und Paket erstellen ist sind 4 Schritte geworden. Baue Fx4 Version, Validiere diese Version, Baue Fx3.5 Version Validiere diese und anschließend erstelle mein Nuget-Paket. Natürlich übernimmt der CI Server auch die Versionierung der Assemblies und des Paketes und checkt die Version immer sauber aus.

Technorati Tags:

Sonntag, Juli 19, 2009

SharePoint (Web services) - MSBuild

Ich habe mich seit Freitag, hautpsächlich am Freitag, mal mit der Integration von MSBuilds und SharePoint Webservices beschäftigt. Ich möchte bei einem Integration-Build möglichst ohne Aufwände Daten in einer Sharepoint-Liste aktualisieren, oder auch mal neue Daten anhängen. Beim bingen nach einer out-of-the-box Lösung bin ich leider ins Leere gelaufen, daher der eigene Ansatz – Erstellen eines MSBuild Tasks.

Wer mit SharePoint über die Webservices arbeiten will, der sollte sehr fit mit Xml sein. Die Webservices arbeiten ausschließlich mit Xml Eingaben zur Steuerung. Mit den Beispielen in der MSDN kommt man gut voran, daher kann man sich schnell in die Webservice-Schnittstelle(n) einarbeiten.

Ergebnis der “Session” sind 3 MSBuild-Tasks um mit Listen im SharePoint zu interagieren. Den Source Code dafür gibt es hier: http://www.zieschang-jan.de/documents/MsBuildExtensions.zip. Ob ich den günstigsten Weg für die Abbildung der Eigenschaft für den Aufruf gewählt habe, weiß ich selber nicht so richtig, aber mir ist kein besser Weg dazu eingefallen.

Einrichtung

Um die Tasks nutzen zu können muss das Projekt compiliert werden, anschließend sind im Ausgabe-Verzeichnis  4 Dateien vorhanden. Es gibt nun 2 Möglichkeiten die Tasks zu nutzen:

  1. Kopieren der DLL und der “*.target”-Datei in den MSBuildExtensionsPath, welcher normalerweise “%programfiles%\MSBuild” ist. Anschließend im Import das Target-File referenzieren <Import Project="$(MSBuildExtensionsPath)\CapeVision.MsBuildExtensions.targets"/>
  2. Die Build-Dateien sollen lokal für das Projekt genutzt werden ohne globale Verfügbarkeit. In diesem Fall ist muss die Referenz für das Target angepasst werden, wie das geht ist im Sample.msbuild im Zip gezeigt.

Usage im MSBuild

Einbindung der Tasks:

   1: <PropertyGroup>
   2:   <CapevisionMsBuildExtensionsTasksPath>.</CapevisionMsBuildExtensionsTasksPath>
   3:   <SharePoint>http://sdc-srv-moss/SiteDirectory/mcc/</SharePoint>
   4:   <List>Bücher</List>
   5: </PropertyGroup>
   6: <Import Project="$(MSBuildExtensionsPath)\CapeVision.MsBuildExtensions.targets"/>

Abrufen alle Listen der SharePoint Site

Dazu muss die Url der SharePoint Site übergeben werden, anschließend werden alle Libraries (Bibliotheken) der Site zurückgegeben. Die Rückgabe kann mittels des Output-Parameters abgefragt werden und anschließend wie andere TaskItems behandelt werden. Das Item verfügt über die folgendenen MetaDaten:

  • ID
  • Title
  • Description
  • Modified
  • Created
  • ItemCount
  • EnabledAttachments
   1: <SharepointListGetListsTask SharePointUrl="$(SharePoint)">
   2:   <Output TaskParameter="Libraries" ItemName="myItems"/>      
   3: </SharepointListGetListsTask>
   4: <Message Text="@(myItems)"/>

Hinzufügen eines neuen Eintrages

Zum Anlegen eines Eintrages werden die Listen Felder und die Werte für diese Einträge benötigt. Beides wird als Items an den Task übergeben. Da ein Item benötigt wird und dies mit den Standardmitteln immer eine Datei erfodert wird im Beispiel immer das Build-Script selber referenziert. Die Spalten und die Werte müssen von der Anzahl übereinstimmen und werden entsprechend der Reihenfolge ausgewertet. Zusätzlich wird der Name der Liste benötigt, in die die Daten eingefügt werden.

   1: <ItemGroup>
   2:   <!-- Include schould result in only one file, item requires file present!-->
   3: <ListFields Include="Sample.msbuild">
   4:   <Name>Title</Name>
   5: </ListFields>
   6: <ListFields Include="Sample.msbuild">
   7:   <Name>Description</Name>
   8: </ListFields>
   9: <Add Include="Sample.msbuild">
  10:   <Name>New MsBuild Script Item</Name>
  11: </Add>
  12: <Add Include="Sample.msbuild">
  13:   <Name>This item was inserted by a script/Name>
  14: </Add>
  15: </ItemGroup>
  16: <SharepointListAddTask SharePointUrl="$(SharePoint)" 
  17:                        ListName="$(List)" ListFields="@(ListFields)"
  18:                        Values="@(Add)"/>

Ändern eines Eintrages

Am kompliziertesten ist das Ändern eines Eintrages, es muss zu den Felder und den Werten noch eine Bedingung übergeben werden, welche Einräge zu ändern sind. Aber an sich doch dann selbstsprechend, oder? Es gibt eine Einschränkung, maximal werden 200 Elemente geändert, weil nur 200 mit der Bedingung geladen werden. Die Bedingung kann sich aus mehreren Einträgen zusammensetzen, diese werden mit UND verbunden. Bei Condition könnte noch ein “operator” mitgegeben werden, der definiert, wie der Vergleich auszuführen ist. Der Standard-Operator ist “Eq”, also Gleichheit.

   1: <ItemGroup>
   2:   <!-- Include schould result in only one file, item requires file present!-->
   3:   <ListFields Include="Sample.msbuild">
   4:     <Name>Title</Name>
   5:   </ListFields>
   6:   <Update Include="Sample.msbuild">
   7:     <Name>Updated MsBuild Script Item</Name>
   8:   </Update>
   9:   <Condition Include="Sample.msbuild">
  10:     <field>Title</field>
  11:     <value>New MsBuild Script Item</value>
  12:   </Condition>
  13: </ItemGroup>
  14: <SharePointListUpdateTask SharePointUrl="$(SharePoint)"
  15:                        ListName="$(List)" ListFields="@(ListFields)"
  16:                        Values="@(Update)"
  17:                        QueryCondition="@(Condition)"/>

TODOs

  • Momentan kann man keine ListItems abfragen, dass muss ich definitiv noch für MSBuild nach ausenlegen. Eigentlich ist der Code bereits vorhanden, denn beim Update werden die Einträge für die Referenzierung (ID) benötigt.
  • Ich werde die Abfragesprache für das Updaten und Laden von Items auf SQL-like Queries umstellen. Dazu gibt es bereits ein interessantes Projekt auf Codeplex http://yacamlqt.codeplex.com/. Die Integration sollte auch nicht so schwer sein.

Montag, Januar 26, 2009

Continuous Integration – Lohnt meistens

Ich bin ein großer Fan von Continuous Integration (CI) und ich bin auch der Meinung, dass es sich meistensimmer auszahlt. Umso schöner, wenn sich bei Projekten der Erfolg relativ schnell herausstellt und auch andere die Vorteile erkennen. In einem meiner aktuellen Projekte habe wir letztes Jahr mit relativ wenig Überzeugungsarbeit CI mit CCNet eingeführt. Der Initiale Aufwand hielt sich in Grenzen, wahrscheinlich überalles bei 3-4PT (mit Einführung und Lernphase von Kollegen). Der Hauptgrund für die Einführung war erst mal Nachvollziehbarkeit und eindeutige Identifizierung von Versionen. Nach dem dieses Ziel erreicht ist, stellen wir gerade bei unseren häufigeren Patch-Bereitstellungen für Tests fest, wie schön es ist ein CI-System zu nutzen. Das System deployed nun aktuelle Testversionen schnell auf unseren Testserver, das ganze ist noch nicht perfekt, aber das muss es auch nicht sein.

Allerdings merke ich auch immer wieder, wie wichtig es ist, dass mindestens die wichtigen Personen im Projekt einen nutzen sehen und wenn es auch die politischen Dinge, wie Governance, Nachvollziehbarkeit, …. sind. Im Projekt haben wir keine strengen Restriktionen, daher sind viele Prüfungen nur als Informationen ausgeführt und werden auf der Seite reported (Emails lassen wir auch weg.). CI ist als integraler Bestandteil des Entwicklungsprozess zu sehen und erfordert nicht DEN großen Aufwand. Für Neulinge auf dem Gebiet kann ich CI-Factory empfehlen, dass auch eine Best-Practice Verwaltungsstruktur aufsetzt. Ich habe den Focus auf MS-Projekte und Tools, aber CI gibt’s für alle.

Hier noch mal meine Meinung zu den Steps, die im Build-System ablaufen sollten:

  1. Version-Bezeichnung bereitstellen
  2. Validieren der Quellen/ Integration (MSBuild)
  3. Unit-Testen der Quellen (NUnit oder XUnit)
  4. Statische Code-Analyse und Prüfung der Richtlinien (FxCop)
  5. Paketieren und/oder bereitstellen der SW (Wix oder XCOPY)
  6. Markieren (Labeln oder Taggen) der Version in der Quellcodeverwaltung

Gerade die Schritte 1, 2, 4, 5 und 6 sind einfach umzusetzen und bringen auch schnell einen Erfolg. Richtig klasse wird es, wenn das ganze Team den Nutzen erkennt und gerade für Continous Builds zur Verifikation der Quellen nutzt. Hierfür sind Entwicklungsrichtlinien und er gemeinsame Konsens erforderlich und alle müssen in das System schauen. Irgendwo gab es mal das nette Zitat „If you broke it, fix it!“, so muss das Motto jedes Teams heißen.

Leider habe ich immer wieder das Gefühl, dass Projektleiter am wenigsten von sowas zu überzeugen sind. Die meisten Projektleiter achten auf das Budget und können natürlich 3PT zusätzlich nicht verkraften, außerdem erfordert Governance Arbeit. Ich muss noch deutlich an meiner Argumentation arbeiten, aber Freizeit opfern ist nicht.

Sonntag, Mai 18, 2008

MSBuild to ccnet updated

Ich habe heute mir endlich mal die Zeit genommen einige gravierenden Fehler im MSBuild-Logger gefixt, den ich angepasste hatte. Außerdem hatte das Xslt einige bösen Macken. Ich habe die aktualisierte Version wieder auf meine HP geladen. Der Link aus meinem alten Artikel zu dem Thema ist gleich geblieben, soll schließlich niemand das fehlerhafte Gelumpe laden.

Mit der neuen Version wird das MSBuild-File nun korrekt in die Build-Logs gemerged und das Xslt wertet die Ergebnisse korrekt aus. Es ist ein Logger für 2.0 und 3.5 enthalten.

Sonntag, Mai 04, 2008

MSBuild directory listing / batching

Immer wenn ich mit den Standard Boardmitteln von MSBuild versuche alle Verzeichnisse aufzulisten, so könnte ich verzweifeln. Immer die Verzeichnisse mittels DIR-Befehl in eine Datei schreiben und anschließend mittels ReadLine-Task auslesen.

    1   <Exec Command = "dir &quot; $(RefItemsPath) &quot; /b /A:D /S> &quot; $(SourceFolder)\refFolders.bbb &quot; " Condition = "'$(RefItemsPath)'!='' and '$(ReferencePath)'==''" />

    2   <Exec Command = "echo $(RefItemsPath)>> &quot; $(SourceFolder)\refFolders.bbb &quot; " Condition = "'$(RefItemsPath)'!='' and '$(ReferencePath)'==''" />

    3   <CreateItem Include = "$(SourceFolder)\refFolders.bbb" Condition = "'$(ReferenceFolder)'!='' and '$(ReferencePath)'==''" >

    4       <Output TaskParameter = "Include" ItemName = "refs" />

    5   </CreateItem>

    6   <ReadLinesFromFile File = "@(refs)" Condition = "'$(ReferenceFolder)'!='' and '$(ReferencePath)'==''" >

    7       <Output TaskParameter = "Lines" PropertyName = "readedLines" />

     8   </ReadLinesFromFile>

Aus meiner Sicht nicht wirklich schön, wenn man nur Verzeichnisse haben möchte, daher habe ich (endlich) einen neuen Task erstellt, da ich immer noch keinen in den CommunityTools gefunden habe. Der Task ist so super einfach, erspart mir aber immer einige Hacks, außerdem werden Build-Files verständlicher.

    3   using System;

    4   using System . Collections . Generic;

    5   using System . Text;

    6   using Microsoft . Build . Framework;

    7   using Microsoft . Build . Utilities;

    8   using System . Xml;

    9   using System . IO;

   10   using System . Collections;

   11   using System . Diagnostics;

   12  

   13   namespace DE . CapeVision . MSBuild . Tasks

   14  {

   15      public class DiretoryListingTask : Task

   16      {

   17          private string baseFolder;

   18          public bool IncludeSubFolders { get ; set ; }

   19          public string SearchPattern { get ; set ; }

   20          [ Output ]

   21          public string [] FolderContent { get ; set ; }

   22          [ Required ]

   23          public string BaseFolder

   24          {

   25              get { return baseFolder; }

   26              set { baseFolder = value ; }

   27          }

   28          public override bool Execute()

   29          {

   30   //#if(DEBUG)

   31   //            Debugger.Launch();

   32   //#endif

   33              if ( ! Directory . Exists( this . baseFolder))

   34                  return true ;

   35              string [] folders = Directory . GetDirectories(

   36                  this . baseFolder

   37                  , string . IsNullOrEmpty( this . SearchPattern) ? "*" : this . SearchPattern

   38                  , this . IncludeSubFolders ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly

   39                  );

   40              this . FolderContent = folders;

   41              return true ;

   42          }

   43      }

   44  }

Das 2. Thema, was ich in dem Zuge angehen wollte, war das Batching mal zu überprüfen. MsBuild ist in der Lage Befehle die mit TaskItems (array) aufgerufen werden zu parallelisieren, bzw. den Befehl immer wieder auszuführen.  Das Batching wird mittels des „%“-Operators angekündigt und alles weitere und ggf. Typenkonvertierungen für den Task macht MsBuild. Man kann das sehr gut mit einem einfachen Message-Task testen und nachstellen. Leider hatte meine Ausgabe nie wirklich geklappt, immer wenn 2 Listen verknüpft werden sollten, so kam folgendes raus:

  iis () --- BROWSER_VIEW
  iis () --- LOG_VIEW   iis () --- FILE_VIEW
  iis () --- CHANGESET_VIEW
  iis () --- TICKET_ADMIN
  iis () --- MILESTONE_ADMIN
  iis () --- ROADMAP_ADMIN
  iis () --- REPORT_ADMIN
  iis () --- WIKI_ADMIN
  iis () --- TIMELINE_VIEW
  iis () --- SEARCH_VIEW
  iis () --- TRAC_ADMIN
  iis (D:\Cache\Msdn\AdvancedBasics) ---
  iis (D:\Cache\Msdn\ContinuousIntegration) ---
  iis (D:\Cache\Msdn\DataPoints) ---
  iis (D:\Cache\Msdn\DependencyInjection) ---
  iis (D:\Cache\Msdn\ExtremeASPNET) ---
  iis (D:\Cache\Msdn\Foundations) ---
  iis (D:\Cache\Msdn\MVCFramework) ---
  iis (D:\Cache\Msdn\OfficeSpace) ---
  iis (D:\Cache\Msdn\TestRun) ---

   12   <Message Importance = "high" Text = "iis (%(folder.FullPath)) --- %(permission.Identity)" ></Message>

Nach etwas recherche fand ich auch ähnlich Probleme und irgendwo auch eine Lösung (Gute Erklärung http://blogs.msdn.com/aaronhallberg/archive/2006/09/05/msbuild-batching-generating-a-cross-product.aspx). Man muss die Ausgabe mittels MsBuild-Task in 2 Targets teilen, so dass der MsBuild-Tasks als Batch durchlaufen wird und anschließend der 2. Taks. Mein Skript sieht komplett so aus:

    1   <?xml version = "1.0" encoding = "utf-8" ?>

     2   <Project DefaultTargets = "Test" xmlns = "http://schemas.microsoft.com/developer/msbuild/2003" >

    3     <UsingTask AssemblyFile = "DE.CapeVision.MSBuild.Tasks.dll" TaskName = "DE.CapeVision.MSBuild.Tasks.DiretoryListingTask" />

    4     <ItemGroup>

    5       <permission Include = "BROWSER_VIEW;LOG_VIEW;FILE_VIEW;CHANGESET_VIEW;TICKET_ADMIN;MILESTONE_ADMIN;ROADMAP_ADMIN;REPORT_ADMIN;WIKI_ADMIN;TIMELINE_VIEW;SEARCH_VIEW;TRAC_ADMIN" />

    6     </ItemGroup>

    7     <Target Name = "Test" >

    8       <DiretoryListingTask BaseFolder = "D:\Cache\Msdn" >

    9         <Output TaskParameter = "FolderContent" ItemName = "folder" />

   10       </DiretoryListingTask>

   11       <Message Importance = "high" Text = "iis (%(folder.FullPath)) --- %(permission.Identity)" ></Message>

   12       <Message Importance = "high" Text = "second:" ></Message>

   13       <MSBuild Projects = "$(MSBuildProjectFile)" Targets = "InternalAddPermission" Properties = "Folder=%(folder.FullPath)" />

   14     </Target>

   15     <Target Name = "InternalAddPermission" >

   16       <Message Importance = "high" Text = "third:" ></Message>

   17       <Message Importance = "high" Text = "trac-admin $(folder) permission add tester %(permission.Identity)" ></Message>

    18     </Target>

   19   </Project>

Gebraucht habe ich beides um die Berechtigungen an den TRAC-Repositories anzupassen, die wir haben. (Dann natürlich nicht mit Message-Task ;))