Intégration Continue avec Visual Studio et Team Foundation Server - Partie II, Présentation de MsBuild et de TfsBuild

Pierre-Emmanuel Dautreppe

Nous allons présenter ici l'intégration continue dans le monde Microsoft.NET, c'est-à-dire avec Visual Studio.NET et le Team Foundation Server.
L'intégration continue s'articule autour de quatre outils:

- MsTest pour écrire des tests automatisés ;

- Static Code Analysis pour valider le code écrit selon un jeu de règles préféfinies ;

- MsBuild pour automatiser un certain nombre de tâches (compilation, déploiement…) ;

- TFSBuild pour automatiser les tâches liées aux builds automatisés .

Nous avions vu dans une première partie à la fois une introduction à l'intégration continue et une description très détaillée des outils MsTest et Static Code Analysis.

Cet article, quant à lui présente en détail les deux derniers outils : MsBuild et TfsBuild et répondra donc aux questions suivantes :

- comment automatiser des tâches grâce à MsBuild ;

- comment réaliser un build automatisé (et le paramétrer) grâce à TfsBuild.

- Titre : Intégration Continue avec Visual Studio et Team Foundation Server - Partie II, Présentation de MsBuild et de TfsBuild

- Auteur : Pierre-Emmanuel Dautreppe

- Parution : 29 avril 2009

- Mise à jour : 17 juin 2020

- Licence : Copyright ® 2009 Pierre-Emmanuel Dautreppe . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Contexte

- II. Présentation brève de MsBuild

- III. Premiers pas avec MsBuild : Ecrire un fichier de build de base

- III-A. Structure d'un fichier de build

- III-B. Qu'est-ce qu'une tâche / Task ?

- III-C. Qu'est-ce qu'un target ?

- III-D. Création d'un fichier de build

- III-E. Exécuter un fichier de build

- IV. Aller plus loin avec MsBuild

- IV-A. S'assurer de la validité du fichier de build

- IV-B. Propriétés d'un fichier de build : les Properties et PropertyGroup

- IV-B-1. Définition

- IV-B-2. Création statique de propriétés

- IV-B-3. Utilisation de Properties

- IV-B-4. Précaution d'emploi : Ordre de déclaration

- IV-B-5. Création dynamique de Properties

- IV-B-5-a. Définition

- IV-B-5-b. Différence entre création statique et dynamique

- IV-B-5-c. Syntaxe de la tâche CreateProperty

- IV-B-5-d. Exemple d'utilisation

- IV-B-6. Changer la valeur d'une propriété

- IV-B-7. Différer la création d'un PropertyGroup

- IV-C. Listes d'un fichier de build : les Items et ItemGroup

- IV-C-1. Définition

- IV-C-2. Création statique d'Items

- IV-C-3. Utilisation d'Items

- IV-C-4. Création dynamique d'Items

- IV-C-4-a. Syntaxe et exemple

- IV-C-4-b. Cas particulier lors de l'utilisation de metadata

- IV-C-5. Précautions lors de l'utilisation d'Items dans des tâches

- IV-C-6. Différer la création d'un ItemGroup

- IV-D. Exécuter un fichier de build

- IV-D-1. Exécution par défaut

- IV-D-2. Surcharge du target à exécuter

- IV-D-3. Surcharge d'une propriété

- IV-D-4. Surcharge de la verbosité d'exécution

- IV-D-5. Omission du nom du fichier

- IV-E. Référencer une tâche du framework 3.5

- IV-F. Plus loin avec les Targets

- IV-F-1. Contrôler l'ordre d'exécution des Targets

- IV-F-1-a. Définition

- IV-F-1-b. Exemple

- IV-F-1-c. Cas des dépendances multiples

- IV-F-2. Appeler explicitement un Target

- IV-F-2-a. Appeler un target via CallTarget

- IV-F-2-b. Appeler un target via MsBuild

- IV-F-2-c. Choisir CallTarget ou MsBuild

- IV-F-2-d. La tâche Exec

- IV-F-3. Passer des paramètres à un Target

- IV-F-3-a. Paramètres d'entrée

- IV-F-3-b. Paramètres de sortie

- V. Factoriser un fichier de build

- V-A. Principes de factorisation

- V-B. Utilisation de fichier .targets

- V-C. Précautions à observer

- V-C-1. Positionnement de la commande d'Import

- V-C-1-a. Redéfinition d'une propriété

- V-C-1-b. Redéfinition d'un target

- V-C-2. Importer un fichier du source control

- VI. Créer ses propres tâches

- VI-A. Créer une tâche simple

- VI-B. Ajouter des entrées sur sa tâche

- VI-B-1. Ajouter des entrées optionnelles

- VI-B-2. Ajouter des entrées obligatoires

- VI-C. Ajouter des sorties sur sa tâche

- VI-D. Lever des erreurs

- VI-E. Déboguer sa tâche

- VII. Introduction à Team Foundation Server (TFS)

- VII-A. Présentation rapide des possibilités du Team Foundation Server

- VII-B. Présentation des différents serveurs entrant en jeu

- VII-C. TFS et Gestion des Droits d'Accès

- VIII. TFS et Intégration Continue

- VIII-A. Créer un build

- VIII-B. Éditer un build

- VIII-C. Exécuter un build

- VIII-C-1. Via VS

- VIII-C-2. En ligne de commande

- VIII-D. Paramétrer un build

- VIII-D-1. Quelques tâches disponibles

- VIII-D-1-a. Microsoft.Build.Tasks

- VIII-D-1-b. Tâches liées au TFS

- VIII-D-2. Quels sont les targets que l'on peut overrider

- IX. TFS et Gestion

- IX-A. Quelques commandes utiles

- IX-A-1. Détruire des fichiers dans le Source Control

- IX-A-2. Faire un « Undo » sur des fichiers

- IX-A-3. Lister les fichiers actuellement en Check-Out

- IX-B. Présentation de TFS Power Tools

- IX-B-1. Nouvelles « check-in policies »

- IX-B-2. Menu contextuel sur les membres d'un Team

- IX-B-3. Gestion simplifiée des alertes

- X. Tips & Tricks

- X-A. Où puis-je trouver des tâches supplémentaires ?

- X-B. Comment modifier le processus de « Get » dans le build ?

- X-B-1. En modifiant le workspace utilisé par le build

- X-B-2. En paramétrant la tâche « Get » qui est faite par le serveur

- X-B-3. En réécrivant la tâche « Get »

- X-C. Comment peut-on récupérer des sources de plusieurs Team Project ?

- X-D. Je crée une propriété utilisant le « BuildNumber », mais celle-ci est vide. Pourquoi ?

- XI. Conclusion

- Remerciements

- Contact

- XII. Annexes - Bibliographie

- XII-A. Structure d'un fichier de build

- XII-B. Table des codes ASCII

- XII-C. Conditions utilisables dans un fichier de build

- XII-D. Différents niveaux de verbosité

- XII-E. Liste des tâches fournies dans Microsoft.Build.Tasks

- XII-F. Les différentes possibilités de gestion offertes par « tf.exe »

I - Contexte

Nous avions vu - dans la première partie de cet article - une présentation de l'intégration continue, ainsi que des outils utiles dans le monde Mirosoft.NET : MsTest et Static Code Analysis.

MsTest nous permettait des tests unitaires pour nos projets, tandis que Static Code Analysis nous permettait de valider notre code source contre une série de règles préétablies.

Nous allons voir maintenant deux autres outils : MsBuild - disponible depuis Visual Studio 2005 (et ce quelque soit la version) - et TfsBuild.

De nombreux liens vers la MSDN seront également donnés de façon à permettre au lecteur d'approfondir davantage les sujets traités.
Notez que ces liens seront toujours donnés vers la MSDN online.
Tous ces liens seront regroupés dans l'appendice (voir la bibliographie) et seront aussi donnés vers une installation locale de la MSDN (en supposant une utilisation de la MSDN fournie avec Visual Studio 2008 SP1).

II - Présentation brève de MsBuild

MsBuild est la pierre angulaire de l'intégration continue dans le monde Microsoft : c'est l'outil qui permet l'automatisation de tâches diverses (compilation, déploiement d'une solution, exécution de tests…).

La communauté Microsoft avait déjà fourni des outils d'intégration continue - pour la plupart des portages du monde Java - comme CruiseControl.NET et NAnt. Mais c'est à partir de la version 2005 que Microsoft inclut son propre moteur au sein de Visual Studio.

Il est même utilisé en interne pour la compilation des solutions et des projets. Il est donc beaucoup plus « simple » (ou tout du moins envisageable sans refaire tout le processus en ligne de commande) de paramétrer la compilation de nos projets dans Visual Studio en modifiant le fichier de build utilisé par Visual Studio. (1)

MsBuild permet en fait d'exécuter un fichier de projet (ou fichier de build), traditionnellement d'extension « .proj » ou « .target ». (2) Il s'agit en fait d'un fichier XML représentant une série d'actions.

Ce fichier de projet génèrera une fois exécuté un fichier texte comportant tous les messages, avertissements ou erreurs relatifs à l'exécution.

L'outil MsBuild est installé avec le framework, autrement dit dans le répertoire « %Windir%\Microsoft.NET\Framework\VersionFramework », soit typiquement dans les répertoires :

- « %Windir%\Microsoft.NET\Framework\v2.0.50727 » pour .NET 2.0 ;

- « %Windir%\Microsoft.NET\Framework\v3.5 » pour .NET 3.5.

III - Premiers pas avec MsBuild : Ecrire un fichier de build de base

Maintenant que l'on sait comment MsBuild peut être utilisé, voyons comment écrire un fichier de build de base.

III-A - Structure d'un fichier de build

Pour plus de détails sur la structure d'un fichier, vous pouvez vous rendre dans les annexes dans la section « Structure d'un fichier de build ».

Un fichier de build est un simple fichier XML pour lequel plusieurs extensions sont possibles. Même si toutes les extensions peuvent être utilisées (seuls le format du fichier et son contenu seront importants), seules les extensions suivantes seront comprises par Visual Studio et permettront de la coloration syntaxique et une autocomplétion (intellisense) partielle.

Extensions autorisées pour un fichier de projet

Extension

Utilisation

.proj

Un fichier de projet personnalisé, ou un fichier entrant dans le cadre de l'intégration continue

.targets

Utilisé en interne par Visual Studio. Ces fichiers ne sont pas utilisés directement, mais sont en général importés par d'autres fichiers de projet. Ils peuvent être vus comme des bibliothèques de targets. (3)

.xxxproj
(.csproj, .vbproj…)

Fichier de projets .NET. Ce sont ces fichiers qui serviront à la compilation des projets

Notez que le fichier XML doit bien sûr être valide. Nous verrons plus en détail comment nous en assurer à la section « S'assurer de la validité du fichier de build ».

Dans un premier temps, contentons-nous de savoir qu'un fichier de build vide a la structure suivante :

Fichier de build vide

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

</Project>

III-B - Qu'est-ce qu'une tâche / Task ?

Comme nous disions dans l'introduction, un fichier de build nous permet de réaliser des tâches diverses, c'est-à-dire des actions. De quel type d'action parlons-nous ? Citons par exemple :

- compiler un projet ;

- écrire un message d'avertissement ;

- exécuter un script ou exécuter le backup d'une base de données ;

- créer un répertoire ou copier un fichier ;

- créer un nouveau site web dans un IIS ;

- redémarrer un serveur distant ;

- créer un fichier de déploiement (zip, msi, wix…).

Cette liste n'est qu'un court exemple du type de tâche que l'on peut réaliser au cours d'une intégration continue : on peut effectivement virtuellement exécuter n'importe quelle action. (4)

Pour avancer, contentons-nous de savoir qu'il existe certaines tâches dans le framework .NET, la plus simple étant la tâche Message permettant d'écrire du texte. Une tâche Message s'utilise de la manière suivante :

Tâche Message

<Message Text="Ceci est le texte qui sera généré par la tâche" />

III-C - Qu'est-ce qu'un target ?

Un Target a deux fonctions spécifiques :

- être un point d'entrée pour le fichier de projet ;

- être un regroupement d'actions.

Un fichier de build va être typiquement comme un programme procédural, qui va appeler des méthodes. Chacune de ces méthodes répondant logiquement aux concepts de cohésion et de responsabilité.

Les targets sont en fait les méthodes d'un fichier de build, méthodes qui pourraient être exécutées via le « programme » (ie lors de l'exécution du fichier de build) ou indépendamment (on exécute une des méthodes du programme).

Un fichier de build peut donc comporter un ou plusieurs target, et comme un target est un point d'entrée du fichier de build, un fichier sans target ne sera pas exécutable.

Création d'un Target dans un fichier de projet

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

<Target Name="NomDuTarget">
</Target>

</Project>

III-D - Création d'un fichier de build

Si on assemble alors les éléments précédents, on a notre fichier de build minimal qui va afficher un simple message :

Fichier de build affichant un message

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

<Target Name="NomDuTarget">
<Message Text="Ceci est le texte qui sera généré par la tache" />
</Target>

</Project>

III-E - Exécuter un fichier de build

Pour exécuter le fichier ci-dessus, nous supposons avoir fait les étapes suivantes :

- le contenu a été sauvegardé dans un fichier appelé « Fichier de base.proj » dans le répertoire « D:\ Fichiers de build » ;

- vous possédez une version du framework .NET 2.0 ou supérieur.

Exécuter un fichier de build

