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.
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#.
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>
- MSBuild tutorial: http://thomasardal.com/msbuild-tutorial/
- Customising your build process with MSBuild: http://www.developerfusion.com/article/84411/customising-your-build-process-with-msbuild/
- Getting Started with MSBuild: https://blogs.msdn.microsoft.com/msbuild/2010/02/25/getting-started-with-msbuild/
- MSBuild Concepts: https://msdn.microsoft.com/fr-fr/library/dd637714.aspx
D’autres propriétés MsBuild sont présentes sur cette page:
https://docs.microsoft.com/fr-fr/visualstudio/msbuild/common-msbuild-project-properties?view=vs-2015
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