MSBuild en 5 min

MSBuild est un moteur de compilation utilisé par Visual Studio pour effectuer des opérations de compilation ou de nettoyage. Lorsque ces opérations sont lancées au moyen de l’interface, c’est l’exécutable msbuild.exe qui sera, en fait, exécuté.

Ainsi, à partir du fichier de la solution ou des fichiers de projets, MSBuild va ordonner et lancer la compilation en utilisant le compilateur csc.exe de façon à générer une application exécutable ou une assembly. Pour connaître la façon d’ordonner la compilation et faire appel à csc.exe avec les bons arguments, MSBuild utilise les informations contenues dans le fichier solution ou dans les fichiers de projet. De ce fait, ces fichiers constituent des espèces de scripts lisibles par MSBuild.

MSBuild est découplé de Visual Studio ce qui permet de:

  • L’utiliser à partir de la ligne de commandes sur des serveurs sans lancer Visual Studio,
  • Personnaliser le mécanisme de compilation pour effectuer des opérations particulières, par exemple, avant la compilation ou pour copier le résultat de compilation dans un répertoire particulier etc…

Le point d’entrée de msbuild.exe est un fichier solution (i.e. un fichier de type .sln) ou un fichier projet (i.e. un fichier de type .csproj, .vbproj, .vcxproj etc…).

Comment accéder à MSBuild ?

Avant Visual Studio 2013, MsBuild était livré avec le Framework .NET et sa version était couplée avec celle du Framework .NET. Depuis Visual Studio 2013 et le Framework .NET 4.5.1, MSBuild est livré avec Visual Studio et sa version est désormais couplée avec celle de Visual Studio. Toutefois l’exécutable msbuild.exe reste découplé de Visual Studio et il est possible de l’exécuter sans que Visual Studio ne soit lancé.

Pour plus d’informations sur les versions de MSBuild: Versions des composants .NET.

Chemin de msbuild.exe

Pour connaître les versions de msbuild.exe sur le disque, il faut aller dans la base de registres et consulter la valeur MSBuildToolsPath des clés se trouvant dans:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSBuild\ToolsVersions\

Il y a autant de clés que de versions du Framework .NET installées. Jusqu’à la version 4.0 (et jusqu’au Framework .NET 4.0 inclus), msbuild.exe se trouve dans le répertoire Windows:

[répertoire de windows]\Microsoft.NET\Framework64\[version du framework .NET]\

Par exemple:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\

Ou

C:\Windows\Microsoft.NET\Framework64\v2.0.50727\

A partir de Visual Studio 2013, msbuild.exe se trouve dans:

[répertoire Program Files (x86)]\MSBuild\[version Visual Studio]\bin\amd64\

Par exemple:

C:\Program Files (x86)\MSBuild\12.0\bin\amd64\

Ou

C:\Program Files (x86)\MSBuild\14.0\bin\amd64\

Comment appeler MSBuild ?

Pour lancer la compilation d’une solution

Pour compiler une solution à partir du fichier .sln et en précisant la configuration de compilation et la plateforme cible (la configuration et la plateforme doivent être définis dans le fichier “.sln”), il faut exécuter:

msbuild.exe "solutionName.sln" /p:configuration=Debug /p:platform="Any CPU"

Les propriétés "/p" sont facultatives.

Appeler une “target” particulière

Les actions de génération (build), de nettoyage (clean) ou de regénération (rebuild) correspondent à des “targets” prédéfininies. Il est possible d’exécuter une “target” sur projet en tapant:

msbuild.exe "[chemin du fichier projet]" /t:[nom des "targets" séparées par ";"]

Par exemple:

msbuild.exe "projectName.csproj" /t:build

Personnaliser une chaine de build avec MsBuild

Le gros intérêt de s’intéresser à MSBuild est de pouvoir personnaliser des comportements de la chaine de build. MSBuild utilise en entrée:

  • des fichiers projet de type .csproj, .vbproj, .vcproj, .vcxproj etc…: se sont des projets typiques de Visual Studio.
  • des fichiers de type .proj ou .targets: ce sont des fichiers qui ne sont pas des projets Visual Studio mais respectant la syntaxe attendue par MSBuild.

