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.

Samstag, Mai 10, 2008

Arbeit / Stress / Unzufriedenheit

Ich habe schon lange nichts mehr Privates geschrieben. Ein bisschen, weil ich nix neues habe, aber auch weil ich mir keine Zeit nehmen wollte oder konnte für das Bloggen. Hier nur einige wenige Informationen was alles so in den letzten Wochen passiert ist.

Eigentlich habe ich gerade 2 sehr interessante Projekte, die mir zumeist Spaß machen weil die Technologie einfach spannend ist. Leider laufen auch noch „Projektleichen“ nebenher, unteranderem auch eins, bei dem ich wieder mehr machen soll bis Ende des Jahres. L Durch die kleinen Leichen und die eigentlich nicht vorhandene Zeit, ist auch eine Menge Stress da. Ein anderer Kollege hat leider genau die gleichen Probleme und den gleichen Stress, in der letzten Woche sogar noch einiges mehr. Aber momentan ist das organisatorische das Schlimmste! Wenn ich mal in der Lage fühle Details dazu aufzuschreiben, dann wird es vielleicht auch verständlich. Momentan kotzt mich das richtig an, ich werde vielleicht nächste Woche mit einem meiner Chefs reden müssen. Ein bisschen Spannung mit einem Externen habe ich in einem Projekt, wo „alte“ auf „neue“ Philosophie trifft und die Technologieverständnisse schon deutlich unterschiedlich sind.

Privat ist mit mir immer noch gar nichts los, naja vielleicht fast gar nichts. In den letzten 2 Wochen war ich auf 2 Kindergeburtstagen (2 und 5), die sehr schön waren. Ich habe auch gelernt, dass man bei 6 5-jährigen Mädels am besten viel Ohropax dabei haben sollte. Ich habe mich außerdem mit 2 Ex-Kolleginnen mit deren Kindern getroffen. Wir haben uns einfach einen ruhigen und angenehmen Tag im Friedrichshain gemacht. Leider vermisse ich immer noch meine Ex-Freundin sehr, sie fehlt mir halt. Ich frage mich öfters, was sie wohl macht, wie es ihr geht. Im Frühling ist es richtig kacke, ich sehe überall nur Verliebte, Paare, Familien … Aber ich habe zum Glück soviel Arbeit, dass ich wenig Zeit finde (oder auch nehme), dass ich wenig Menschen sehe.

Nun ist erst mal Pfingsten ein bisschen Leichen wegräumen versuchen, Eltern besuchen, vielleicht Freunde treffen und noch Brüderchen besuchen. Es gibt einiges zu tun, also bis bald und schöne Feiertage.

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 ;))

Dienstag, April 29, 2008

Workflow Foundation Erkenntnisse (Hosting TODOs)

Ich habe die letzten Wochen Beratung unter anderem zum Thema Windows Workflow Foundation gemacht. Dabei sollte das Workflow Designer-Control für die Anpassung der WFs genutzt werden. Im Großen und Ganzen ein super Tool und sehr interessant und mächtig, allerdings hatte die Demo, die wir benutzt haben einige ärgerliche Probleme. Eine Version mit ausführlicher Anleitung kann beim Artikel „Windows Workflow Foundation: Everything About Re-Hosting the Workflow Designer“ heruntergeladen werden.

1.       Bei der Verwendung von Umlauten, bekommt die Anwendung arge Probleme, hier müssen einige Methoden in der Loader-Klasse angepasst werden.

In der Methode PerformFlush sollte das Encoding für den Writer gesetzt werden, hier der Auszug:

   244   if (rootActivity != null )

  245  {

  246      using ( XmlWriter xmlWriter = XmlWriter . Create(

  247          this . xoml

  248          , new XmlWriterSettings () { Encoding = Encoding . UTF8, OmitXmlDeclaration = false }))

  249      {

  250          WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer ();

  251          xomlSerializer . Serialize(xmlWriter, rootActivity);

   252      }

  253  }

Außerdem habe ich noch die Methode PerformLoad angepasst, hier der Auszug:

  193   Activity rootActivity = null ;

  194   using ( StreamReader sr = new StreamReader ( this . xoml, Encoding . UTF8, true ))

  195  {

  196      using ( XmlReader reader = XmlReader . Create( this . xoml))

  197      {

  198          WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer ();

  199          try

  200          {

  201              rootActivity = xomlSerializer . Deserialize(serializationManager, reader) as Activity ;

  202          }

  203          catch ( Exception ex)

  204          {

  205              Trace . WriteLine(ex);

  206          }

  207      }

  208      if (rootActivity != null && designerHost != null )

  209      {

  210          AddObjectGraphToDesignerHost(designerHost, rootActivity);

  211          Type companionType = rootActivity . GetValue( WorkflowMarkupSerializer . XClassProperty) as Type ;

  212          if (companionType != null )

  213              SetBaseComponentClassName(companionType . FullName);

   214      }

  215  }

Bei der letzen Änderung bin ich mir nicht sicher, aber dennoch der Hinweis. Die Methode GetRootActivity in der Helper-Klasse könnte ebenfalls zu Problemen führen, auch hier eine kleine Anpassung:

   52   internal static Activity GetRootActivity( string fileName, IServiceProvider serviceProvider)

   53  {

   54      Activity rootActivity = null ;

   55      using ( StreamReader sr = new StreamReader (fileName, Encoding . UTF8, true ))

   56      {

   57          using ( XmlReader reader = XmlReader . Create(sr

   58              , new XmlReaderSettings () { XmlResolver = new System . Xml . XmlUrlResolver () }))

   59          {

   60              WorkflowMarkupSerializer xomlSerializer = new WorkflowMarkupSerializer ();

   61              DesignerSerializationManager ser = new DesignerSerializationManager (serviceProvider);

   62              ser . CreateSession();

   63              rootActivity = xomlSerializer . Deserialize(ser, reader) as Activity ;

    64          }

   65      }

   66      return rootActivity;

   67  }