MsBuild "Fichier de base.proj"

Cela produira alors le résultat suivant :

Résultat de l'exécution d'un fichier de build

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 6/01/2009 18:31:52.
Project "D:\Fichiers de build\Fichier de base.proj" on node 0 (default targets).
Ceci est le texte qui sera gendre par la tâche
Done Building Project "D:\Fichiers de build\Fichier de base.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.31

Le code précédent ne fonctionnera bien sûr que si vous avez ajouté le chemin complet de MsBuild dans la variable d'environnement Path. Dans le cas contraire, vous devrez indiquer le chemin complet pour l'exécution.

IV - Aller plus loin avec MsBuild

Nous avons exposé jusque là le cas le plus basique d'un fichier de build. Nous allons maintenant aller beaucoup plus loin pour présenter les outils qui seront réellement nécessaires pour automatiser nos tâches quotidiennes.

IV-A - S'assurer de la validité du fichier de build

Le fichier de build est un fichier XML et doit donc être valide par rapport aux spécifications XML. Il faut donc faire attention aux valeurs que l'on veut spécifier dans les éléments et dans les attributs.

Prenons deux exemples classiques d'erreurs dans un fichier XML :

Exemple d'erreurs dans un fichier de build et leurs corrections

<!-- Erreur, car présence de "&" dans une balise -->
<ElementInvalide>Valeur 1 & Valeur 2</ElementInvalide>
<!-- Correction ici par un bloc CDATA -->
<ElementValide><![CDATA[Valeur1 & Valeur2]]></ElementValide>

<!-- Erreur, car l'attribut possède un guillemet -->
<ElementAvecAttributInvalide Attribut="Je possède une (")"/>
<!-- Correction en utilisant le code ASCII -->
<ElementAvecAttributValide Attribut="Je possède une (%22)"/>

Pour corriger les erreurs tout en possédant un fichier cohérent, il faudra parfois avoir recours à des astuces, d'autant que certains caractères sont réservés par MsBuild et ne peuvent donc pas être utilisés tels quels. On pourra noter les astuces suivantes :

- utilisation de blocs CDATA pour protéger un élément ;

- utilisation de codes ASCII pour remplacer les caractères réservés par MsBuild ;

- utilisation de propriétés lorsque l'on n'est pas capable de protéger correctement ce que l'on veut mettre dans un attribut (dans la source ci-dessus, plutôt que d'utiliser « %22 », on aurait ainsi pu déplacer la valeur dans une propriété, et référencer la propriété. Nous verrons ici comment utiliser les propriétés) ;

- utilisation des correspondances HTML de certains caractères

Quelques caractères réservés et leurs équivalents protégés

Caractère réservé

Caractère protégé

&

&amp;

<

&lt;

>

&gt;

Caractère réservé

Caractère ASCII

"

%22

;

%3B

*

%2A

Pour voir la liste des codes ASCII, on peut aller voir sur www.asciitable.com ou dans la MSDN. Plus d'informations dans les annexes : « Table des codes ASCII ».

IV-B - Propriétés d'un fichier de build : les Properties et PropertyGroup

IV-B-1 - Définition

On peut définir des Properties que l'on pourra utiliser dans notre fichier de projet.

Une property dans un fichier de build est très similaire à une propriété en .NET ou à une variable contenant une valeur simple (string, int, double…).

Ces propriétés pourront être créées statiquement - par simple déclaration dans le fichier - ou bien dynamiquement - lors de l'exécution du fichier de build.

IV-B-2 - Création statique de propriétés

Pour créer une property, on peut ajouter dans notre fichier un élément « PropertyGroup » qui peut regrouper plusieurs éléments, chacun d'entre eux étant une nouvelle property. Notez que l'on peut retrouver plusieurs éléments « PropertyGroup » dans un même fichier de projet.

Création de deux propriétés

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

<PropertyGroup>
<MaPropriete1>Test1</MaPropriete1>
<MaPropriete2>Test2</MaPropriete2>
</PropertyGroup>

</Project>

Dans le source ci-dessus, nous avons créé :

- une propriété « MaPropriete1 » de valeur « Test1 » ;

- une propriété « MaPropriete2 » de valeur « Test2 ».

IV-B-3 - Utilisation de Properties

Utiliser une propriété est très simple. Il suffit d'utiliser la syntaxe « $(NomPropriete) ».

Création de la propriété « FileNameWithExt » en utilisant deux propriétés

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

<PropertyGroup>
<FileName>Mastering Regular Expressions</FileName>
<Extension>pdf</Extension>
<FileNameWithExt>$(FileName).$(Extension)</FileNameWithExt>
</PropertyGroup>

</Project>

Notez cependant qu'il est important que les propriétés soient définies avant leur appel. Pour cela, il est important de prendre en compte les différences signalées entre « PropertyGroup » et « CreateProperty » détaillées dans le point lié à la « Création dynamique des propriétés ».

IV-B-4 - Précaution d'emploi : Ordre de déclaration

Il faut être vigilant à l'ordre de création des propriétés. En effet la création des propriétés (définies via PropertyGroup) se fait lors de l'initialisation du fichier, mais ce, de haut en bas.

L'ordre de définition des propriétés est important

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

<Target Name="Target1">
<Message Text="Dans Target 1" />
<Message Text="$(Prop1)" />
<Message Text="$(Prop2)" />
</Target>

<PropertyGroup>
<Prop1>Prop1 : $(PropBase)</Prop1>
<PropBase>test</PropBase>
<Prop2>Prop2 : $(PropBase)</Prop2>
</PropertyGroup>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:16:51.
Project "C:\Temp\build.proj" on node 0 (default targets).
Dans Target 1
Prop1 :
Prop2 : test
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.03

Dans l'exemple ci-dessus, on se rend compte que l'affichage de la propriété Prop1 ne donne pas le résultat escompté, puisque l'on utilise PropBase qui est déclaré après Prop1. L'affichage de Prop2 quant à lui est bien correct.

On se rend compte alors que les propriétés ont bien été initialisées avant l'exécution du Target, mais en respectant leur ordre de déclaration, du haut vers le bas.

IV-B-5 - Création dynamique de Properties

IV-B-5-a - Définition

Il est également possible d'utiliser une tâche CreateProperty pour créer dynamiquement une propriété. Cela permet par exemple d'utiliser des variables qui sont retournées par une autre tâche.

IV-B-5-b - Différence entre création statique et dynamique

Il faut cependant être très conscient des différences entre création statique et dynamique.

Création statique

Toutes les variables (propriétés ou items) utilisées pour créer une propriété dans un PropertyGroup doivent exister au moment de la création (et donc il ne peut s'agir du résultat d'une méthode appelée au sein d'un target). En effet toutes les propriétés créées via un PropertyGroup sont initialisées au lancement du fichier.

Création dynamique

Si les propriétés créées dans un PropertyGroup sont visibles et existent à tout moment dans le fichier de Projet, celles créées par la tâche « CreateProperty » n'existent qu'une fois la tâche exécutée. Cependant il n'est pas possible de référencer une propriété si celle-ci est définie après son utilisation (voir l'exemple sur les ordres de déclaration).

IV-B-5-c - Syntaxe de la tâche CreateProperty

La tâche CreateProperty prend en paramètre d'entrée la valeur de la propriété à créer et nous renvoie la valeur. Il faut alors l'associer au nom de la propriété que l'on veut créer. La syntaxe est similaire pour toutes les tâches ayant des paramètres de sorties (output). Plus synthétiquement, la syntaxe est la suivante :

Syntaxe de la tâche CreateProperty

<CreateProperty Value="Valeur de la propriété, par exemple: Hello">
<Output TaskParameter="Value"
PropertyName="Le nom de la Property que l'on veut créer" />
</CreateProperty>

où Value est le nom de la propriété de la tâche (propriété marquée avec un OutputAttribute) que l'on veut récupérer sous forme de Property. Dans le cas de la tâche CreateProperty, c'est la propriété « Value ».

IV-B-5-d - Exemple d'utilisation

Dans l'exemple ci-dessous, nous allons créer une propriété « MaProprieteDynamique » (de valeur 'Test3') via la tâche CreateProperty. Cette propriété pourra être utilisée exactement de la même façon que celles créées via un PropertyGroup.

Création dynamique de propriétés

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

<Target Name="Target1">
<CreateProperty Value="Test3">
<Output TaskParameter="Value" PropertyName="MaProprieteDynamique" />
</CreateProperty>

<Message Text="$(MaProprieteDynamique)" />
</Target>

</Project>

IV-B-6 - Changer la valeur d'une propriété

Si l'on désire changer la valeur d'une propriété, on peut également utiliser la tâche CreateProperty. Elle ne se contente en effet pas de créer une nouvelle propriété, mais également de mettre à jour la valeur, s'il existe déjà une propriété avec ce nom.

Création dynamique de propriétés

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

<PropertyGroup>
<MaPropriete>Valeur initiale</MaPropriete>
</PropertyGroup>

<Target Name="Target1">
<Message Text="$(MaPropriete)" />

<CreateProperty Value="Nouvelle valeur">
<Output TaskParameter="Value" PropertyName="MaPropriete"/>
</CreateProperty>

<Message Text="$(MaPropriete)" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:18:54.
Project "C:\Temp\build.proj" on node 0 (default targets).
Valeur initiale
Nouvelle valeur
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.07

IV-B-7 - Différer la création d'un PropertyGroup

Nous avons vu dans la section « Création statique de propriétés » que les « PropertyGroup » étaient initialisés lors du lancement du fichier.

Il est cependant possible de différer leur initialisation de façon à ce que les PropertyGroup se comportent comme des propriétés dynamiques. Pour cela, il suffit de les placer au sein d'un Target.

Pour illustrer cet exemple, nous utilisons l'attribut DependsOnTarget. Nous le détaillerons dans la section « Contrôler l'ordre d'exécution des Targets ».
Supposons seulement à ce stade que le Target SetProprietes sera lancé avant le Target Main.

Création dynamique d'une propriété via un PropertyGroup

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

<Target Name="Main" DependsOnTargets="SetProprietes">
<Message Text="Ma Propriete Dynamique: $(MaProprieteDynamique)" />
</Target>

<Target Name="SetProprietes">
<!-- Ici la propriété n'a pas encore été créée -->
<Message Text="Ma Propriete Dynamique: $(MaProprieteDynamique)" />

<!-- Là on va créer dynamiquement la propriété -->
<PropertyGroup>
<MaProprieteDynamique>Valeur</MaProprieteDynamique>
</PropertyGroup>

<!-- La propriété existe donc ici -->
<Message Text="Ma Propriete Dynamique: $(MaProprieteDynamique)" />
</Target>
</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 23/04/2009 8:15:04.
Project "D:\Fichiers de build\Création de propriétés via PropertyGroup.proj" on node 0 (default targets).
Ma Propriete Dynamique:
Ma Propriete Dynamique: Valeur
Main:
Ma Propriete Dynamique: Valeur
Done Building Project "D:\Fichiers de build\Création de propriétés via PropertyGroup.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.03

Dans l'exemple ci-dessus, on remarque que le PropertyGroup n'a pas été initialisé lors du démarrage du fichier, mais bien lorsque le flot d'exécution est passé sur le PropertyGroup.

Cette syntaxe est en fait une version simplifiée de la tâche CreateProperty.

IV-C - Listes d'un fichier de build : les Items et ItemGroup

IV-C-1 - Définition

Outre les propriétés, on peut également utiliser des Items qui sont une structure beaucoup plus complexe. On peut les comparer en fait à une collection d'objets non typés, chaque objet étant typiquement un type simple (int, string…), mais auquel on peut adjoindre des propriétés (appelées MetaData).

Les MetaData sont en fait des propriétés d'une des valeurs de l'Item. Toutes les valeurs d'un même Item ne doivent donc pas forcément posséder les mêmes MetaData.
Nous verrons plus clairement ce cas dans l'exemple proposé dans la partie traitant de la « création statique des items ».

IV-C-2 - Création statique d'Items

Comme les PropertyGroup pour les propriétés, on retrouve des ItemGroup pour la création d'Items. La syntaxe est cependant légèrement différente de par la définition possible des MetaData.

Création de plusieurs items

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Item1 Include="Item avec Metadata"
<Display>True</Display>
<AutreMetaData>Valeur</AutreMetaData>
</Item1>

<Item2 Include="Item sans Metadata" />

<Item3 Include="Val1;Val2"/>
</ItemGroup>
</Project>

Dans l'exemple ci-dessus, on a créé

- un item « Item1 » possédant une valeur « Item avec Metadata » lui-même composé de deux metadata

- « Display » de valeur « True »,

- « AutreMetaData » de valeur « Valeur » ;

- un item « Item2 » possédant une valeur : « Item sans Metadata » ;

- un item « Item3 » possédant deux valeurs : « Val1 » et « Val2 ».

Si l'on veut déclarer plusieurs valeurs dans un même item, on peut soit utiliser la syntaxe de « Item3 » ci-dessus, soit déclarer deux attributs XML de cette façon :

Création d'un item avec deux valeurs, chacune ayant des MetaData différentes

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Item1 Include="Val1"
<Display>True</Display>
</Item1>

<Item1 Include="Val2"
<AutreMetaData>Valeur</AutreMetaData>
</Item1>
</ItemGroup>
</Project>

C'est de cette façon que l'on peut spécifier des metadata différentes pour certaines valeurs d'un même item.