Ces fichiers définissent des éléments qui seront lus par MSBuild pour effectuer ces différentes actions. Les actions ne sont pas forcement des actions de génération ou nettoyage, l’intérêt est de pouvoir définir et exécuter d’autres actions comme faire appel à un script powershell, effectuer des copies de fichiers, afficher un message etc…

Les éléments principaux d’un fichier projet exécutable par MSBuild sont:

  • target: cet élément est désigné par un nom qui pourra être appelé en argument de MSBuild de façon à indiquer l’action qui doit être réalisée. Les “targets” prédéfinies sont "build", "clean" et "rebuild" mais il est possible d’en définir d’autres. Les targets exécutent des “tasks”.
  • task: ce sont des actions simples à exécuter, comme par exemple, faire une copie de fichiers ou lancer une compilation etc…
  • item: généralement MSBuild traite une liste d’éléments, le plus souvent une liste de fichiers.
  • property: il s’agit d’une variable dont on définit la valeur et qui est réutilisable dans le reste du script.

Fichier projet

Ce fichier contient la définition des éléments définis plus haut de façon à ce que MSBuild exécute des actions.
Un fichier projet doit contenir un noeud Project, par exemple:

<?xml version="1.0" encoding="utf-8" ?> 
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
  <Target Name="Build"> 
    <Message Text="Hello world" /> 
  </Target> 
</Project>

Pour exécuter MSBuild avec ce fichier, il faut taper:

msbuild.exe helloWorld.proj /t:Build

DefaultTargets

Si on précise une “target” par défaut:

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

On peut exécuter directement la “target” en tapant:

msbuild.exe helloWorld.proj

Property

On peut définir des variables pour lesquelles on affecte des valeurs dans des noeuds PropertyGroup:

<PropertyGroup> 
  <[nom de la propriété]>[valeur de la propriété]</[nom de la propriété]> 
</PropertyGroup> 

On peut ensuite réutiliser une propriété en utilisant la syntaxe:

$([nom de la propriété])

Par exemple:

<PropertyGroup> 
    <OutputDirectory>bin\debug</OutputDirectory> 
    <OutputAssembly>$(OutputDirectory)\HelloWorld.exe</OutputAssembly> 
    <Optimize>false</Optimize> 
</PropertyGroup> 

Portée des propriétés

La portée des propriétés n’est pas la même si elles sont définies directement dans le noeud Project du fichier projet ou dans un noeud target.

Propriétés réservées

Certaines propriétés sont réservées et reconnues directement par MSBuild sans nécessiter d’en définir la valeur au préalable.
Par exemple:

Nom Valeur
MSBuildProjectDirectory Répertoire où se trouve le fichier projet
MSBuildProjectFile Nom du fichier projet
MSBuildProjectExtension Extension fichier projet
MSBuildProjectPath Chemin du fichier projet
MSBuildProjectDefaultTargets Liste des “targets” par défaut du fichier projet
MSBuildBinPath Répertoire où se trouve msbuild.exe

On peut trouver une liste plus exhaustive de ces propriétés sur MSDN.

Variables d’environnement

Elles sont accessible dans un fichier projet en utilisant directement son nom. Par exemple pour la variable d’environnement BIN_PATH:

<FinalOutput>$(BIN_PATH)\MyAssembly.dll</FinalOutput>

Valeur de la base de registre

Une valeur est accessible avec la syntaxe:

$(registry:[chemin de la clé]@[nom de la valeur])

Par exemple:

<PropertyGroup> 
  <VisualStudioWebBrowserHomePage> 
    $(registry:HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\12.0\WebBrowser@HomePage) 
  </VisualStudioWebBrowserHomePage> 
<PropertyGroup> 

Propriétés dont la valeur est définie à l’appel de MSBuild

On peut utiliser directement des propriétés dans un fichier projet si leur valeur a été définie dans l’appel à MSBuild. Pour définir la valeur d’une propriété dans l’appel, on peut taper:

msbuild.exe [nom fichier projet] /p:[nom de la propriété]=[valeur de la propriété]

Par exemple:

