Accueil Nos publications Blog MSBuild – Batching et intersection de collections

MSBuild – Batching et intersection de collections

MSBuild, l’outil de Build de Microsoft fournit une manière intéressante de faire des boucles et des conditions : le Batching. En effet, MsBuild, comme NAnt, peut être utilisé pour automatiser les déploiements. Il faut alors manipuler des ensembles de fichiers et il est monnaie courante d’avoir des règles spécifiques pour certains fichiers en fonction de leur extension, de leur répertoire etc… C’est là que la puissance du batching va vous aider à garder un code concis et lisible, ce qui n’est pas gagné d’avance dans un fichier xml…

Métadatas et Batching

Comment ça marche?

Dans les scripts MSBuild, chaque item (objet) possède des métadatas (champs). On accède à une liste d’items par @(NomDeLaListe), à la liste des métadatas par %(NomDeLaListe.NomDeLaMetadata).  Imaginons que nous avons une liste de fichiers à copier et que nous voulons copier que les fichiers exe. Le code sera alors le suivant :

<Copy SourceFiles="@(CopyFiles)" DestinationFolder="@(DestinationFolders)"
Condition=" '%(CopyFiles.Extension)' == '.exe' " />

En interne, MSBuild listera toutes les extensions présentes dans la collection CopyFiles. Il divisera ensuite la tâche Copy en autant de tâche Copy qu’il y a d’extensions différentes dans CopyFiles. Enfin, il n’effectuera que la tâche qui vérifie la condition CopyFiles.Extension == .exe . La division de la tâche en fonction des métadatas est appelé Batching.

Comment on l’utilise?

Le batching, bien que méconnu et sous-utilisé, est souvent la solution “miracle” à vos problèmes. Il permet de factoriser énormément le code. Par exemple, le code suivant effectue une copie récursive tout en gardant l’arborescence  :

<ItemGroup>
<CopyFiles Include="C:\tmp\**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(CopyFiles)" DestinationFolder="D:\Destination\%(RecursiveDir)"/>

RecursiveDir contient le chemin remplacé par les “**” dans la déclaration de CopyFiles. On peut remarquer qu’ici, vu que CopyFiles est la seule collection utilisée dans la tâche copy, on peut oublier de spécifier que c’est sur elle que l’on doit faire le batching (on ne met pas le nom de la collection devant le nom de la Metadata).

Il est aussi possible faire du batching en fonction d’une collection qui n’apparait pas dans la tâche :

<RegexReplace Input="@(Files)"
Expression="\.css"
Replacement="_%(Themes.Filename).css">
<Output ItemName ="ThemedFiles" TaskParameter="Output" />
</RegexReplace>

La tâche ci-dessus  crée, pour chaque fichier .css de la collection Files, pour chaque thème, un fichier suffixé par _NomDuTheme .

Utiliser le Batching pour manipuler des collections

Intersection de deux collections

Le batching peut être utilisé pour faire une jointure entre deux collections. Imaginons que vous avez deux listes de fichiers et que vous voulez ne gardez que ceux qui apparaissent dans les deux collections. La manière classique serait :

<APrivedeB Include="@(SetA)" Exclude="@(SetB)"/>
<AinterB Include="@(SetA)" Exclude="@(APrivedeB )"/>

Avec le batching, on peut le faire en une seule ligne, il faut partager la métadata entre les deux collections :

<CreateItem Include="@(SetA)" Condition=" @(SetB) != '' and '%(identity)' != ''">
<Output TaskParameter="Include" ItemName="AinterB"/>
</CreateItem>

Dans la tâche ci-dessus, nous avons mentionné deux collections, mais aussi une métadata (identity, qui correspond au chemin d’un fichier renseigné à sa création dans MSBuild) sans préciser l’itemgroup (collection) auquel elle se rattache. Dans ce cas, MSBuild va rattacher la métadata aux deux itemgroups. On aura l’équivalent d’une jointure externe SQL. C’est là que la condition @(SetB) != ” intervient à nouveau. En effet, pour une identité donnée, présente dans A, si cette dernière n’est pas présente dans B cette condition ne sera pas satisfaite.

Pour mieux comprendre

L’exemple suivant  (tiré de ce très bon article) , peut aider à comprendre:

<ItemGroup>
<GroupA Include="file1.txt"/>
<GroupA Include="file2.txt"/>
<GroupA Include="file3.txt"/>
<GroupA Include="file4.txt"/>
</ItemGroup>

<ItemGroup>
<GroupB Include="file1.txt"/>
<GroupB Include="file3.txt"/>
<GroupB Include="file5.txt"/>
</ItemGroup>

<Message Text="|GroupA| - |Identity| - |GroupB|"/>
<Message Text="|@(GroupA)| - |%(Identity)| - |@(GroupB)|"/>

Produira la sortie suivante :

|GroupA| - |Identity| - |GroupB|
|file1.txt| - |file1.txt| - |file1.txt|
|file2.txt| - |file2.txt| - ||
|file3.txt| - |file3.txt| - |file3.txt|
|file4.txt| - |file4.txt| - ||
|| - |file5.txt| - |file5.txt|

Sources/Aller plus loin

Explication du “Batching Brainteaser”
Documentation du batching sur MSDN
Le batching expliqué par THE expert MsBuild
Introduction aux transformations sur MSDN
Description des métadatas de base