IV-C-3 - Utilisation d'Items

Pour utiliser un item, il faut utiliser la syntaxe « @(NomItem) ».

Pour accéder à une metadata particulière d'un Item, il suffit d'utiliser la syntaxe « %(NomItem.NomMetaData) » ou « %(NomMetaData) ». Cette deuxième syntaxe pourrait cependant échouer à la moindre ambiguïté et est d'ailleurs la plupart du temps à déconseiller en termes de lisibilité.

Récupération d'items et de MetaData

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

<ItemGroup>
<Item1 Include="Val1;Val2" />
<Item2 Include="Test avec Display">
<Display>True</Display>
</Item2>
<Item2 Include="Test sans Display">
<Display>False</Display>
</Item2>
</ItemGroup>

<Target Name="Target1">
<Message Text="Item1 = @(Item1)" />
<Message Text="Item2 = @(Item2)" />
<Message Text="Item2.Display = %(Item2.Display)" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:20:22.
Project "C:\Temp\build.proj" on node 0 (default targets).
Item1 = Val1;Val2
Item2 = Test avec Display;Test sans Display
Item2.Display = True
Item2.Display = False
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.01

Comme on pouvait s'y attendre, demander la valeur d'Item2 va nous afficher la liste de ses différentes valeurs concaténées par un « ; ».

En demandant la valeur d'une metadata d'Item2, il y aura autant de lignes générées que de valeurs de l'item possédant la metadata demandée.

IV-C-4 - Création dynamique d'Items

IV-C-4-a - Syntaxe et exemple

Il est également possible d'utiliser une tâche CreateItem pour créer dynamiquement un item.

La syntaxe sera très proche de celle de la tâche CreateProperty. Le grand avantage est que cela nous permet d'utiliser des conditions de création.

Création dynamique d'un item

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

<PropertyGroup>
<MyValue>Hello</MyValue>
</PropertyGroup>

<ItemGroup>
<Item2 Include="Test avec Display">
<Display>True</Display>
</Item2>
<Item2 Include="Test sans Display">
<Display>False</Display>
</Item2>
</ItemGroup>

<Target Name="Target1">

<CreateItem
Include="@(Item2)"
Condition="'%(Display)'=='true'"
AdditionalMetadata="MonDisplay=%(Display);AutreMetaData=$(MyValue)">

<Output
TaskParameter="Include"
ItemName="MonNouvelItemAvecMetadata"/>
</CreateItem>

<Message Text="@(MonNouvelItemAvecMetadata)" />
<Message Text="%(MonNouvelItemAvecMetadata.MonDisplay)" />
<Message Text="%(MonNouvelItemAvecMetadata.AutreMetaData)" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:21:26.
Project "C:\Temp\build.proj" on node 0 (default targets).
Test avec Display
True
Hello
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.04

Dans l'exemple ci-dessus, on crée dynamiquement un Item en spécifiant que

- on veut le créer à partir de l'Item « Item2 » (qui possède deux valeurs : « Test avec Display » et « Test sans Display ») ;

- en filtrant sur tous les Items « Item2 » pour lesquels on a une MetaData « Display » de valeur « true » ;

- on veut ajouter deux nouvelles MetaData à toutes les valeurs de l'item que l'on veut créer, le premier étant simplement la valeur de la MetaData « Display » de l'Item inclus, et l'autre provenant d'une propriété.

Notez que pour avoir plus d'informations sur les conditions possibles, vous pouvez vous référer à la bibliographie sur les conditions utilisables.

Notez enfin que ces conditions sont également utilisables sur un ItemGroup et un PropertyGroup au complet afin d'éviter la création de certaines variables qui pourraient être inutiles.

IV-C-4-b - Cas particulier lors de l'utilisation de metadata

Notez que dans les AdditionalMetaData, on peut utiliser des Properties, des MetaData de l'Item que l'on inclut, ou des MetaData d'autres items. Dans ce dernier cas, s'il y a plusieurs valeurs de cet autre item possédant la MetaData souhaitée, chaque valeur de l'Item généré sera clonée afin que chaque valeur puisse posséder toutes les MetaData possibles. Voyons cela dans l'exemple ci-dessous :

Création dynamique d'un item avec metadata d'autres items - Exemple 1

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

<ItemGroup>
<Item1 Include="Test avec Display">
<Display>True</Display>
</Item1>
<Item1 Include="Test sans Display">
<Display>False</Display>
</Item1>
</ItemGroup>

<Target Name="Target2">
<CreateItem Include="Valeur"
AdditionalMetadata="Display=%(Item1.Display)">
<Output TaskParameter="Include" ItemName="MonNouvelItem" />
</CreateItem>

<Message Text="MonNouvelItem = @(MonNouvelItem)" />
<Message Text="MonNouvelItem.Display = %(MonNouvelItem.Display)" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:22:28.
Project "C:\Temp\build.proj" on node 0 (default targets).
MonNouvelItem = Valeur;Valeur
MonNouvelItem.Display = True
MonNouvelItem.Display = False
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.04

Ici, nous avons donc créé un item en lui assignant une seule valeur (« Valeur »), et en lui assignant comme nouvelle metadata la valeur de la metadata « Display » de l'item Item1. Comme Item1 possède plusieurs éléments, et que ces éléments n'ont pas tous la même valeur pour la metadata « Display », alors le nouvel item contiendra deux éléments.

Si tous les éléments d'Item1 possédaient la même valeur pour « Display », alors ce clonage n'aurait pas lieu.

Création dynamique d'un item avec metadata d'autres items - Exemple 2

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

<ItemGroup>
<Item1 Include="Test avec Display">
<Display>True</Display>
</Item1>
<Item1 Include="Test également avec Display">
<Display>True</Display>
</Item1>
</ItemGroup>

<Target Name="Target1">
<CreateItem Include="Valeur"
AdditionalMetadata="Display=%(Item1.Display)">
<Output TaskParameter="Include" ItemName="MonNouvelItem" />
</CreateItem>

<Message Text="MonNouvelItem = @(MonNouvelItem)" />
<Message Text="MonNouvelItem.Display = %(MonNouvelItem.Display)" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:24:20.
Project "C:\Temp\build.proj" on node 0 (default targets).
MonNouvelItem = Valeur
MonNouvelItem.Display = True
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.07

IV-C-5 - Précautions lors de l'utilisation d'Items dans des tâches

Comme nous l'avons vu dans les exemples précédents, il faut bien prendre en compte le fait que les Items sont des collections de valeurs, et donc deux items différents pourraient avoir un nombre de valeurs différent et des metadata différentes. De même, deux valeurs d'un même item pourraient également avoir des metadata différentes. Par conséquent, il se peut que les tâches qui utilisent des items aient des comportements particuliers.

En effet, il est impossible de joindre dans une même collection des éléments différents de façon cohérente. MsBuild va donc automatiquement scinder les appels aux tâches lorsque plusieurs Items sont utilisés.

La tâche est appelée plusieurs fois pour chaque MetaData

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

<ItemGroup>
<Item1 Include="ValeurItem1">
<Data>La MetaData de l'Item1</Data>
</Item1>
<AutreItem Include="ValeurAutreItem">
<Display>True</Display>
</AutreItem>
</ItemGroup>

<Target Name="Target1">
<Message Text="Item1: @(Item1) - Autre Item: @(AutreItem)" />
<Message Text="Item1: %(Item1.Data) - Autre Item: %(AutreItem.Display)" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:25:23.
Project "C:\Temp\build.proj" on node 0 (default targets).
Item1: ValeurItem1 - Autre Item: ValeurAutreItem
Item1: La MetaData de l'Item1 - Autre Item:
Item1:  - Autre Item: True
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.03

De même si l'on complète l'exemple en ajoutant une nouvelle valeur pour l'item 1, le résultat aura une ligne supplémentaire.

La tâche est appelée plusieurs fois pour chaque MetaData

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

<ItemGroup>
<Item1 Include="ValeurItem1">
<Data>La MetaData de Item1</Data>
</Item1>
<Item1 Include="AutreValeurItem1">
<Data>La MetaData de l'autre Item1</Data>
</Item1>
<AutreItem Include="ValeurAutreItem">
<Display>True</Display>
</AutreItem>
</ItemGroup>

<Target Name="Target1">
<Message Text="Item1: @(Item1) - Autre Item: @(AutreItem)" />
<Message Text="Item1: %(Item1.Data) - Autre Item: %(AutreItem.Display)" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:26:15.
Project "C:\Temp\build.proj" on node 0 (default targets).
Item1: ValeurItem1;AutreValeurItem1 - Autre Item: ValeurAutreItem
Item1: La MetaData de Item1 - Autre Item:
Item1: La MetaData de l'autre Item1 - Autre Item:
Item1:  - Autre Item: True
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.03

Pour réellement concaténer ces valeurs, il faudrait passer par la création d'items ou de propriétés intermédiaires.

IV-C-6 - Différer la création d'un ItemGroup

De même que pour les PropertyGroup, il est possible de différer la création d'un ItemGroup de façon à ce que celui-ci ne soit pas initialisé au démarrage du fichier. Il suffit également de déplacer l'ItemGroup dans le target, à l'emplacement du flot d'exécution souhaité.

Pour illustrer cet exemple, nous utilisons l'attribut DependsOnTarget. Nous le détaillerons dans la section « Contrôler l'ordre d'exécution des Targets ».
Supposons seulement à ce stade que le Target SetProprietes sera lancé avant le Target Main.

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

<Target Name="Main" DependsOnTargets="SetItem">
<Message Text="Mon Item Dynamique: @(MonItemDynamique)" />
<Message Text="Mon Item MetaData : %(MonItemDynamique.MetaData)" />
</Target>

<Target Name="SetItem">
<!-- Ici l'item n'a pas encore été créée -->
<Message Text="Mon Item Dynamique: @(MonItemDynamique)" />
<Message Text="Mon Item MetaData : %(MonItemDynamique.MetaData)" />

<!-- Là on va créer dynamiquement la propriété -->
<ItemGroup>
<MonItemDynamique Include="Valeur">
<MetaData>Valeur MetaData</MetaData>
</MonItemDynamique>
</ItemGroup>

<!-- L'item existe donc ici -->
<Message Text="Mon Item Dynamique: @(MonItemDynamique)" />
<Message Text="Mon Item MetaData : %(MonItemDynamique.MetaData)" />
</Target>
</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 23/04/2009 8:26:11.
Project "D:\Fichiers de build\Création d'items via ItemGroup.proj" on node 0 (default targets).
Mon Item Dynamique:
Mon Item MetaData :
Mon Item Dynamique: Valeur
Mon Item MetaData : Valeur MetaData
Main:
Mon Item Dynamique: Valeur
Mon Item MetaData : Valeur MetaData
Done Building Project "D:\Fichiers de build\Création d'items via ItemGroup.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.01

IV-D - Exécuter un fichier de build

Nous avions vu dans la section « Exécuter un fichier de build » la façon la plus simple pour exécuter un fichier. Il y a cependant d'autres façons de le faire, de façon à surcharger le comportement par défaut.

IV-D-1 - Exécution par défaut

Pour rappel, l'exécution la plus simple requiert uniquement le nom du fichier « .proj » à lancer. Dans ce cas, le target par défaut sera exécuté.

Le target par défaut sera déterminé comme suit :

- si un seul target dans le fichier, c'est celui-là qui sera lancé ;

- si plusieurs targets

- si l'un d'entre eux est marqué comme par défaut, alors c'est celui-là qui sera lancé (nous verrons plus d'informations sur la notion de target par défaut dans la section « Contrôler l'ordre d'exécution des Targets »),

- sinon, c'est le premier target présent dans le fichier qui sera lancé

MsBuild : Exécution d'un fichier de build

MsBuild BuildA.proj

IV-D-2 - Surcharge du target à exécuter

On peut cependant utiliser l'option « /target:NomTarget » ou « /t:NomTarget » pour spécifier un autre target de démarrage. Dans ce cas, le target par défaut sera ignoré.

Pour en spécifier plusieurs, il faut les séparer par un « ; » ou utiliser plusieurs options « /t ».

MsBuild : Exécution de la cible « Rebuild » d'un fichier de build

MsBuild /t:Rebuild BuildA.proj

IV-D-3 - Surcharge d'une propriété

On a également la possibilité de surcharger la valeur des propriétés statiques - c'est-à-dire celles créées dans un PropertyGroup - lors de l'exécution. Pour cela, il suffit d'utiliser l'option « /property » (ou sa forme abrégée « /p ») avec la syntaxe  « /property:NomPropriete=ValeurPropriete » ou « /p:NomPropriete=ValeurPropriete ».

De nouveau pour surcharger plusieurs propriétés, il faut soit les séparer par un « ; » soit utiliser plusieurs options « /p ».

Par exemple, avec le fichier de projet « Test.proj » suivant :

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

<PropertyGroup>
<Message>Hello</Message>
</PropertyGroup>

<Target Name="Target1">
<Message Text="$(Message)" />
</Target>

</Project>

On peut surcharger la valeur de « Message » de la manière suivante :

Changer la valeur de certaines propriétés à l'exécution

REM. Va afficher "Hello"
MsBuild Test.proj

REM. Va afficher "Bonjour"
MsBuild /property:Message=Bonjour Test.proj
MsBuild /p:Message=Bonjour Test.proj

Notez cependant qu'il n'est pas possible de surcharger d'une manière similaire la valeur ou une metadata d'un item statique.