msbuild.exe helloWorld.proj /p:platform=x64

Condition d’affectation

On peut préciser une condition d’affectation pour la valeur d’une propriété en utilisant l’attribut Condition dans le noeud de la propriété:

<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>

Plus de détails sur les propriétés sur MSDN.

Item

Ce sont des listes dynamiques d’éléments, le plus souvent, des listes de fichiers. Ces listes ont une portée similaire à celle des propriétés en fonction du noeud dans lequel elles sont définies.

Les valeurs des “items” sont calculées avant exécution des “targets”

Si ces listes sont fournies en entrée des “targets”, leur valeur est calculée avant d’exécuter les “targets”.

Un “item” est défini dans un noeud ItemGroup.

On parle d’un “item” mais il s’agit bien d’une liste d’éléments.

Définition d’items sans wildcards

Pour définir une liste simple d’items, on peut indiquer directement leur valeur dans un sous-noeud d’un noeud ItemGroup:

<ItemGroup> 
  <Compile Include = "file1.cs"/> 
  <Compile Include = "file2.cs"/> 
</ItemGroup>

Ou

<ItemGroup> 
  <Compile Include = "file1.cs;file2.cs"/> 
</ItemGroup>

Dans cet exemple “l’item” Compile contient une liste de fichiers.

Définition dynamique d’items avec wildcards

Les valeurs de l’item peuvent être calculées dynamiquement en fonction de la valeur des attributs Include et Exclude:

  • Include: permet d’indiquer les fichiers qui seront inclus dans la liste.
  • Exclude: permet d’indiquer les fichiers qui seront exclus de la liste.

Par exemple:

<ItemGroup> 
  <MyReleaseFiles Include=".\bin\debug\*.*" Exclude=".\bin\debug\*vshost.exe" /> 
</ItemGroup>

On peut utiliser les caractères de remplacement (i.e. wildcards) suivants pour les attributs Include et Exclude:

  • ?: permet d’indiquer la présence d’un caractère.
  • *: permet d’indiquer la présence de zéro ou plusieurs caractères.
  • **: permet d’indiquer une suite partielle de chemin, par exemple, "D:/**/*.cs" permet de désigner des fichiers .cs dans des répertoires différents sur le disque “D”.

Utiliser un “item”

Pour utiliser un “item”, il faut utiliser la syntaxe:

@([nom de la liste])

Ainsi la liste “MyReleaseFiles”:

<ItemGroup> 
  <MyReleaseFiles Include=".\bin\debug\*.*" /> 
</ItemGroup>

Peut être utilisée dans une “target”:

<Target Name="Release" > 
  <Copy SourceFiles="@(MyReleaseFiles)" DestinationFolder="$(MyReleaseOutput)" /> 
</Target>

Metadata

Il est possible de définir des metadatas par éléments de la liste de cette façon:

<ItemGroup> 
  <InputFile Include="One.cs" > 
    <Culture>false</Culture> 
  </InputFile> 
  <InputFile Include="Two.cs"> 
     <Culture>true</Culture> 
  </InputFile> 
</ItemGroup>

Plus de détails sur les metadatas sur MSDN.

Task

Une “task” est une action exécutée au sein d’une “target”. Une “target” peut comporter plusieurs “tasks”.
Pour appeler une “task”, il faut l’ajouter dans un noeud Target, par exemple la “task” MakeDir consistant à créer un répertoire, peut être appelée de la façon suivante:

<Target Name="MakeBuildDirectory"> 
  <MakeDir Directories="$(BuildDir)" /> 
</Target>

Les “tasks” font référence à une implémentation qui se trouve une classe .NET. Il existe plusieurs “tasks” usuelles prédéfinies dans Microsoft.Build.Framework. Toutefois il est possible de définir une “task” personnalisée avec une implémentation C#.

Utilisation d’un “item” en entrée de la “task”

Lorsqu’une “task” utilise un “item” défini en dehors de la “target” dans laquelle se trouve la “task”, “l’item” est calculé avant l’exécution de la “target”.
“L’item” qui est calculé à l’intérieur d’une “task” correspond au résultat de la “task”. Ce résultat doit être indiqué dans un élément Output.