2.       Die Nutzung einer Konfigurationsdatei für die möglichen Controls finde ich mehr als ungünstig, hier sollte man die AddToolboxEntries-Methode der ToolboxService-Klasse anpassen, in dem alle Assemblies (aus dem aktuellen und dessen Unterverzeichnis) durchlaufen werden und geprüft wird, ob Klassen mit der Basis-Klasse „Activity“ vorhanden sind. Außerdem nicht vergessen die Standard Activities zu laden. Das Durchlaufen der Assemblies im Ordner ist in den Code-Zeilen nicht vorhanden.

   546   Assembly assembly = typeof (System . Workflow . Activities . ActiveDirectoryRole ) . Assembly;

  547  LoopAssemblyTypes(lb, assembly);

  548  assembly = typeof ( TerminateActivity ) . Assembly;

  549  LoopAssemblyTypes(lb, assembly);

  550   //VS2008 required

  551  assembly = typeof ( SendActivity ) . Assembly;

   552  LoopAssemblyTypes(lb, assembly);

Hier die aufgerufene Methode (Außerdem wird der Assembly-Name als Titel verwendet):

   555   private void LoopAssemblyTypes( ListBox lb, Assembly assembly)

  556  {

  557      Type [] assemblyTypes = assembly . GetTypes();

  558      bool groupSet = false ;

  559      foreach ( Type assemblyType in assemblyTypes)

  560      {

  561          if (assemblyType . IsAbstract || ! assemblyType . IsClass || ! assemblyType . IsSubclassOf( typeof ( Activity )))

  562          {

  563              Debug . WriteLine( string . Format( "Class '{0}' not a valid Activity!" , assemblyType . FullName), "ToolboxService.LoopAssemblyTypes" );

  564          }

  565          else

  566          {

  567              Trace . WriteLine( string . Format( "Loading '{0}' into toolbox!" , assemblyType . FullName), "ToolboxService.LoopAssemblyTypes" );

  568              SelfHostToolboxItem item = new SelfHostToolboxItem (assemblyType, assemblyType . FullName);

  569              tbItems . Add(item);

  570              if ( ! groupSet)

  571              {

  572                  lb . Items . Add(assembly . FullName . Split( new char [] { ',' }, 2 )[ 0 ]);

  573                  groupSet = true ;

  574              }

  575              lb . Items . Add(item);

   576          }

  577      }

  578  }

3.       Übergeben der Typen an den TypeProvider dazu die Methode Initialize des Loaders erweitern. (Wird der TypeProvider nicht erweitert mit den Assemblies und dieser auch überall übergeben, so kann das der Grund sein, warum definierte Workflows nicht geladen werden. (Es gibt auch keine Fehlermeldung)

  102   foreach ( SelfHostToolboxItem toolboxItem in toolbox . Items)

   103  {

  104      typeProvider . AddAssembly(toolboxItem . Compon entClass . Assembly);

  105  }

  106  host . AddService( typeof ( ITypeProvider ), typeProvider, true );

4.       Außerdem habe ich einen ToolTip in die Anwendung integriert, der das DescriptionAttribute auswertet und ausgibt. Dazu muss kräftig in die ToolboxService-Klasse eingegriffen werden.

Die erste Änderung in der OnListBoxMouseMove

  692   int currentIndex = listBox . IndexFromPoint( new Point (e . X, e . Y));

  693   if (currentIndex >= 0 )

  694  {

  695      SelfHostToolboxItem item = listBox . Items[currentIndex] as SelfHostToolboxItem ;

  696      if (item == null || string . IsNullOrEmpty(item . Tooltip))

  697      {

  698          this . tooltip = null ;

  699  

  700          currentHighlightItemIndex = Int32 . MinValue;

  701      }

  702      else

  703      {

  704          {

  705              // ToolTip setzen

  706              if (currentHighlightItemIndex != currentIndex)

  707              {

  708                  this . tooltip = item . Tooltip;

  709                  currentHighlightItemIndex = currentIndex;

  710                  this . tp . Show(tooltip, this , new Point (e . X, e . Y));

   711              }

  712          }

  713      }

  714  }

Auf Klassenebene sind die 3 Fields definiert

  717   ToolTip tp = new ToolTip ();

  718   string tooltip;

   719   int currentHighlightItemIndex;

Außer den beschriebenen Anpassungen kann man sich durchaus auch noch kräftig in der OnDrawItem-Methode austoben, hier kann man die Anzeige den eigenen Wünschen anpassen.

Man muss/sollte in seinen Workflows-Assemblies unbedingt das XmlnsDefinitionAttribute verwenden und einen für sich eindeutigen Namespace definieren. Mittels des Attributes wird die Auflösung der Namepsaces im Xml (Xoml-Workflow) und der CLR gesteuert. Kommt es zu Problemen beim Laden der Workflows im Designer, obwohl der TypeProvider überall mitgegeben wird, so kann man sich mit einem Trick weiterhelfen. Das XmlnsDefinitionAttribute wird auf den CLR-Namespace gesetzt, zum Beispiel xmlns:cv="clr-namespace:DE.CapeVision.Workflow.MainActivities;assembly=CapeVisionMainActivityLib". Viel mehr Details dazu unter XAML Namespaces and Namespace Mapping.