IV-D-4 - Surcharge de la verbosité d'exécution

Il est également possible de contrôler le niveau de verbosité lors de l'exécution pour avoir plus ou moins de détail et de commentaires.

On peut utiliser pour cela l'option « /verbosity » ou sa forme abrégée « /v ». Les différentes valeurs possibles correspondent en fait aux valeurs (en minuscules) de l'énumération Microsoft.Build.Framework.LoggerVerbosity :

1. quiet (ou q) :

2. minimal (ou m) :

3. normal (ou n) :

4. detailed (ou d) :

5. diagnostic (ou diag).

Pour plus d'informations sur les différences entre ces différentes valeurs, vous pouvez vous reporter aux liens donnés dans la bibliographie sur les différents niveaux de verbosité.

Il est cependant important de connaître cette possibilité, puisqu'il peut y avoir un facteur de dix entre la quantité d'information archivée en mode diagnostique et en mode normal.

Changer le niveau de la verbosité

MsBuild /verbosity:detailed Test.proj
MsBuild /v:detailed Test.proj

IV-D-5 - Omission du nom du fichier

Notez enfin que MsBuild exécutera en fait le fichier de projet qu'il trouve dans le répertoire courant. Par conséquent, si votre répertoire courant ne comporte qu'un seul fichier de projet, vous pouvez même l'omettre dans la ligne de commande.

IV-E - Référencer une tâche du framework 3.5

Par défaut, lorsque l'on exécute MsBuild, il référence déjà un certain nombre de tâches, telles que les tâches Message, CreateProperty et CreateItem. Il référence en fait toutes les tâches qui sont déclarées dans la DLL Microsoft.Build.Task.dll.

Avec l'arrivée du framework 3.5, Microsoft a ajouté de nouvelles tâches et a complété des tâches existantes. Toutes ces « nouvelles » tâches ont été ajoutées dans la DLL Microsoft.Build.Task.v3.5.dll.

Si l'on veut utiliser une de ces tâches définies dans une autre DLL que celle référencée par défaut, on peut utiliser la commande UsingTask.

Notez bien que l'on référence une simple tâche présente dans une DLL, et non toute la DLL. Par conséquent, si l'on veut utiliser trois tâches de la même DLL, il est impératif de déclarer trois UsingTask différents.

La commande UsingTask nous offre deux paramètres mutuellement exclusifs que sont AssemblyName et AssemblyPath. Le premier nous permet de donner le nom de l'assembly - à condition que celle-ci soit définie dans la GAC. Le second nous permet de spécifier le chemin complet vers une DLL.

Utiliser la version « 3.5 » d'une tâche - ici la tâche GetFrameworkPath

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

<UsingTask TaskName="GetFrameworkPath"
AssemblyName="Microsoft.Build.Tasks.v3.5"/>

<Target Name="Target1">
<GetFrameworkPath>
<Output TaskParameter="FrameworkVersion35Path"
PropertyName="MsBuildPath"/>
</GetFrameworkPath>

<Message Text="$(MsBuildPath)" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 27/01/2009 7:55:48.
Project "D:\Fichiers de build\Utilisation de UsingTask.proj" on node 0 (default targets).
C:\WINDOWS\Microsoft.NET\Framework\v3.5
Done Building Project "D:\Fichiers de build\Utilisation de UsingTask.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03

Si nous avions omis la commande UsingTask, alors MsBuild aurait simplement utilisé la version « 2.0 » de la tâche et nous aurions reçu l'erreur suivante :

The « FrameworkVersion35Path » parameter is not supported by the « GetFrameworkPath » task. Verify the parameter exists on the task, and it is a gettable public instance property.

Il est donc crucial de bien regarder dans quelle DLL (ie dans quelle version du framework) la tâche que vous voulez utiliser a été déclarée. La MSDN nous fournit cette information via les éléments Assembly et Version Information.

Une tâche existant à la fois dans le framework 2.0 et 3.5 (comme la tâche GetFrameworkPath par exemple) n'apparaitra qu'une seule fois dans la MSDN.
Ainsi lorsque l'on visualisera la tâche elle-même, la MSDN nous indiquera qu'elle existe dans la DLL Microsoft.Build.Task.dll. Mais c'est lorsque l'on regardera la propriété FrameworkVersion35Path que l'on verra qu'elle appartient à la DLL Microsoft.Build.Task.v3.5.dll

IV-F - Plus loin avec les Targets

IV-F-1 - Contrôler l'ordre d'exécution des Targets

IV-F-1-a - Définition

Bien que l'on puisse choisir lors de l'exécution le « Target » à exécuter (voir la section « Exécuter un fichier de build »), il est possible de contrôler dans le fichier l'ordre d'exécution via les attributs DefaultTargets, InitialTargets et DependsOnTargets.

Ces différents attributs sont assez explicites :

- DefaultTargets spécifie le(s) target(s) qui sera exécuté si aucun target n'est spécifié lors de l'exécution en ligne de commande (cas de la syntaxe proposé dans la section « Exécuter un fichier de build » - version simple) ;

- InitalTargets spécifie le(s) target(s) qui sera lancé au démarrage, avant tout autre target ;

- DependsOnTargets spécifie le(s) target(s) à lancer avant l'exécution d'un autre target.

Si les deux premiers sont donc liés au fichier de projet, le dernier est lié à un target.

IV-F-1-b - Exemple

Contrôler l'ordre d'exécution des targets

<Project DefaultTargets="Target1" InitialTargets="Init"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<Target Name="PreTarget1" />
<Target Name="PreTarget2" />
<Target Name="Init" />

<Target Name="Target1" DependsOnTargets="PreTarget1;PreTarget2" />
</Project>

Dans l'exemple ci-dessus, il est spécifié que :

- si aucun autre target n'est spécifié lors de l'exécution, le target exécuté sera « Target1 » (DefaultTargets) ;

- quel que soit le target démarré lors de l'exécution, le target « Init » sera lancé en premier (InitialTargets) ;