Résultat d’une “task”

Une “task” peut éventuellement calculer un résultat. Si c’est le cas, ce résultat doit être rangé dans un élément Output. Cet élément doit se trouver à l’intérieur du noeud de la “task”:

<Target Name="CopyFiles"> 
  <Copy 
    SourceFiles="@(MySourceFiles)" 
    DestinationFolder="@(MyDestFolder)"> 
    <Output 
        TaskParameter="CopiedFiles" 
        ItemName="SuccessfullyCopiedFiles"/> 
  </Copy> 
</Target>

Dans cet exemple, le résultat de la “task” Copy qui est une liste de fichiers copiés, est calculée dans “l’item” SuccessfullyCopiedFiles. “CopiedFiles” est le paramètre de la “task” Copy qu’on souhaite affecter à “l’item” SuccessfullyCopiedFiles.
Sachant que le résultat de la “task” est rangé dans un “item”, on peut y accéder en utilisant la syntaxe:

@(SuccessfullyCopiedFiles)

Lorsque le résultat doit être rangé dans une propriété, dans le noeud Output, il faut utiliser l’attribut PropertyName, par exemple:

<Csc ... 
    <Output TaskParameter="BuildSucceeded" 
              PropertyName="BuildWorked" /> 
</Csc>

Comme une propriété normal, le résultat de la “task” rangé dans une propriété, peut être utilisée en utilisant la syntaxe:

$(BuildWorked)

Plus de détails sur l”élément Output sur MSDN.

“Tasks” courantes

Quelques “tasks” les plus courantes:

Nom Fonction
Message Affiche un message sur la console.
Copy Permet de copier un ou plusieurs fichiers d’un répertoire à un autre
Delete Supprimer un ou plusieurs fichiers
MakeDir Crée un ou plusieurs répertoires
RemoveDir Supprime un ou plusieurs répertoires
Exec Permet d’exécuter un processus externe
MSBuild Permet d’exécuter un ou plusieurs “targets” dans un fichier MSBuild externe
Csc Permet d’exécuter le compilateur C# pour produire un exécutable ou une assembly

Target

Une “target” correspond à une liste d’actions qu’il est possible d’exécuter en indiquant le nom de la “target” lors de l’appel à MSBuild. Une “target” est composée de plusieurs “tasks” qui correspondent à des actions à exécuter.

Le plus souvent le point d’entrée d’une “target” est un ou plusieurs “items”. Toutefois il faut avoir en tête que lorsque qu’un “item” défini en dehors de la “target”, est calculé avant l’exécution de la “target”.

Une “target” se définit à l’intérieur d’un noeud Target et est identifiable en utilisant l’attribut Name:

<Target Name="MakeBuildDirectory"> 
  <MakeDir Directories="$(BuildDir)" /> 
</Target>

Le résultat d’une “target” correspond au résultat qui pourrait éventuellement être défini dans une “task” de la “target” (voir Résultat d’une “task”).

Plus de détails sur les “targets” MSBuild sur MSDN.

Appeler une “target” lors de l’appel à msdbuild.exe

Il faut utiliser l’option "/t":

msbuild.exe /t:MakeBuildDirectory

Définir une dépendance entre les “targets”

On peut définir une dépendance entre “targets” en utilisant l’attribut DependsOnTarget dans le noeud Target:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
  <Target Name="Build"> 
    <!-- ... --> 
  </Target> 
  <Target Name="Release" DependsOnTargets="Build"> 
    <!-- ... --> 
  </Target> 
</Project>

Dans cet exemple, la “target” Release dépend de la “target” Build. Cette dépendance permet d’exécuter les “targets” dans un ordre qui permet de respecter les dépendances. Ainsi la “target” Build sera exécutée avant la target Release.

Appel d’une “target” dans un autre fichier projet

Pour appeler une “target” qui se trouve dans un autre fichier projet, il faut utiliser la “task” MSBuild:

<?xml version="1.0" encoding="utf-8" ?> 
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
  <Target Name="Build"> 
    <MSBuild Projects="msbuildintro.csproj" Targets="Build" /> 
  </Target> 
</Project>

