mercredi 30 juillet 2008

Créer des tâches personnalisées pour un Team Build

But

Créer une tâche personnalisée qui sera exécutable par un serveur de build Team System.

Pré requis

Configuration de la machine de développement :
La création de build custom task se fait en VB ou en C# dans Visual Studio et se base sur l'API de Team System. Donc il faut un Visual Studio + l'installation du SDK Team System ou plus simple, Visual Studio Team System.

Configuration du serveur de build (Team Build) :
Pour que le serveur de build puisse compiler la documentation du code en se basant sur SandCastle, il faut bien sur installer SandCastle... Logique non ? Allez voir sur http://www.codeplex.com pour avoir la dernière version.
Ensuite il faut installer SandCastleBuilder sur le serveur de build (l'application qui permettra de compiler l'aide grâce à des projets de documentation), voir aussi sur http://www.codeplex.com pour le téléchargement.


Scénario 1 :
Le serveur de build exécute les tâches qui sont définies dans le fichier tfsbuild.proj. Le format de ce fichier de projet est écrit dans le standard MSBuild et propose différentes choses, dont notamment la possibilité d'exécuter une ligne de commande. On va donc pouvoir compiler la documentation de notre code avec SandCastleBuilder (voir pré requis dans le scénario 2) en l'appelant depuis le fichier de projet du team build.

Le code de l'exemple :

<target name="GenerateDocumentation">
<propertygroup>
<sandcastlebuidlerpath>C:\Program Files\EWSoftware\Sandcastle Help File Builder\SandcastleBuilderConsole.exe</sandcastlebuidlerpath>
<sandcastlebuilderprojectfile>..\Sources\DemoDocumentation\DemoDocumentation.shfb</sandcastlebuilderprojectfile>
<sandcastlebuilderarguments>-OutputPath="$(OutDir)\"</sandcastlebuilderarguments>
</propertygroup>
<exec command="">
</exec>

Scénario 2 :
On étudie ici l'ajout d'une tâche qui consiste à compiler la documentation du code avec SandCastleBuilder, après la compilation du code.
Le serveur de build va donc exécuter les tâches dans l'ordre suivant :

  1. prendre la dernière version des sources
  2. compiler la solution (sln)
  3. exécuter les tests unitaires existants
  4. compiler la documentation du code compilé en 1

Création de la custom task :
Une custom task est encapsulée dans une Dll .Net qui sera référencée dans le fichier de projet du Team Build (tfsbuild.proj). Pour rappel ce fichier de projet est un fichier type MSBuild, donc pour plus d'informations sur ce format rechercher MSBuild dans Google.

  1. Nous créons un projet de type ClassLibrary dans Visual Studio
  2. Nous lui ajoutons les références aux assemblies de Team System :
    • . Microsoft.Build.Framework
    • . Microsoft.TeamFoundation.Client
    • . Microsoft.TeamFoundation.Build.Common
  3. On supprime Class1.cs qui ne sert à rien...
  4. On ajoute notre nouvelle classe qui va nous permettre de compiler la documentation de code. Je vais l'appeler BuildHelp.
  5. Microsoft nous propose 2 manières d'implémenter une classe tâche de build. Soit hériter de la classe Microsoft.Build.Utilities.Task ou alors implémenter l'interface Microsoft.Build.Framework.ITask. Dans notre exemple nous prendrons la première solution, plus rapide à implémenter.
  6. La classe Task est une classe abstraite et définie la méthode Execute() comme abstraite, nous la surchargeons donc dans notre classe BuildHelp. C'est cette méthode qui sera invoquée par le serveur de build pour déclencher le travail de notre custom task.
  7. Le code de notre fonction Execute() est plutôt simple puisqu'il consiste à appeler le programme SandCastleBuilderConsole.exe en lui passant en paramètres, le projet SandCastleBuilder qui contient tous les paramètres de génération de documentation.