- avant de lancer le target « Target1 », les deux targets « PreTarget1 » et « PreTarget2 » seront exécutés - et ce dans cet ordre - (« Target1 » DependsOnTargets « PreTarget1 » et « PreTarget2 ».

Comme présenté en exemple dans l'attribut DependsOnTargets, on peut préciser plusieurs Target en les séparant par des « ; ». Ceci est également vrai pour les attributs DefaultTargets et InitialTargets.

IV-F-1-c - Cas des dépendances multiples

Notez cependant que dans le cas de l'exemple ci-dessous, l'exécution du Target1 nécessite l'exécution des « PreTarget1 » et « PreTarget2 ». De plus « PreTarget2 » nécessite également l'exécution de « PreTarget1 ».

Dans ce cas, le « PreTarget1 » ne sera exécuté qu'une et une seule fois.

Contrôler l'ordre d'exécution des : pas de redondance d'exécution

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

<Target Name="PreTarget1">
<Message Text="Dans PreTarget1" />
</Target>
<Target Name="PreTarget2" DependsOnTargets="PreTarget1">
<Message Text="Dans PreTarget2" />
</Target>

<Target Name="Target1" DependsOnTargets="PreTarget1;PreTarget2">
<Message Text="Dans Target1" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:35:48.
Project "C:\Temp\build.proj" on node 0 (default targets).
Dans PreTarget1
PreTarget2:
Dans PreTarget2
Target1:
Dans Target1
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.10

IV-F-2 - Appeler explicitement un Target

Il est possible d'appeler explicitement un target au sein d'un autre target. Pour cela, deux tâches différentes existent : CallTarget et MsBuild.

IV-F-2-a - Appeler un target via CallTarget

Cette tâche n'est utilisable que si les targets cibles et source se trouvent dans le même fichier de build, sinon c'est vers la tâche MsBuild qu'il faut se tourner.

Appel explicite d'un Target

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

<Target Name="Target1">
<Message Text="Dans Target 1" />

<CallTarget Targets="Target2" />
</Target>

<Target Name="Target2">
<Message Text="Dans Target 2" />
</Target>
</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:37:04.
Project "C:\Temp\build.proj" on node 0 (default targets).
Dans Target 1
Target2:
Dans Target 2
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.03

Notez cependant que le target ne pourra être exécuté qu'une seule fois, de la même façon que pour les dépendances entre les targets décrites dans la partie « Cas des dépendances multiples ».

Ainsi le résultat sera inchangé si on a le fichier de build suivant :

Appel explicite d'un Target - pas de redondance d'exécution

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

<Target Name="Target1">
<Message Text="Dans Target 1" />

<CallTarget Targets="Target2" />
<CallTarget Targets="Target2" />
</Target>

<Target Name="Target2">
<Message Text="Dans Target 2" />
</Target>
</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/05/2009 23:38:02.
Project "C:\Temp\build.proj" on node 0 (default targets).
Dans Target 1
Target2:
Dans Target 2
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.03

Notez enfin qu'avec la tâche CallTarget, les targets sources et cibles partageront les mêmes propriétés, qu'elles aient été créées statiquement ou dynamiquement. Il ne sera donc pas possible de donner une autre valeur à une propriété le temps de l'appel à ce target, comme si l'on passait un paramètre à une méthode. (5)

IV-F-2-b - Appeler un target via MsBuild

Nous avons également à notre disposition la tâche MsBuild. Elle possède l'énorme avantage de nous permettre de passer des paramètres (ie des propriétés) au target cible, ainsi que d'appeler plusieurs fois le même target. Par conséquent, nous pouvons utiliser cette tâche afin d'utiliser des targets comme s'il s'agissait de « méthodes utilitaires ».

Pour appeler la tâche, nous devons spécifier le fichier de projet que l'on veut exécuter. Pour cela, nous pouvons :

- soit donner le chemin et le nom du fichier à exécuter ;

- soit utiliser la propriété $(MsBuildProjectFile) qui nous permet d'exécuter un target situé dans le fichier de projet courant.

Appel explicite d'un Target en utilisant MsBuild

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

<Target Name="Target1">
<Message Text="Dans Target1" />

<MSBuild Targets="Target2" Projects="$(MSBuildProjectFile)" />
</Target>

<Target Name="Target2">
<Message Text="Dans Target2" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 9/01/2009 8:59:47.
Project "D:\Appel de target via MsBuild.proj" on node 0 (default targets).
Dans Target1
Project "D:\Appel de target via MsBuild.proj" (1) is building "D:\Appel de target via MsBuild.proj" (1:2) on node 0 (Target2 target(s)).
Dans Target2
Done Building Project "D:\Appel de target via MsBuild.proj" (Target2 target(s)).

Done Building Project "D:\Appel de target via MsBuild.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.04

Comme on peut le voir dans l'exemple, c'est le même process MsBuild.exe qui va exécuter les deux targets.

Cette méthode nous permet d'exécuter plusieurs fois le même target, à quelques limitations près. Il faut savoir en effet que MsBuild.exe stocke la liste de tous les targets exécutés et interdit qu'un target exécuté ne soit ré-exécuté une seconde fois.

Ainsi pour qu'un target puisse être lancé plusieurs fois, il faut impérativement qu'ils soient considérés comme différents. Pour cela, on peut se permettre de passer des propriétés différentes en entrée. Nous donnerons un exemple de ce cas dans la partie « Passer des paramètres à un Target ».

IV-F-2-c - Choisir CallTarget ou MsBuild

Reprenons sous la forme d'un tableau les principales différences entre les deux tâches pour savoir laquelle choisir selon les cas.

CallTarget

MsBuild

Exécuter un target se trouvant dans le même fichier

Exécuter un target se trouvant dans un autre fichier

Partager toutes les propriétés existantes entre le target source et cible

(6)

Passer des propriétés au target cible

(7)

Appeler plusieurs fois le même target

Récupérer des propriétés en sortie de l'exécution

IV-F-2-d - La tâche Exec

Une dernière possibilité existe également : la tâche Exec qui permet d'exécuter une ligne de commande. On va alors construire la ligne de commande comme décrit dans la section « Exécuter un fichier de build ».

Appel explicite d'un Target en utilisant Exec

<GetFrameworkPath>
<Output TaskParameter="Path" PropertyName="MsBuildPath"/>
</GetFrameworkPath>

<Exec Command="$(MsBuildPath)\msbuild %22$(MSBuildProjectFile)%22 /t:Target2" />

Notez qu'ici on utilise la propriété Path de la tâche GetFrameworkPath. Selon nos besoins, il pourra être important d'utiliser les propriétés FrameworkVersion20Path ou FrameworkVersion35Path. Pour cela, on pourra alors utiliser la version 3.5 de la tâche GetFrameworkPath en se basant sur l'exemple fourni dans la section « Référencer une tâche du framework 3.5 ».

IV-F-3 - Passer des paramètres à un Target

IV-F-3-a - Paramètres d'entrée

Comme nous l'avons vu, il n'est possible de passer des paramètres d'entrée à une tâche que si on l'appelle via les tâches Exec ou via MsBuild.

Pour la tâche Exec, il suffit bien sûr d'utiliser la même syntaxe que lorsque l'on utilise MsBuild en ligne de commande, soit via les options « /property » ou « /p » tel que décrit dans la section « Surcharge d'une propriété ».

En ce qui concerne la tâche MsBuild, une propriété spécifique peut être utilisée à cet effet.

Passage de propriété avec la tâche MsBuild

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

<Target Name="Target1">
<MSBuild Targets="Target2"
Projects="$(MSBuildProjectFile)"
Properties="Test=0" />

<MSBuild Targets="Target2"
Projects="$(MSBuildProjectFile)"
Properties="Test=1" />
</Target>

<Target Name="Target2">
<Message Text="Dans Target2: Test=$(Test)" />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 27/01/2009 18:16:30.
Project "D:\Input Params avec MsBuild.proj" on node 0 (default targets).
Project "D:\Input Params avec MsBuild.proj" (1) is building "D:\Input Params avec MsBuild.proj" (1:2) on node 0 (Target2 target(s)).
Dans Target2: Test=0
Done Building Project "D:\Input Params avec MsBuild.proj" (Target2 target(s)).

Project "D:\Input Params avec MsBuild.proj" (1) is building "D:\Input Params avec MsBuild.proj" (1:3) on node 0 (Target2 target(s)).
Dans Target2: Test=1
Done Building Project "D:\Input Params avec MsBuild.proj" (Target2 target(s)).

Done Building Project "D:\Input Params avec MsBuild.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.09

IV-F-3-b - Paramètres de sortie

Il est également possible de recevoir des paramètres de sortie lors de l'exécution d'un target. Il peut s'agir soit de propriétés, soit d'items.

Récupération de paramètres de sortie sous forme de propriété

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

<Target Name="Target1">
<!-- Récupération de la sortie lors de l'utilisation de CallTarget -->
<CallTarget Targets="Target2">
<Output TaskParameter="TargetOutputs" PropertyName="OutputCallTarget"/>
</CallTarget>
<Message Text="Target2 a renvoyé $(OutputCallTarget)" />

<!-- Récupération de la sortie lors de l'utilisation de MsBuild -->
<MSBuild Targets="Target2" Projects="$(MSBuildProjectFile)">
<Output TaskParameter="TargetOutputs" PropertyName="OutputMsBuild" />
</MSBuild>
<Message Text="Target2 a renvoyé $(OutputMsBuild)" />
</Target>

<Target Name="Target2" Outputs="$(OutputProperty)">
<CreateProperty Value="MaValeur">
<Output TaskParameter="Value" PropertyName="OutputProperty" />
</CreateProperty>
</Target>
</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 27/01/2009 18:28:36.
Project "D:\Output Params.proj" on node 0 (default targets).
Target2 a renvoyé MaValeur
Target2 a renvoyé MaValeur
Done Building Project "D:\Output Params.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03

Dans l'exemple ci-dessus, nous allons simplement appeler (via les tâches CallTarget et MsBuild) le Target2 présent dans le même fichier.

Ce target se contente de créer une propriété, et spécifie qu'elle va la retourner. On peut alors lire la propriété TargetOutputs des tâches pour récupérer la propriété.

De la même façon, on pourrait récupérer un item. Le fichier ci-dessous nous donnerait exactement le même résultat d'exécution.

On fera bien attention d'utiliser l'attribut ItemName et non plus PropertyName pour la récupération de la valeur.

Récupération de paramètres de sortie sous forme d'item

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

<Target Name="Target1">
<!-- Récupération de la sortie lors de l'utilisation de CallTarget -->
<CallTarget Targets="Target2">
<Output TaskParameter="TargetOutputs" ItemName="OutputCallTarget"/>
</CallTarget>
<Message Text="Target2 a renvoyé @(OutputCallTarget)" />

<!-- Récupération de la sortie lors de l'utilisation de MsBuild -->
<MSBuild Targets="Target2" Projects="$(MSBuildProjectFile)">
<Output TaskParameter="TargetOutputs" ItemName="OutputMsBuild" />
</MSBuild>
<Message Text="Target2 a renvoyé @(OutputMsBuild)" />
</Target>

<Target Name="Target2" Outputs="@(OutputItem)">
<CreateItem Include="MaValeur">
<Output TaskParameter="Include" ItemName="OutputItem" />
</CreateItem>
</Target>

</Project>

V - Factoriser un fichier de build

V-A - Principes de factorisation

Un fichier de build doit être considéré comme un élément de code à part entière et doit donc suivre la même logique et réflexion lors de sa création. Ce fichier sera donc également factorisé et refactoré. Ne perdez pas de vue en effet qu'un fichier de build sur un projet important peut rapidement atteindre plusieurs centaines - voire milliers - de lignes.

Parmi les différents critères auxquels il faudra veiller, on peut citer entre autres :

- commentaires du code ;

- principe de responsabilité unique dans un même target ;

- pas de redondance du code ;

- extraction des fonctionnalités communes dans une bibliothèque partagée.

Il est évidemment très simple de commenter son fichier de build.

Et nous avons déjà vu comment il est possible de créer des targets, de les appeler, de créer des dépendances entre eux, ou encore comment envoyer des paramètres et recevoir des valeurs en retour. Par conséquent il est également très simple de répondre à la notion de responsabilité.

La redondance de code quand elle peut également être évitée via la factorisation dans d'autres targets.

Si on se place maintenant dans une problématique plus large avec la création de fichiers de build pour plusieurs projets, la notion de bibliothèque de targets peut devenir très intéressante de façon à regrouper certaines tâches communément réalisées.

Microsoft utilise à cette fin l'extension « .targets », comme c'est le cas pour le fichier « Microsoft.CSharp.targets » par exemple.

V-B - Utilisation de fichier .targets

Il est possible de faire référence à un autre fichier de projet afin de mutualiser du code. Pour cela, on utilise l'élément « Import ».

Import du fichier « Commun.targets »

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="Commun.targets"/>
</Project>

Notez que cet élément peut être déclaré à n'importe quel endroit du fichier de projet.

Il faut visualiser le fonctionnement de cette commande comme une copie du contenu du fichier importé à l'emplacement où la commande est déclarée.

V-C - Précautions à observer

Si l'utilisation de fichier d'import est très pratique pour factoriser et partager le code - elle est d'ailleurs massivement utilisée par Microsoft pour mutualiser les fichiers de projet - certaines précautions sont à observer.

V-C-1 - Positionnement de la commande d'Import

L'emplacement auquel on déclare la commande d'import peut avoir des conséquences sur le fonctionnement du fichier de build.

Pour s'en rendre compte, il faut se souvenir qu'un fichier de build est toujours traité de bas en haut. Ainsi on peut prendre deux exemples : le premier nous montrera la redéfinition d'une propriété, et le suivant nous permettra de faire une redéfinition d'un target.

Pour ces exemples, nous allons nous baser sur le fichier commun suivant :

Déclaration d'un fichier .targets : « Commun.targets »

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

<PropertyGroup>
<MaPropriete>Valeur Par Défaut</MaPropriete>
</PropertyGroup>

<Target Name="Go">
<Message Text="Dans le fichier commun : $(MaPropriete)"/>
</Target>

</Project>

V-C-1-a - Redéfinition d'une propriété

Comme les propriétés sont initialisées de haut en bas, selon l'emplacement de la ligne d'Import, on peut permettre de redéfinir la valeur d'une propriété.

Ainsi dans le cas suivant :

Utilisation du fichier « Commun.targets » - Exemple 1

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

<PropertyGroup>
<MaPropriete>Nouvelle valeur</MaPropriete>
</PropertyGroup>

<Import Project="Commun.targets" />

</Project>

Si on visualise la copie du contenu du fichier « Commun.targets », on aurait l'assignation de « Nouvelle Valeur » dans la propriété, puis l'assignation de « Valeur Par Défaut ». En toute logique, nous obtenons le résultat d'exécution suivant :

Résultat de l'utilisation du fichier « Commun.targets » - Exemple 1

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 12/05/2009 21:46:46.
Project "C:\Temp\build.proj" on node 0 (default targets).
Dans le fichier commun : Valeur Par Défaut
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.23

Si en revanche, nous positionnons la commande d'Import en début de fichier, alors le résultat change totalement :

Utilisation du fichier « Commun.targets » - Exemple 2

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

<Import Project="Commun.targets" />

<PropertyGroup>
<MaPropriete>Nouvelle valeur</MaPropriete>
</PropertyGroup>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 12/05/2009 21:48:24.
Project "C:\Temp\build.proj" on node 0 (default targets).
Dans le fichier commun : Nouvelle valeur
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.03

Si on déclare deux propriétés statiques avec le même nom, c'est alors la deuxième valeur qui sera assignée à la propriété (ie. celle qui est présente le plus bas dans le fichier).
Si on déclare deux propriétés dynamiques (ou une statique et une dynamique) avec le même nom, c'est la valeur de la dernière qui est créée (donc exécutée) qui sera assignée à la propriété.
C'est sur ce principe que l'on peut changer la valeur d'une propriété. Se référer à la section « Changer la valeur d'une propriété » pour plus d'informations.

V-C-1-b - Redéfinition d'un target

Si l'on redéclare un target nommé « Go » dans le fichier .proj, alors on pourra redéfinir le target original selon l'emplacement de la commande d'Import.

Supposons par exemple le cas suivant :

Utilisation du fichier « Commun.targets » - Exemple 3

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

<Target Name="Go">
<Message Text="Dans le fichier .proj : $(MaPropriete)"/>
</Target>

<Import Project="Commun.targets" />

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 12/05/2009 21:50:13.
Project "C:\Temp\build.proj" on node 0 (default targets).
Dans le fichier commun : Valeur Par Défaut
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.01

Ici nous allons rencontrer le target « Go » tout d'abord dans sa définition dans le fichier .proj, puis la version du fichier « .targets ». C'est donc naturellement elle qui sera exécutée.

Par contre, si on définit la commande d'import en en-tête de fichier, alors c'est la version du fichier .proj qui sera conservée.

Utilisation du fichier « Commun.targets » - Exemple 4

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

<Import Project="Commun.targets" />

<Target Name="Go">
<Message Text="Dans le fichier .proj : $(MaPropriete)"/>
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 12/05/2009 21:55:17.
Project "C:\Temp\build.proj" on node 0 (default targets).
Dans le fichier .proj : Valeur Par Défaut
Done Building Project "C:\Temp\build.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.01

Si on déclare deux targets avec le même nom, alors seul le dernier existe réellement. C'est ainsi que l'on peut « surcharger » ou redéfinir un target.
Cette solution est massivement utilisée dans les fichiers de Visual Studio, de façon à laisser au développeur des « points d'accroche » dans l'automatisation de certaines tâches (compilation, intégration continue…)

V-C-2 - Importer un fichier du source control

Il faut savoir que les commandes d'Import sont exécutées en priorité, avant même l'initialisation des propriétés et items statiques.

De plus, le fichier que l'on importe doit impérativement se trouver sur le disque.

Par conséquent il n'est pas possible de référencer un fichier dans le Source Control, et il n'est pas possible de faire un « Get » de fichier dans le Source Control avant que les imports ne soient effectués.

Par conséquent, si vous désirez vraiment utiliser un fichier commun présent dans le Source Control, la seule solution est au sein d'une tâche du fichier de faire un Get pour copier le fichier localement, et puis de déléguer les appels au fichier commun en utilisant la tâche MSBuild.

On perd évidemment dans ce cas la possibilité de redéfinition de propriétés et de targets, et nous devons donc coder nous-mêmes des redirections.

VI - Créer ses propres tâches

Microsoft a déjà fourni un certain nombre de tâches permettant de réaliser les actions de base.

Et si celles-ci s'avèrent insuffisantes, vous pouvez également référencer d'autres DLL exposant des tâches. On peut citer à ce propos « Sdc Tasks Library » (8) qui expose plus de 150 tâches différentes par exemple :

- travailler avec Biztalk ;

- gérer des groupes ou des utilisateurs dans Active Directory ;

- gérer des packages (CAB, ZIP, WIX, MSI) ;

- travailler avec des Source Control (Source Safe, Team Foundation Server, SourceGear Vault) ;

- gérer IIS via la création et le paramétrage de site web dédié, de répertoires virtuels, d'Application Pool… ;

- travailler avec des outils externes (devenv, FxCop, MsTest, NUnit, InstallShield…) ;

- travailler avec des fichiers et des répertoires.

Cependant, si malgré cette quantité de tâches différentes vous ne trouviez pas votre bonheur, Microsoft vous laisse la possibilité de créer vos propres tâches.

Et une tâche étant une simple classe .NET, cela vous laisse virtuellement la possibilité de réaliser n'importe quelle tâche d'automatisation.

VI-A - Créer une tâche simple

La première étape pour créer une tâche est de créer une nouvelle DLL dans laquelle on référence les DLL suivantes :

- « Microsoft.Build.Utilities » ou « Microsoft.Build.Utilities.v3.5 » ;

- « Microsoft.Build.Tasks » ou « Microsoft.Build.Tasks.v3.5 » ;

- « Microsoft.Build.Framework ».

On peut ensuite créer une classe héritant de la classe « Task » et overrider la méthode « Execute ». C'est cette méthode qui sera appelée lorsque la tâche sera exécutée.

Création d'une tâche de base

using Microsoft.Build.Utilities;

namespace CustomTasks
{
public class TacheSimple : Task
{
/// <summary>
/// Cette méthode sera appelée lorsque la tâche sera exécutée
/// </summary>
/// <returns>True si la tâche réussit, sinon false</returns>
public override bool Execute()
{
this.Log.LogMessage("Message écrit par la tâche TacheSimple");

//La tâche a réussi
return true;
}
}
}

On a accès à la propriété « Log » qui permet d'afficher des messages.

Une fois cette DLL compilée, on peut référencer la tâche que l'on vient de créer et l'utiliser dans le fichier de build.

Enregistrement de la tâche et utilisation dans le fichier de build

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

<UsingTask TaskName="SimpleTask"
AssemblyFile="C:\...\CustomTasks.dll" />

<Target Name="MainTarget">
<SimpleTask />
</Target>

</Project>

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 25/02/2009 7:56:26.
Project "D:\Fichiers de build\Execute TacheSimple.proj" on node 0 (default targets).
Message écrit par la tâche TacheSimple
Done Building Project "D:\Fichiers de build\Execute TacheSimple.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.15

Notez qu'il suffit que la tâche fasse un « return false » pour qu'elle soit considérée comme échouant et donc fasse potentiellement stopper le build.

VI-B - Ajouter des entrées sur sa tâche

VI-B-1 - Ajouter des entrées optionnelles

Il est évidemment intéressant de pouvoir paramétrer la tâche pour modifier son fonctionnement. Pour cela, il suffit d'ajouter des propriétés (obligatoires ou non) sur la classe que l'on vient de créer.

Création et utilisation d'une tâche avec input - Code C#

using Microsoft.Build.Utilities;

namespace CustomTasks
{
public class TacheSimpleAvecInput : Task
{
private string nom;

public string Nom
{
get { return nom; }
set { nom = value; }
}

/// <summary>
/// Cette méthode sera appelée lorsque la tâche sera exécutée
/// </summary>
/// <returns>True si la tâche a réussi, sinon false</returns>
public override bool Execute()
{
string nom = string.IsNullOrEmpty(this.Nom)
? "World" : this.Nom;
this.Log.LogMessage("Hello " + nom);

return true; //La tâche a réussi
}
}
}

Création et utilisation d'une tâche avec input - Fichier .proj

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

<UsingTask TaskName="TacheSimpleAvecInput"
AssemblyFile="C:\...\CustomTasks.dll" />

<Target Name="MainTarget">
<TacheSimpleAvecInput />
<TacheSimpleAvecInput Nom="Pierre-Emmanuel Dautreppe" />
</Target>

</Project>

Création et utilisation d'une tâche avec input - Exécution

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 25/02/2009 8:06:55.
Project "D:\Fichiers de build\Execute TacheSimpleAvecInput.proj" on node 0 (default targets).
Hello World
Hello Pierre-Emmanuel Dautreppe
Done Building Project "D:\Fichiers de build\Execute TacheSimpleAvecInput.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)

Time Elapsed 00:00:00.06

VI-B-2 - Ajouter des entrées obligatoires

Pour rendre un input obligatoire, il suffit d'ajouter un attribut « Required » sur la propriété et le framework va la gérer différemment.

Création et utilisation d'une tâche avec input obligatoire

public class TacheSimpleAvecInput : Task
{
[Required]
public string Nom
{
get { return nom; }
set { nom = value; }
}

// ...Reste de l'implémentation omise
}

En exécutant de nouveau le fichier de build précédent, nous avons le résultat suivant :

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 25/02/2009 8:13:15.
Project "D:\Fichiers de build\Execute TacheSimpleAvecInput.proj" on node 0 (default targets).
D:\Fichiers de build\Execute TacheSimpleAvecInput.proj(6,7): error MSB4044: The "TacheSimpleAvecInput" task was not given a value for the required parameter "Nom".
Done Building Project "D:\Fichiers de build\Execute TacheSimpleAvecInput.proj" (default targets) -- FAILED.

Build FAILED.

"D:\Fichiers de build\Execute TacheSimpleAvecInput.proj" (default target) (1) ->
(Target1 target) ->
D:\Fichiers de build\Execute TacheSimpleAvecInput.proj(6,7): error MSB4044: The "TacheSimpleAvecInput" task was not given a value for the required parameter "Nom".

0 Warning(s)
1 Error(s)

Time Elapsed 00:00:00.07

VI-C - Ajouter des sorties sur sa tâche

Une tâche peut également produire des résultats et les communiquer au build. Pour cela, il suffit d'ajouter une propriété marquée avec l'attribut « Output ».

On aura alors la possibilité dans le fichier de build d'utiliser un élément Output pour récupérer la valeur soit sous la forme de propriété, soit sous la forme d'item.

Création et utilisation d'une tâche avec Output - Code C#

using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;

namespace CustomTasks
{
public class TacheSimpleAvecOutput : Task
{
private string text;

[Output]
public string Text
{
get { return text; }
}

/// <summary>
/// Cette méthode sera appelée lorsque la tâche sera exécutée
/// </summary>
/// <returns>True si la tâche réussit, sinon false</returns>
public override bool Execute()
{
this.text = "Message écrit par la tâche simple";
this.Log.LogMessage(this.Text);
return true;
}
}
}

Création et utilisation d'une tâche avec Output - Fichier .proj

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

<UsingTask TaskName="TacheSimpleAvecOutput"
AssemblyFile="C:\...\CustomTasks.dll" />

<Target Name="Target1">
<TacheSimpleAvecOutput>
<Output TaskParameter="Text" PropertyName="OutputText" />
</TacheSimpleAvecOutput>
<Message Text="Tâche Message : $(OutputText)" />
</Target>
</Project>

Création et utilisation d'une tâche avec Output - Exécution

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3053]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 2/04/2009 18:23:12.
Project "D:\Fichiers de build\Execute TacheSimpleAvecOutput.proj" on node 0 (default targets).
Message écrit par la tâche simple
Tâche Message : Message écrit par la tâche simple
Done Building Project "D:\Fichiers de build\Execute TacheSimpleAvecOutput.proj" (default targets).

Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.03