Dans cet exemple, la “task” va exécuter la “target” Build dans le fichier projet msbuildintro.csproj.

Importer des “targets” d’un autre fichier MSBuild

On peut importer des “targets” définis dans un autre fichier MSBuild. Ce fichier peut être un fichier projet, toutefois ces “targets” sont généralement définis dans un fichier spécialisé avec une extension “.targets”.
L’import se fait en utilisant le noeud Import à l’intérieur du noeud Project du fichier projet:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
    <Import Project="$(CommonLocation)\General.targets" /> 
</Project>

Dans cet exemple, l’import se fait à partir du fichier se trouvant dans $(CommonLocation)\General.targets.

Fichiers “.targets” livrés avec MSBuild

Les projets Visual Studio comportent des “targets” prédéfinis: build, clean et rebuild. Ces “targets” sont définis dans des fichiers “.targets” qui sont livrés avec MSBuild. Ces fichiers sont importés dans le fichier projet de Visual Studio.
Les fichiers “target” livrés avec MSBuild sont:

  • Microsoft.Common.targets: pour l’importer il faut indiquer: <Import Project="Microsoft.Common.targets" />.
  • Microsoft.CSharp.targets: pour l’importer il faut indiquer: <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />.
  • Microsoft.VisualBasic.targets: pour l’importer il faut indiquer: <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />.

Ces fichiers se trouvent dans le répertoire de MSBuild. Jusqu’au Framework .NET 4.0, ce répertoire est:

[répertoire de windows]\Microsoft.NET\Framework64\[version du framework .NET]\

Par exemple:

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\

A partir de Visual Studio 2013, le chemin est:

[répertoire Program Files (x86)]\MSBuild\[version Visual Studio]\bin\amd64\

Par exemple:

C:\Program Files (x86)\MSBuild\12.0\bin\amd64\

“Task” personnalisée

Des “tasks” personnalisées peuvent être implémentées en utilisant directement du code C#.

Par exemple, le code C# de la “task” peut être implémenté dans une assembly. Il faut dériver la classe “task” à définir de Microsoft.Build.Framework.Task:

using System; 
using Microsoft.Build.Utilities; 
using Microsoft.Build.Framework; 
 
namespace MyTasks 
{ 
  public class AddTask : Task 
  { 
    private int argument1; 
    private int argument2; 
    private int sum; 
 
    [Required] 
    public int Argument1 
    { 
      get { return argument1; } 
      set { argument1 = value; } 
    } 
  
    [Required] 
    public int Argument2 
    { 
      get { return argument2; } 
      set { argument2 = value; } 
    } 
  
    [Output] 
    public int Sum 
    { 
      get { return sum; } 
      set { sum = value; } 
    } 
  
    public override bool Execute() 
    { 
      try 
      { 
        sum = argument1 + argument2; 
      } 
      catch (ArithmeticException e) 
      { 
        Console.WriteLine("Error occured during addition: {0}", e.Message); 
        return false; 
      } 
      return true; 
    } 
  } 
}

On utilise les attributs:

  • Required: pour indiquer un argument en entrée de la “task” qui est obligatoire,
  • Output: pour indiquer la propriété en sortie de la “task”.

On peut appeler la “task” à partir d’un fichier projet en utilisant l’élément UsingTask dans le noeud Project pour indiquer l’assembly dans laquelle se trouve la classe. On peut appeler la “task” en utilisant directement le nom de la classe AddTask:

<?xml version="1.0" encoding="utf-8" ?> 
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> 
  <UsingTask TaskName="MyTasks.AddTask" AssemblyFile=".\AddTask.dll"/> 
  <Target Name="Addition"> 
    <AddTask Argument1="10" Argument2="12"> 
      <Output TaskParameter="Sum" PropertyName="CalculatedSum" /> 
    </AddTask> 
  </Target> 
</Project>
One response... add one

A partir de Visual Studio 2017, le chemin de MsBuild.exe se trouve dans le répertoire de Visual Studio:
C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin

Dans le cas où on a installé MsBuild.exe à partir des “Build Tools”:
C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\MSBuild\15.0\Bin

Leave a Reply