Voici du code simple (pour l'exemple) pour faire ceci :


public override bool Execute()
{
// Création du processInfo
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(@"SandcastleBuilderConsole.exe");
psi.Arguments = "monProjet.shfb";
psi.RedirectStandardOutput = true;
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
psi.UseShellExecute = false;
System.Diagnostics.Process sandCastleBuilder = null;

// La fonction Log.LogCommandLine() est fournie par Team System et permet d'écrire dans le journal de build
Log.LogCommandLine(MessageImportance.Low, @"SandcastleBuilderConsole.exe" + " " + "monProjet.shfb");
sandCastleBuilder = System.Diagnostics.Process.Start(psi); // Démarre le programme de compilation de la documentation
sandCastleBuilder.WaitForExit(MAX_WAITING_TIME);

return true;
}

La valeur que retourne la fonction Execute() permet au serveur de build de connaitre le résultat de la tâche qu'il a démarré. True la tâche s'est déroulée avec succès, sinon false.

8- On compile la Dll.
9- Pour ajouter la custom task au serveur de build, on édite le fichier projet du serveur de build, le fameux fichier tfsbuild.proj. On lui ajoute notre tâche à la fin après la dernière balise <ItemGroup>:
<usingtask taskname="Reseaux.TeamSystem.Build.CustomTask.BuildHelp" assemblyfile="$(SolutionRoot)\\DemoDocumentation\\Reseaux.TeamSystem.Build.CustomTasks.dll">
<target name="GenerateDocumentation">
<buildhelp>
</buildhelp>
10- Pour pouvoir exécuter notre custom task, le serveur de build doit pourvoir la trouver, donc on peut la mettre dans le répertoire de la solution (il y a d'autres méthodes, voir dans Google et/ou les MSDN). On ajoute donc la Dll générée qui contient notre custom task, dans le contrôle de code source Team System, à coté du fichier sln que notre serveur de build compile.
11- On archive tout (le fichier projet du TeamBuild, la Dll de la customTask) et on démarre un nouveau build.

En principe dans le répertoire de sortie définit par le projet SandCastleBuilder, vous devriez y trouver l'aide compilée.
Si ce n'est pas le cas aller voir dans le fichier de log du serveur de build et rechercher l'appel à votre custom task pour voir si une erreur est remontée.

Ajouter l'output de SandCastleBuilder : Bon aller il y a de grandes chances pour que le shfb qui tourne en local se comporte autrement sur le serveur de build, donc ça serait cool d'avoir l'output de SansCastleBuilderConsole dans le fichier de log du serveur de build. Pour faire ça, on remplace le code de notre Custom Task pour obtenir ceci :

public override bool Execute()
{
bool result = false;
string applicationName = @"SandcastleBuilderConsole.exe";
string parameters = "monProjet.shfb";
System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(applicationName);
psi.Arguments = parameters;
psi.RedirectStandardOutput = true;
psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
psi.UseShellExecute = false;
System.Diagnostics.Process sandCastleBuilder = null;

try
{
Log.LogCommandLine(MessageImportance.Low, applicationName + " " + parameters);
sandCastleBuilder = System.Diagnostics.Process.Start(psi);
}
catch (ObjectDisposedException e)
{
Log.LogErrorFromException(e, true);
}
catch (InvalidOperationException e)
{
Log.LogErrorFromException(e, true);
}
catch (ArgumentNullException e)
{
Log.LogErrorFromException(e, true);
}
catch (Win32Exception e)
{
Log.LogErrorFromException(e, true);
}
finally
{
if (sandCastleBuilder != null)
sandCastleBuilder.WaitForExit(MAX_WAITING_TIME);
}

try
{
string errorMessage = string.Empty;
string logMessage = string.Format("No output available for {0}", applicationName);
if (sandCastleBuilder.HasExited)
{
string applicationOutput = sandCastleBuilder.StandardOutput.ReadToEnd();
if (sandCastleBuilder.StandardOutput != null)
logMessage = string.Format("See {0} output bellow :\r\n{1}", applicationName, applicationOutput);

if (applicationOutput.IndexOf("Fatal error") != -1)
errorMessage = "See previous error in application output";
else
result = true;
}

Log.LogMessage(logMessage); // Affiche la sortie de l'outil
if (errorMessage.Length > 0)
Log.LogError(errorMessage, null);
}
catch (InvalidOperationException e)
{
Log.LogErrorFromException(e, true);
}
catch (Win32Exception e)
{
Log.LogErrorFromException(e, true);
}
catch (NotSupportedException e)
{
Log.LogErrorFromException(e, true);
}

return result;
}

Rendre les arguments de notre custom task paramétrables, dans le fichier projet Team Build :
Ce qui est pratique c'est qu'on peut facilement définir des paramètres à notre classe custom task. Ces paramètres sont automatiquement lus dans le fichier projet et envoyé à notre custom task par le serveur de build.

Il faut donc ajouter une propriété publique à notre classe BuildHelp. Cette propriété doit portée l'attribut [Required].
Par exemple on peut définir le nom et l'emplacement du fichier projet de documentation à compiler (.shfb)

[Required]
public string SandCastleBuilderProjectFile
{
get { return _sandCastleBuilderProjectFile; }
set { _sandCastleBuilderProjectFile = value; }
}

Et dans le fichier projet, on ajoute le paramètre :

<buildhelp sandcastlebuilderprojectfile=""..\Sources\DemoDocumentation\DemoDocumentation.shfb" ">
</buildhelp></target>/<usingtask></target>

Voilà pour l'essentiel.

Sur mes projets, à ce jour j'ai préféré n'utiliser que la configuraion du fichier TFSBuild.proj en faisant appel aux outils externes via la commande <exec.../> utilisable dans les fichiers MSBUILD. Ca me permet de faire ce dont j'ai besoin (compilation de la documentation de code, regroupement de Dll en une seule et packaging dans un MSI) sans avoir à maintenir de code supplémentaire qui serait induit par l'utilisation de custom task codée en C# ou VB.

J'espère que ça vous aidera à démarrer sur le sujet.

Bien sur, Google est très parlant sur le sujet.

Bon codage à toutes et tous...

jeudi 17 juillet 2008

TeamSystem : Comment supprimer définitivement un WorkItem

Aller sur le serveur TeamSystem et exécuter la commande qui suit :
tfpt destroywi /server:SERVERNAME /workitemid:IDWORKITEM

La commande tfpt.exe fait partie des PowerTools de Team System :
Télécharger les ici http://msdn2.microsoft.com/en-us/tfs2008/bb980963.aspx

Ajouter vos assemblies dans la boite de dialogue "Add References" de Visual Studio


Quoi de plus chouette lorsqu'on distribue sa/ses Dll, que de la voir apparaitre dans la fenêtre "Add References" de Visual Studio ?
Certainement plein de choses, mais ce petit tips fait bon effet et peut faciliter la vie des clients de votre Dll.

Il suffit d'ajouter une clé de registre ici HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AssemblyFolders\ avec comme valeur le chemin vers le répertoire contenant votre / vos Dll.

Exemple :
- je veux ajouter les Dll contenues dans le dossier C:\Program Files\Common Files\MonProduit, il faut ajouter la clé suivante dans la base de registre :
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AssemblyFolders\MonProduit
@="C:\\Program Files\\Common Files\\MonProduit"

La modification sera prise en compte lors du démarrage d'une nouvelle instance de Visual Studio.

Voilà ! Et bon codage.