VI-D - Lever des erreurs

Pour faire échouer une tâche et donc potentiellement le build, il y a plusieurs possibilités au sein même de notre classe héritant de « Task » :

- faire en sorte que la méthode « Execute » return « false » ;

- lancer une exception dans la méthode « Execute » ;

- utiliser les méthodes

- « LogError »,

- « LogErrorFromException »,

- « LogErrorFromResources »,

- « LogErrorWithCodeFromResources ».

VI-E - Déboguer sa tâche

Il est tout à fait possible de déboguer les tâches que l'on est en train de créer. Pour cela, il suffit de mettre un point d'arrêt dans la tâche et de déboguer le projet avec MsBuild. Pour cela :

- créer un fichier de build simple qui permet d'utiliser la tâche (en spécifiant dans la tâche « UsingTask » l'emplacement de la DLL compilée en debug)

- cliquer droit sur le projet (la Class Library .NET contenant la tâche) et « Propriétés » ;

- cliquer sur l'onglet « Debug » ;

- sélectionner l'option « Start External Program » et choisir MsBuild (« %windir%\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe ») ;

- aouter les « Command Lines Argument » nécessaires pour lancer le fichier de projet :

- typiquement les paramètres du type « /t » ou « /p » pour lancer un target spécifique de votre fichier, ou overrider une propriété.

Vous pouvez maintenant lancer votre projet en debug.

VII - Introduction à Team Foundation Server (TFS)

VII-A - Présentation rapide des possibilités du Team Foundation Server

Il serait vain de vouloir présenter le Team Foundation Server en quelques lignes alors que des livres entiers lui sont consacrés.

Je vais me contenter ici de présenter brièvement certaines des fonctionnalités de l'outil.

Le TFS intègre un Source Control puissant que Microsoft conseille d'utiliser pour les grandes entreprises. On y retrouve les fonctionnalités de vos Source Control préférés (SourceSafe ou SubVersion par exemple) dans un outil parfaitement intégré à Visual Studio.

Il inclut également un système d'intégration continue : à la fois un langage pour écrire des fichiers d'automatisation / de build, semblable à NAnt, mais également un serveur d'intégration appelé « Build Server ». C'est ce serveur qui va permettre de récupérer les sources à intervalle régulier et de lancer des tests.

Pour cela, on va créer des « Build Agent » avec lesquels le TFS va communiquer pour lancer les builds automatisés.

Le TFS propose également de gérer des Works Items, soit un système de gestion de tâches et d'incident. Ce système est directement couplé avec le mécanisme de build automatisé puisque dès qu'un build échoue, une nouvelle tâche sera créée automatiquement.

Enfin, il nous offre également des guides / templates de gestion de projet qui permettent entre autres de configurer plus facilement l'outil selon notre mode de fonctionnement.

VII-B - Présentation des différents serveurs entrant en jeu

Client : L'utilisateur / développeur qui va se connecter au Team Foundation Server a installé Visual Studio et le Team Explorer. Le Team Explorer peut-être vu comme un plugin de Visual Studio permettant la connexion et les interactions avec le TFS.

Team Server proxy : Ce composant (facultatif) sera typiquement utilisé sur des sites importants. Il permet de mettre en cache les requêtes faites au Team Foundation Server afin d'améliorer les performances. Typiquement ce proxy sera installé dans chacun des sites (société) qui vont se connecter sur un même Team Foundation Server.

Team Foundation Server : Il s'agit du serveur proprement dit, composé de l'« Application Tier » et du « Data Tier ». Ces deux tiers peuvent être installés sur le même serveur ou sur deux serveurs différents. Le « Data Tier » est toujours transparent pour l'utilisateur qui ne communiquera qu'avec « l'Application Tier ». Le « Data Tier » se repose sur Sql Server 2005. Notez que le TFS 2010 ne fonctionnera qu’avec Sql Server 2008.

Team Build Server : Il peut être installé sur un serveur dédié ou sur le Team Foundation Server. C'est le serveur sur lequel seront exécutés les builds demandés sur le serveur

VII-C - TFS et Gestion des Droits d'Accès

Le Team Foundation Server repose sur Active Directory et sur ADAM pour gérer les droits. Il est possible via le Team Explorer de paramétrer les droits des différents utilisateurs ou groupes.

Les droits peuvent être modifiés par un administrateur du Team Foundation Server selon la procédure suivante :

- aller dans le Team Explorer ;

- clic droit sur le projet pour lequel on veut ajouter des droits ;

- sélectionner « Team Project Settings » ;

- sélectionner « Security » ;

- sélectionner le groupe ou l'utilisateur pour lequel vous voulez modifier les droits ;

- cliquer sur « Allow » pour le droit à ajouter.

La gestion des droits dans le TFS est complexe, car elle concerne :
- les droits sur le serveur ;
- les droits sur un Team Project ;
- les droits sur le source control ou sur un fichier / répertoire spécifique.
Ces différents niveaux de sécurité vont donc être paramétrables à différents endroits (via les propriétés du Team Project, via le Source Control…) et il n'existe pour le moment aucune gestion centralisée possible

VIII - TFS et Intégration Continue

VIII-A - Créer un build

Le build se crée en deux étapes. On passe tout d'abord par un assistant de création pour les étapes les plus basiques puis le paramétrage du build se fera en modifiant le fichier XML produit. (9)

Pour créer un build, il faut impérativement avoir les droits de « Create a Build ».

Pour créer un build :

- aller dans le Team Explorer ;

- clic droit sur le projet pour lequel on veut ajouter un build ;

- sélectionner « Team Builds » et « New Build Definition » ;

- onglet Général

1. Taper le nom du build et la description ;

- onglet Workspace

1. Vous avez ici la possibilité de paramétrer le workspace utilisé par le build. Cela a la même signification que pour un développeur et va donc dicter les répertoires du Source Control auquel le mécanisme de build aura accès, et à quel endroit il va les télécharger localement,

2. Par défaut, aucune modification n'est requise ici ;

- onglet Project File

1. Un build se base sur un fichier de projet XML, d'extension .proj,

2. Visual Studio 2008 nous laisse la possibilité de modifier son emplacement,

3. Nous pouvons nous contenter de l'emplacement par défaut et donc simplement cliquer sur Create,

4. Visual Studio nous propose alors une fenêtre avec toutes les solutions disponibles dans le workspace que nous avons créé. A vous de cocher les solutions sur lesquelles vous voulez travailler dans le build automatisé, puis cliquer sur Next,

5. Sélectionner la configuration que vous voulez utiliser pour le build ;

- configuration : Debug ou Release selon le type de compilation que vous voulez faire ;

- Platform :

- .NET pour un site web,

- x86 pour une DLL,

- Mixed Platforms pour une solution comportant à la fois un site web et une DLL (10) ;

6. Cliquez sur « Next » ;

7. Vous pouvez ici préciser si vous voulez exécuter des tests lors de votre build automatisé. Pour cela, vous pouvez soit vous baser sur des listes de tests (.vsmdi) ou laisser le build découvrir les tests exécutables dans les DLL que vous lui spécifiez ;

8. Vous pouvez ensuite cliquer sur Finish ;

- onglet Retention Policy

1. Vous allez lancer de très nombreux builds et ils peuvent prendre beaucoup de place sur le serveur. Cet onglet vous permet de spécifier ce que vous voulez faire de vos builds : tous les garder, les supprimer…

2. Notez que ce choix va probablement dépendre de la taille de vos builds et de votre propre politique de build ;

- onglet Build Defaults

1. Choisissez le Build Agent par défaut qui lancera votre build. L'agent est simplement le serveur de build. Ce choix par défaut pourra être « overridé » lors de chaque lancement de build.

2. Indiquez ensuite le chemin réseau sur lequel vous voulez que les fichiers de votre build soient copiés une fois celui-ci terminé

- onglet Triggers

1. Vous avez ici la possibilité de paramétrer à quelle fréquence vous voulez que votre build se lance.

2. Deux choix typiques sont de le lancer chaque nuit, mais également à chaque remontée de code (check-in)

VIII-B - Éditer un build

Le build étant composé de deux parties (les éléments introduits dans la fenêtre « Build Definition » et ceux du fichier .proj), l'édition va se faire également à ces deux endroits.

Pour éditer les propriétés générales, il suffira de cliquer droit sur le build en question et de choisir Edit Build Definition pour que l'on puisse changer les valeurs introduites dans ce wizard.

Pour le fichier .proj, on pourra :

- cliquer droit sur le build ;

- cliquer sur View Configuration Folder ;

- une fois la fenêtre du Source Control ouverte, alors on pourra double cliquer sur le fichier .proj pour le modifier.

Attention : la fenêtre de Build Definition se sauvegarde directement en base de données. Par conséquent, chaque modification sera immédiate et prise en compte dans le build suivant.
En revanche, le fichier .proj, est un fichier du Source Control comme les autres, et on devra donc faire un « check-in » avant que ses modifications soient effectives.
Il est important de prendre en compte cette différence de façon à synchroniser sa façon de travailler avec les builds automatisés que l'on veut lancer.

VIII-C - Exécuter un build

VIII-C-1 - Via VS

Il y a deux façons d'exécuter un build : de façon graphique ou en ligne de commande.

- Aller dans le Team Explorer.

- Ouvrir le nœud du Team Project souhaité.

- Ouvrir le nœud « Team Builds ».

- Soit

- cliquer droit sur le build souhaité ;

- cliquer sur « Queue New Build ».

- Soit

- bouble cliquer sur le build souhaité. La liste des différents builds déjà réalisés est alors affichée ;

- cliquer sur l'icône Build en haut à gauche de la fenêtre.

- On aura alors la possibilité de changer l'agent (ie le build server) qui va lancer le build, sa priorité, et l'emplacement de copie une fois le build terminé.

- Cliquer sur « Queue ».

VIII-C-2 - En ligne de commande

Les builds sont en fait exécutés par l'outil TFSBuild.exe. Cet outil fera en interne appel à MsBuild.

TFSBuild se trouve dans le répertoire « %ProgramFiles%/Microsoft Visual Studio 9.0/Common7/IDE ».

TFSBuild : Lancer un build

TFSBuild start TeamFoundationServerName TeamProject BuildTypeName /queue
TFSBuild start svrTFS MyTeamProject "Mon Team Build" /queue

L'option « /queue » est optionnelle, mais permet de rendre la commande non bloquante, en insérant le build à lancer dans la queue des builds, comme cela peut être fait lorsque l'on passe par l'interface graphique.

Dans l'exemple, nous demandons l'exécution d'un build - nommé « Mon Team Build » - du Team Project « MyTeamProject », et ce sur le serveur « svrTFS ».

VIII-D - Paramétrer un build

VIII-D-1 - Quelques tâches disponibles

Le framework .NET 3.5 vient déjà avec quelques tâches prédéfinies : il s'agit des tâches disponibles dans la DLL Microsoft.Build.Tasks ou dans Microsoft.Build.Tasks selon la version que vous utilisez.

Cependant le fait de travailler avec le Team Foundation Server met à notre disposition d'autres DLL et d'autres tâches, spécialisées dans l'intégration continue.

VIII-D-1-a - Microsoft.Build.Tasks

MsBuild est déjà livré avec un certain nombre de tâches par défaut. Pour connaître la liste complète, veuillez visiter la bibliographie « Liste des tâches fournies dans Microsoft.Build.Tasks ».

On peut cependant lister quelques-unes des tâches qui seront utilisées le plus souvent :

Liste des tâches principales disponibles dans le Framework

Tâche

Description

AspNetCompiler

Va précompiler un site web

Copy

Copie de fichiers dans le système de fichiers

CreateItem
CreateProperty

Création d'Item et de Property

Csc
Vbc

Compilation de projets C# et VB.NET

Delete

Suppression de fichiers dans le système de fichiers

Exec

Exécution d'une action en ligne de commande

GetFrameworkPath

Retourne le chemin d'installation du framework (pour lequel cette version de MsBuild a été buildée)

MakeDir
RemoveDir

Création et Suppression de répertoires dans le système de fichiers

VIII-D-1-b - Tâches liées au TFS

On peut entre autres citer deux DLL qui contiennent des tâches liées à l'intégration continue : « Microsoft.TeamFoundation.Build.Tasks.dll » et « Microsoft.TeamFoundation. Build.Tasks.VersionControl.dll ».

On peut cependant lister quelques-unes des tâches qui seront utilisées le plus souvent :

Liste des principales tâches liées au Source Control

Tâche

Description

Get

Effectue un téléchargement des sources du Source Control dans le répertoire du Workspace courant

Label

Met un label sur certaines sources du Source Control

CreateWorkspaceTask
DeleteWorkspaceTask

Crée (ou supprime) un workspace dans le TFS pour l'utilisateur courant

VIII-D-2 - Quels sont les targets que l'on peut overrider

Lorsque l'on crée un nouveau Build, tout le processus de récupération des sources, compilation, test… est déjà prévu par le TFS.

Pour cela, le fichier de build que nous allons créer importe un fichier de Visual Studio : « Microsoft.TeamFoundation.Build.targets », fichier qui se trouve dans le répertoire « %ProgramFiles%\MSBuild\Microsoft\VisualStudio\TeamBuild ».

Ce fichier décrit l'intégralité du Workflow de build et propose des « zones d'accrochage », autrement dit, des emplacements de paramétrage du fichier afin de nous laisser le plus de liberté possible pour notre build.

Vous trouverez ci-dessous le workflow complet qui est exécuté dans le build, avec le nom de tous les targets qui sont appelés. En gras sont notés les targets vides qui sont explicitement prévus comme zones d'accroche.

- CanonicalizePaths

- EndToEndIteration

1. CheckSettingsForEndToEndIteration

2. InitializeBuildProperties

3. BeforeEndToEndIteration

4. BuildNumberOverrideTarget

5. InitializeEndToEndIteration

6. InitializeWorkspace

1. BeforeInitializeWorkspace

2. CoreInitializeWorkspace

3. AfterInitializeWorkspace

7. TeamBuild

1. CleanAll

1. BeforeClean

2. CoreCleanAll

3. AfterClean

2. InitializeBuild

3. PreBuild

1. Get

- BeforeGet

- CoreGet

- AfterGet

2. Label

- BeforeLabel

- CoreLabel

- AfterLabel

4. CleanCompilationOutput

1. BeforeClean

2. CallClean

3. AfterClean

5. Compile

1. BeforeCompile

2. CallCompile

3. AfterCompile

6. PostBuild

1. GetChangesetsAndUpdateWorkItems

- BeforeGetChangesetsAndUpdateWorkItems

- CoreGetChangesetsAndUpdateWorkItems

- AfterGetChangesetsAndUpdateWorkItems

7. Test

1. BeforeTest

2. CoreTest

3. AfterTest

8. GenerateDocumentation

9. PackageBinaries

8. DropBuild

1. BeforeDropBuild

2. CoreDropBuild

3. AfterDropBuild

9. AfterEndToEndIteration

Il est également tout à fait possible de redéfinir un des autres targets si l'on décide de se passer totalement d'un comportement standard. Par exemple si l'on décide de faire nous-mêmes la récupération des sources dans le Source Control, alors on décidera de redéfinir le target Get ou CoreGet.

Notez cependant que le workflow est très ouvert et que chaque tâche est hautement configurable en fonction des propriétés ou des items déclarés dans le fichier. Il peut être plus facile de changer une simple propriété que de réécrire un target.

IX - TFS et Gestion

L'interface offerte par le Team Explorer est parfois limitée. Il y a en effet de nombreuses commandes qui ne sont pas disponibles. Cependant quasiment tout est faisable en ligne de commande via l'outil « tf.exe » disponible sur « %ProgramFiles%/Microsoft Visual Studio 9.0/Common7/IDE ».

Vous trouverez plus d'informations sur cet outil dans la bibliographie « Les différentes possibilités de gestion offertes par « tf.exe » ».

IX-A - Quelques commandes utiles

IX-A-1 - Détruire des fichiers dans le Source Control

On peut supprimer des fichiers dans le Source Control, mais le Team Explorer ne nous propose pas de les détruire. Par « détruire », on parle ici d'un équivalent du « Destroy permanently » de Visual Source Safe.

En effet, lorsque l'on supprime un fichier, il est simplement noté comme « Supprimé » dans le Source Control. Si on le détruit, il est physiquement supprimé du Source Control et on ne pourra plus le récupérer.

Détruire un élément du Source Control

tf destroy $/MyTeamProject/MyItem.cs

IX-A-2 - Faire un « Undo » sur des fichiers

TFS nous permet de faire un « Undo Check-Out » sur les fichiers sur lesquels nous avons pris la main, autrement dit d'annuler nos modifications locales.

Cependant nous ne pouvons faire ceci via l'interface de Visual Studio que sur les fichiers pour lesquels nous avons fait un « Check-Out » sur la machine courante.

Si nous voulons annuler nos modifications en cours sur une autre machine, nous devons pour cela utiliser une ligne de commande :

Annuler nos modifications en cours sur un élément du Source Control

tf undo /workspace:MonWorkspace;MonLogin $/MyTeamProject/MyItem.cs

Nous pouvons également utiliser une option « /recursive » de façon à travailler au niveau d'un répertoire.

IX-A-3 - Lister les fichiers actuellement en Check-Out

Une bonne pratique avant de livrer une version est de vérifier que plus aucun développeur n'a du travail en cours, ou des fichiers « oubliés » sur une machine. Autrement dit, s'assurer qu'il n'y a plus aucun fichier en « Check-Out » qui devrait être remonté sur le serveur.

Pour cela, on peut utiliser la commande « status » :

Lister les fichiers en cours de modification dans le Source Control

tf status $/MyTeamProject /recursive /user:*

On peut réduire la recherche à un élément particulier (un répertoire ou un fichier) en précisant le chemin complet de l'élément, et non pas en se contentant d'indiquer le nom du Team Project.

On peut également se restreindre à un utilisateur particulier en remplaçant l'« * » par le login de l'utilisateur.

Notez que l'on peut utiliser une option supplémentaire : « /format:detailed » de façon à obtenir plus d'informations sur les fichiers en check-out :

Résultat de la comande « tf status » avec un format de sortie

IX-B - Présentation de TFS Power Tools

Les commandes de gestion offertes par Visual Studio et le Team Explorer ne sont pas totalement satisfaisantes, et l'utilisation de lignes de commande pour effectuer des tâches quotidiennes peut être pénible. (11)

Il existe cependant un projet Open Source appelé « TFS Power Tools » qui nous permet de compléter cette interface graphique. Cet outil est téléchargeable sur http://msdn.microsoft.com/en-us/teamsystem/bb980963.aspx.

Le Team Explorer, avant et après installation des TFS Power Tools

Quelques avantages que l'on peut lister :

IX-B-1 - Nouvelles « check-in policies »

Nous n'avons pas abordé le point des « check-in polocies » dans ce document. En quelques mots, cela permet de définir des règles à respecter avant de pouvoir remonter (faire un check-in) des fichiers dans le Source Control. Deux exemples typiques : « ajouter un commentaire lors de la remontée » et « que tous les tests aient été exécutés et soient réussis ».

Pour ajouter une check-in policy, il suffit d'aller dans le Team Explorer et de faire un clic droit sur le Team Project pour sélectionner « Team Project Settings » puis « Source Control ». L'onglet « Check-in Policies » nous permet alors de configurer les policies que l'on veut utiliser sur notre projet.

Comme Visual Studio et le Team Foundation Server sont totalement extensibles, il est également possible de créer vos propres « Check-in Policies ».
Néanmoins, cela nécessite de rentrer dans le modèle objet du TFS, et dépasse donc le cadre de ce document.

IX-B-2 - Menu contextuel sur les membres d'un Team

Les Power Tools vont également lister les différents membres d'un Team Project. Sur chaque membre, on trouvera un menu contextuel nous offrant les options suivantes :

- Visualiser la liste des check-in réalisés ;

- Visualiser la liste des shelvesets (12) ;

- Visualiser la liste des fichiers actuellement en check-out, et ce quel que soit le workspace de la personne.

La liste des fichiers en check-out nous permet alors de faire facilement des « Undo » sur ces fichiers, pour autant que l'utilisateur en ait le droit.

IX-B-3 - Gestion simplifiée des alertes

Il est tout à fait possible dans le Team Foundation Server d'ajouter des alertes, c'est-à-dire typiquement d'envoyer des mails lorsqu'un événement se produit sur le serveur.

Deux alertes typiques :

- avertir lorsqu'un build se termine (et qu'il échoue) ;

- avertir si un utilisateur fait un check-in de certains fichiers (typiquement s'ils sont impactant pour d'autres utilisateurs.

Une alerte se base sur une requête sur différents tables et champs du TFS.

Configuration d'une alerte pour avertir si un build échoue

X - Tips & Tricks

X-A - Où puis-je trouver des tâches supplémentaires ?

Visual Studio et le TFS proposent un ensemble de tâches par défaut que l'on peut utiliser pour paramétrer notre build automatisé.

Cependant, on va rapidement avoir des besoins plus larges par exemple pour :

- lancer des outils en ligne de commande (FxCop, SandCastle, StyleCop…) ;

- manipuler la base de registre ;

- manipuler la GAC ;

- manipuler l'Active Directory ;

- travailler avec des fichiers textes ou XML ;

- créer ou configurer des sites web dans IIS.

Pour réaliser ce genre de tâches, il sera nécessaire de passer par d'autres types de tâches. On peut trouver sur CodePlex un projet qui nous expose plus d'une centaine de tâches supplémentaires répondant à la très grande majorité des besoins de paramétrage pour vos projets.

Vous pouvez télécharger ce projet sur http://www.codeplex.com/sdctasks.

X-B - Comment modifier le processus de « Get » dans le build ?

Si le processus de « Get » qui est fait par défaut dans le build ne vous convient pas (parce qu'il ramène trop de fichiers par exemple), il est possible de le modifier.

X-B-1 - En modifiant le workspace utilisé par le build

Le build va récupérer l'intégralité des fichiers qui sont définis dans son workspace. À condition que la liste des fichiers à télécharger soit statique (13), on peut alors se contenter de ne travailler sur la liste de mapping du workspace utilisé par le build.

Pour la modifier :

- clic droit sur le build et choisir « Edit Build Definition » ;

- c'onglet « Workspace » nous permet d'éditer les mappings.

X-B-2 - En paramétrant la tâche « Get » qui est faite par le serveur

Si par défaut le build télécharge l'intégralité des fichiers du workspace, il est possible de surcharger ce comportement de façon à ne récupérer que certains répertoires du workspace.

Pour cela, il suffit de redéfinir la propriété « GetFileSpec » qui est utilisée en interne.

Attention : ceci ne marchera que si le fichier ou répertoire que vous spécifiez ici apparait bien dans votre workspace.
Cette propriété n'est en effet utilisée que pour faire un GET d'une partie du workspace.

X-B-3 - En réécrivant la tâche « Get »

Enfin si aucune des solutions précédentes ne fonctionne, vous pouvez « tout simplement » réécrire vous même le processus de « Get » dans votre fichier de build.

Pour cela, on overridera le target « CoreGet ».

Target "CoreGet" dans sa version "par défaut"

<Target Name="CoreGet"
Condition=" '$(SkipGet)'!='true' and '$(IsDesktopBuild)'!='true' "
DependsOnTargets="$(CoreGetDependsOn)" >

<!-- Get the sources for the given workspace-->
<Get TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Workspace="$(WorkspaceName)"
Version="$(GetVersion)"
Filespec="$(GetFilespec)"
PopulateOutput="$(GetPopulateOutput)"
Overwrite="$(GetOverwrite)"
Preview="$(PreviewGet)"
Recursive="$(RecursiveGet)"
Force="$(ForceGet)">
<Output TaskParameter="Gets" ItemName="Gets" />
<Output TaskParameter="Replaces" ItemName="Replaces" />
<Output TaskParameter="Deletes" ItemName="Deletes" />
<Output TaskParameter="Warnings" ItemName="GetWarnings" />
</Get>

<SetBuildProperties Condition=" '$(GetVersion)' != '$(SourceGetVersion)' "
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
SourceGetVersion="$(GetVersion)" />

<PropertyGroup>
<SourceGetVersion>$(GetVersion)</SourceGetVersion>
</PropertyGroup>

</Target>

X-C - Comment peut-on récupérer des sources de plusieurs Team Project ?

Si vous avez besoin de récupérer des sources se situant dans un autre Team Project - pour que la compilation se passe correctement par exemple - alors le plus simple est de modifier le workspace de votre build.

Il s'agit en fait de modifier le processus de « Get », et vous trouverez les différentes façons de faire au paragraphe « Comment modifier le processus de « Get » dans le build ? ».

X-D - Je crée une propriété utilisant le « BuildNumber », mais celle-ci est vide. Pourquoi ?

Dans la section « Propriétés d'un fichier de build : les Properties et PropertyGroup », nous avions vu les différences entre propriétés statiques et propriétés dynamiques.

La propriété « BuildNumber » est une propriété créée dynamiquement par un Target du processus de build par défaut. Par conséquent, vous ne pouvez l'utiliser que dans une autre propriété créée dynamiquement.

Sinon, vous utiliseriez la propriété avant que sa valeur ne soit spécifiée.

XI - Conclusion

Nous avons pu pénétrer dans cette partie au cœur même de l'intégration continue grâce à MsBuild et à TfsBuild.

Nous avons donc maintenant - virtuellement - la possibilité d'automatiser tout process que nous faisons au sein de nos phases de développement, test et déploiement. Virtuellement seulement, puisqu'il faut que cela soit faisable en code .NET.

Nous pouvons donc maintenant modifier nos fichiers de projet afin d'inclure des références conditionnelles (14), ou des événements de pré ou postcompilation (15). Nous allons également pouvoir travailler sur des fichiers d'automatisation de déploiement ou d'intégration continue en paramétrant finement nos applications.

Avec l'article précédent nous présentant MsTest et Static Code Analysis, nous avons donc couvert les outils principaux offerts par Visual Studio et Team Foundation Server pour mettre en place l'intégration continue sur nos projets.
Peut-on aller plus loin ?
Oui le Team Foundation Server est basé sur un modèle très ouvert et nous permet donc de paramétrer bien davantage nos projets selon notre méthodologie de fonctionnement : travail avec des Work Items (pour le suivi des tâches ou des incidents), paramétrage des itérations, gestion de modèles de documents…
Il peut nous permettre d'aller encore plus loin dans les tests et de réaliser des tests de charge et de performance, des tests sur un jeu de données, ou encore des tests ciblant directement une application web, Windows ou WPF (16).
On peut également développer facilement des outils de statistiques en se basant sur l'API offerte par le TFS ou même créer nos propres règles à suivre lors d'un check-in.
Bref autant dire que j'espère - via ces deux articles - avoir suscité l'envie de plonger dans les immenses possibilités offertes par ces produits.

Remerciements

Je tiens à remercier particulièrement les personnes suivantes :

- Norman Deschauwer et Thierry Thoua qui m'ont - à leur insu - grandement motivé à terminer cet article… afin de pouvoir en commencer d'autres ;

- Louis-Guillaume Morand qui a toujours été présent pour m'assister dans l'intégration de ces articles sur Developpez.com ;

- Thibaut Van Spaandonck qui a pris beaucoup de temps pour relire cet article et le commenter ;

- Toute l'équipe de Developpez.com qui améliore sans cesse ses outils pour les rédacteurs d'articles.

Contact

Pour plus d'informations, vous pouvez me contacter (via le forum de Developpez.com) ou par mail sur pedautreppe[arobase]skynet.be
N'hésitez pas à me contacter pour me signaler des imprécisions ou des erreurs dans cet article ainsi que si vous aimiez que je détaille davantage une des sections dans un article ultérieur.

Vous pourrez trouver également toute une série d'articles plus courts (et en anglais) traitant de .NET, des VSTO ou encore des méthodologies agiles sur mon blog

XII - Annexes - Bibliographie

XII-A - Structure d'un fichier de build

MSDN 2008

http://msdn.microsoft.com/en-us/library/bcxfsh87.aspx

MSDN 2005

http://msdn2.microsoft.com/en-us/library/bcxfsh87(VS.80).aspx.

MSDN (local)

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_fxgenref/html/d1cda56a-dbef-4109-9201-39e962e3f653.htm

Section document

Structure d'un fichier de build

XII-B - Table des codes ASCII

MSDN 2008

http://msdn.microsoft.com/en-us/library/4z4t9ed1(VS.80).aspx

MSDN 2005

http://msdn.microsoft.com/en-us/library/4z4t9ed1(VS.80).aspx

MSDN (local)

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/script56/html/vsmscANSITable.htm
ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/script56/html/vsmscANSITable2.htm

Section document

S'assurer de la validité du fichier de build

XII-C - Conditions utilisables dans un fichier de build

MSDN 2008

http://msdn2.microsoft.com/en-us/library/7szfhaft.aspx

MSDN 2005

http://msdn.microsoft.com/en-us/library/7szfhaft(VS.80).aspx

MSDN (local)

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_fxgenref/html/9d7aa308-b667-48ed-b4c9-a61e49eb0a85.htm

Section document

Création dynamique d'Items

XII-D - Différents niveaux de verbosité

MSDN 2008

http://msdn.microsoft.com/en-us/library/microsoft.build.framework.loggerverbosity.aspx

MSDN 2005

http://msdn.microsoft.com/en-us/library/microsoft.build.framework.loggerverbosity(VS.80).aspx

MSDN (local)

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/fxref_microsoft. build.framework/html/f1722548-4018-8230-3b63-4289c232d389.htm

Section document

Surcharge de la verbosité d'exécution

XII-E - Liste des tâches fournies dans Microsoft.Build.Tasks

MSDN 2008

http://msdn.microsoft.com/en-us/library/7z253716(VS.80).aspx

MSDN 2005

http://msdn2.microsoft.com/en-us/library/7z253716.aspx

MSDN (local)

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_fxgenref/html/b3144b27-a426-4259-b8ae-5f7991b202b6.htm

Section document

Microsoft.Build.Tasks

XII-F - Les différentes possibilités de gestion offertes par « tf.exe »

MSDN 2008

http://msdn.microsoft.com/en-us/library/z51z7zy0.aspx

MSDN 2005

http://msdn.microsoft.com/en-us/library/z51z7zy0(VS.80).aspx

MSDN (local)

ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vsetsc04/html /6d06a590-5dea-4cb8-bf5d-d6446c8d0ec7.htm

Section document

Quelques commandes utiles

(1)Notez cependant que le fichier utilisé par Visual Studio est très complexe et dépasse largement le périmètre de ce document. Il est totalement déconseillé de le modifier sans avoir une parfaite connaissance de la syntaxe des commandes utilisées. Il sera cependant beaucoup plus simple pour un projet en particulier de travailler sur le fichier « .csproj » qui est lui-même un fichier de build.
Ces fichiers de compilation (d'extension « .targets ») sont présents dans le répertoire « %Windir%\Microsoft.NET\Framework\VersionFramework ».

(2)Notez que seules ces deux extensions seront reconnues par Visual Studio pour ajouter de la coloration syntaxique et de l'IntelliSense. Notez cependant que l'IntelliSense fournie dans ces types de fichiers est souvent approximative.

(3)Nous définirons plus loin la notion de target

(4)Dans la mesure où l'on est capable de faire cette action en code .NET : via une API propre, un utilitaire externe…

(5)On pourrait évidemment utiliser la tâche CreateProperty avant la tâche CallTarget, de façon à changer la valeur courante de la propriété, puis d'appeler de nouveau CreateProperty afin de repositionner la valeur d'origine. Cependant, nous serions très éloignés ici du principe de passage de paramètres.

(6)On peut passer des propriétés au target cible via la tâche MsBuild, donc on peut également passer toutes les propriétés courantes. Cela revient néanmoins à toutes les lister ce qui peut rendre l'exercice fastidieux, voire impossible.

(7)On peut utiliser une tâche CreateProperty de façon à créer une propriété qui sera utilisée par le target. Cependant on en revient dans ce cas à partager les propriétés existantes entre le target source et cible, puisque la nouvelle propriété sera également disponible pour le target source

(8)« Sdc Tasks Library » est un projet Open Source exposé sur CodePlex. Pour plus d'informations sur ce produit, vous pouvez vous rendre sur http://www.codeplex.com/sdctasks

(9)Notez que Visual Studio 2010 se basera sur Windows Workflow Foundation et nous permettra donc d'utiliser un éditeur graphique pour paramétrer les différentes « activités » de notre build automatisé.

(10)Notez que d'autres types de configuration existent, mais ne seront cependant pas décrits dans ce document.

(11)Dans la version 2005 de Visual Studio, il n'était pas possible de stopper ou de supprimer un build automatisé en cours d'exécution. L'utilisation de ligne de commandes était donc encore plus fréquente.

(12)Un « shelveset » est un emplacement de stockage isolé pour un utilisateur : il est en effet possible de remonter des parties de code dans le Source Control - afin d'en garantir la pérennité - sans pour autant les inclure dans la solution utilisée par tous les membres de l'équipe.
Ce shelveset peut être visualisé à tout moment par n'importe quel membre de l'équipe, et sur n'importe quelle machine. C'est également un des moyens qui peut être utilisé pour faire transiter du code d'un workspace à l'autre.
Il permet également de mettre en pause un développement en cours, de façon à se concentrer sur une autre tâche, sans risque de perdre le travail sur la tâche initiale, ni de polluer les remontées de la deuxième tâche dans le cas où les mêmes fichiers sont impactés.

(13)Par « statique », j'entends que la liste ne soit pas dépendante du moindre élément de configuration. On peut en effet se trouver face à des cas où certains fichiers ne seront récupérés que pendant les builds nocturnes de façon à travailler sur les conditions les plus proches de la production (où l'on ne va pas mocker certains éléments de l'application).

(14)Le fichier de projet étant un fichier de build, on peut utiliser des conditions sur les ItemGroup qui référencent nos DLL. Par conséquent, on peut imaginer d'inclure certaines versions de DLL en debug et d'autres versions en release

(15)Si on peut déjà mettre ce type d'événement ou de macros via les propriétés du projet, on a certaines limitations. Travailler directement au niveau du fichier de projet va nous permettre d'utiliser toutes les possibilités offertes par MsBuild.

(16)Le support des applications Windows et WPF n'est offert qu'à partir de la version 2010 de Visual Studio