Partager via


Comment MSBuild génère des projets

MSBuild est le moteur de build de Microsoft utilisé pour générer la plupart des projets Visual Studio. MSBuild appelle des compilateurs et d’autres outils pour générer votre code, mais inclut également des options de configuration et de personnalisation flexibles, ainsi que l’infrastructure pour la création non seulement de fichiers binaires compilés, mais également d’un large éventail d’autres artefacts de sortie. MSBuild est très configurable et personnalisable, mais pour tirer le meilleur parti de cette personnalisation, il est important de comprendre le fonctionnement de MSBuild. Dans cet article, vous allez découvrir comment MSBuild traite vos fichiers projet, qu’ils soient appelés à partir de Visual Studio ou à partir d’une ligne de commande ou d’un script. Connaître le fonctionnement de MSBuild peut vous aider à mieux diagnostiquer les problèmes et à personnaliser mieux votre processus de génération. Cet article décrit le processus de génération et s’applique en grande partie à tous les types de projets.

Le processus de génération complet se compose de

  • démarrage initial : traitement des options de ligne de commande.
  • évaluation : interprétation et traitement du texte du fichier projet MSBuild.
  • exécution : exécute les cibles et les tâches qui créent le projet.

Outre les fichiers sources et d’autres artefacts d’entrée, les importations externes définissent les détails du processus de génération, y compris les importations standard telles que Microsoft.Common.targets et les importations configurables par l’utilisateur au niveau de la solution ou du projet.

Démarrage

MSBuild peut être appelé à partir de Visual Studio via le modèle objet MSBuild dans Microsoft.Build.dll, ou en appelant l’exécutable (MSBuild.exe ou dotnet build) directement sur la ligne de commande, ou dans un script, comme dans les systèmes CI. Dans les deux cas, les entrées qui affectent le processus de génération incluent le fichier projet (ou l’objet projet interne à Visual Studio), éventuellement un fichier solution, des variables d’environnement et des commutateurs de ligne de commande ou leurs équivalents de modèle objet. Pendant la phase de démarrage, les options de ligne de commande ou les équivalents de modèle objet sont utilisés pour configurer les paramètres MSBuild, tels que la configuration des enregistreurs d’événements. Les propriétés définies sur la ligne de commande à l’aide du commutateur -property ou -p sont définies en tant que propriétés globales, ce qui remplace toutes les valeurs qui seraient définies dans les fichiers projet, même si les fichiers projet sont lus ultérieurement.

Les sections suivantes concernent les fichiers d’entrée, tels que les fichiers solution ou les fichiers projet.

Solutions et projets

Les instances MSBuild peuvent être constituées d’un projet ou de nombreux projets dans le cadre d’une solution. Les fichiers de solution au format .slnx ou au format .sln sont tous les deux pris en charge (dans MSBuild 17.12 et versions ultérieures). Le fichier solution (.sln) n’est pas un fichier XML MSBuild, mais MSBuild l’interprète pour connaître tous les projets qui doivent être générés pour les paramètres de configuration et de plateforme donnés. Lorsque MSBuild traite cette entrée, elle est appelée build de solution. Il comporte quelques points extensibles qui vous permettent d’exécuter quelque chose à chaque build de solution, mais étant donné que cette build est une exécution distincte des builds de projet individuelles, aucun paramètre de propriétés ou définitions cibles de la build de solution n’est pertinent pour chaque build de projet.

Vous pouvez découvrir comment étendre la construction de la solution à Personnaliser la construction de la solution.

Comparaison des compilations Visual Studio et des compilations MSBuild.exe

Il existe des différences significatives entre le moment où les projets sont générés dans Visual Studio et lorsque vous appelez DIRECTEMENT MSBuild, via l’exécutable MSBuild ou lorsque vous utilisez le modèle objet MSBuild pour démarrer une build. Visual Studio gère l’ordre de génération du projet pour les builds Visual Studio ; il appelle uniquement MSBuild au niveau du projet individuel, et quand il le fait, quelques propriétés booléennes (BuildingInsideVisualStudio, BuildProjectReferences) sont définies qui affectent considérablement ce que FAIT MSBuild. À l’intérieur de chaque projet, l’exécution se produit de la même façon que lorsqu’elle est appelée via MSBuild, mais la différence se produit avec les projets référencés. Dans MSBuild, lorsque des projets référencés sont requis, une build se produit réellement ; autrement dit, il exécute des tâches et des outils, et génère la sortie. Lorsqu’une build Visual Studio recherche un projet référencé, MSBuild retourne uniquement les sorties attendues du projet référencé ; Elle permet à Visual Studio de contrôler la génération de ces autres projets. Visual Studio détermine l’ordre de génération et les appels dans MSBuild séparément (si nécessaire), tous complètement sous le contrôle de Visual Studio.

Une autre différence se produit lorsque MSBuild est appelé avec un fichier solution, MSBuild analyse le fichier solution, crée un fichier d’entrée XML standard, l’évalue et l’exécute en tant que projet. La build de solution est exécutée avant tout projet. Lors de la génération à partir de Visual Studio, aucun de ces événements ne se produit ; MSBuild ne voit jamais le fichier solution. Par conséquent, la personnalisation de la génération de solution (à l’aide de avant. SolutionName.sln.targets et après. SolutionName.sln.targets) s’applique uniquement aux builds MSBuild.exe, dotnet buildou pilotées par modèle objet, et non aux builds Visual Studio.

Kits SDK de projet

La fonctionnalité sdk pour les fichiers projet MSBuild est relativement nouvelle. Avant cette modification, les fichiers projet importaient explicitement les fichiers .targets et .props qui définissaient le processus de compilation pour un type de projet particulier.

Les projets .NET Core importent la version du Kit de développement logiciel (SDK) .NET appropriée pour eux. Consultez la vue d’ensemble, Kits de développement logiciel (SDK) de projet .NET Core et les informations de référence sur les propriétés.

Phase d’évaluation

Cette section explique comment ces fichiers d’entrée sont traités et analysés pour produire des objets en mémoire qui déterminent ce qui sera généré.

L’objectif de la phase d’évaluation est de créer les structures d’objet en mémoire en fonction des fichiers XML d’entrée et de l’environnement local. La phase d’évaluation se compose de six passes qui traitent les fichiers d’entrée tels que les fichiers XML du projet ou, ainsi que les fichiers XML importés, généralement nommés .props ou fichiers .targets, selon qu’ils définissent principalement des propriétés ou définissent des cibles de build. Chaque passe génère une partie des objets en mémoire qui sont utilisés ultérieurement dans la phase d’exécution pour générer les projets, mais aucune action de génération réelle ne se produit pendant la phase d’évaluation. Dans chaque passe, les éléments sont traités dans l’ordre dans lequel ils apparaissent.

Les passes de la phase d’évaluation sont les suivantes :

  • Évaluer les variables d’environnement
  • Évaluer les importations et les propriétés
  • Évaluer les définitions d’éléments
  • Évaluer les éléments
  • Évaluer les éléments UsingTask
  • Évaluer les cibles

Les importations et les propriétés sont évaluées dans la même passe, dans l’ordre de leur apparition, comme si les importations étaient développées sur place. Par conséquent, les paramètres de propriété dans les fichiers précédemment importés sont disponibles dans les fichiers importés ultérieurement.

L’ordre de ces passes a des implications significatives et est important de savoir quand vous personnalisez le fichier projet. Consultez Ordre d’évaluation des propriétés et des éléments.

Évaluer les variables d’environnement

Dans cette phase, les variables d’environnement sont utilisées pour définir des propriétés équivalentes. Par exemple, la variable d’environnement PATH est rendue disponible en tant que propriété $(PATH). Lors de l’exécution à partir de la ligne de commande ou d’un script, l’environnement de commande est utilisé normalement, et lorsqu’il est exécuté à partir de Visual Studio, l’environnement en vigueur lorsque Visual Studio lance est utilisé.

Évaluer les importations et les propriétés

Dans cette phase, l’ensemble du code XML d’entrée est lu, y compris les fichiers projet et la chaîne entière d’importations. MSBuild crée une structure XML en mémoire qui représente le code XML du projet et tous les fichiers importés. À ce stade, les propriétés qui ne sont pas dans les cibles sont évaluées et définies.

Par conséquent, MSBuild lisant tous les fichiers d’entrée XML au début de son processus, les modifications apportées à ces entrées pendant le processus de génération n’affectent pas la build actuelle.

Les propriétés en dehors de n’importe quelle cible sont gérées différemment des propriétés au sein des cibles. Dans cette phase, seules les propriétés définies en dehors de n’importe quelle cible sont évaluées.

Étant donné que les propriétés sont traitées dans l’ordre dans le passage des propriétés, une propriété à n’importe quel point de l’entrée peut accéder aux valeurs de propriété qui apparaissent précédemment dans l’entrée, mais pas aux propriétés qui apparaissent ultérieurement.

Étant donné que les propriétés sont traitées avant l’évaluation des éléments, vous ne pouvez pas accéder à la valeur d’un élément pendant une partie du passage des propriétés.

Évaluer les définitions d’éléments

Dans cette phase, les définitions des éléments sont interprétées et une représentation de ces définitions est créée en mémoire.

Évaluer les éléments

Les éléments définis à l’intérieur d’une cible sont gérés différemment des éléments en dehors de n’importe quelle cible. Dans cette phase, les éléments en dehors de n’importe quelle cible et leurs métadonnées associées sont traités. Les métadonnées définies par les définitions d’éléments sont remplacées par les métadonnées définies sur les éléments. Étant donné que les éléments sont traités dans l’ordre dans lequel ils apparaissent, vous pouvez référencer les éléments qui ont été définis précédemment, mais pas ceux qui apparaissent ultérieurement. Étant donné que la passe d’éléments a lieu après la passe des propriétés, les éléments peuvent accéder à n’importe quelle propriété si elles sont définies en dehors des cibles, que la définition de propriété apparaisse ultérieurement ou non.

Évaluer les éléments UsingTask

Dans cette phase, les éléments UsingTask sont analysés, et les tâches sont déclarées pour être utilisées ultérieurement pendant la phase d’exécution.

Évaluer les cibles

Dans cette phase, toutes les structures d’objets cibles sont créées en mémoire, en préparation de l’exécution. Aucune exécution réelle n’a lieu.

Phase d’exécution

Dans la phase d’exécution, les cibles sont ordonnées et exécutées, et toutes les tâches sont exécutées. Mais tout d’abord, les propriétés et les éléments définis dans les cibles sont évalués ensemble dans une phase unique dans l’ordre dans lequel ils apparaissent. L’ordre de traitement est notamment différent de la façon dont les propriétés et les éléments qui ne se trouvent pas dans une cible sont traités : toutes les propriétés d’abord, puis tous les éléments, dans des passes distinctes. Les modifications apportées aux propriétés et aux éléments d’une cible peuvent être observées après la modification de la cible.

Ordre de génération des cibles

Dans un seul projet, les cibles s’exécutent en série. Le problème central est de déterminer l’ordre dans lequel tout générer afin que les dépendances soient utilisées pour générer les cibles dans l’ordre approprié.

L’ordre de génération cible est déterminé par l’utilisation des attributs BeforeTargets, DependsOnTargets et AfterTargets sur chaque cible. L’ordre des cibles ultérieures peut être influencé pendant l’exécution d’une cible antérieure si la cible précédente modifie une propriété référencée dans ces attributs.

Les règles de classement sont décrites dans Déterminer l’ordre de génération cible. Le processus est déterminé par une structure de pile contenant des cibles à générer. La cible située en haut de cette tâche démarre l’exécution et, si elle dépend d’autre chose, ces cibles sont envoyées au-dessus de la pile et commencent à s’exécuter. Si une cible n’a aucune dépendance, elle s’exécute jusqu’à la fin et sa cible parente reprend.

Références de projet

Il existe deux chemins de code que MSBuild peut prendre, celui normal décrit ici et l’option de graphique décrite dans la section suivante.

Les projets individuels spécifient leur dépendance vis-à-vis d’autres projets par le biais de ProjectReference éléments. Lorsqu’un projet situé en haut de la pile commence à générer, il atteint le point où la cible ResolveProjectReferences s’exécute, une cible standard définie dans les fichiers cibles courants.

ResolveProjectReferences invoque la tâche MSBuild en utilisant les éléments ProjectReference comme entrées pour obtenir les sorties. Les éléments ProjectReference sont transformés en éléments locaux tels que Reference. La phase d’exécution MSBuild du projet actuel s’interrompt pendant que la phase d’exécution commence à traiter le projet référencé (la phase d’évaluation est effectuée en premier si nécessaire). Le projet référencé génère uniquement une fois que vous avez commencé à générer le projet dépendant, ce qui crée une arborescence de projets.

Visual Studio permet de créer des dépendances de projet dans des fichiers de solution (.sln). Les dépendances sont spécifiées dans le fichier solution et sont respectées uniquement lors de la génération d’une solution ou lors de la génération à l’intérieur de Visual Studio. Si vous générez un projet unique, ce type de dépendance est ignoré. Les références de solution sont transformées par MSBuild en éléments ProjectReference et par la suite sont traitées de la même manière.

Option Graph

Si vous spécifiez le commutateur de génération de graphique (-graphBuild ou -graph), le ProjectReference devient un concept de première classe utilisé par MSBuild. MSBuild analyse tous les projets et construit le graphique de l’ordre de génération, un graphique de dépendance réel des projets, qui est ensuite parcouru pour déterminer l’ordre de génération. Comme pour les cibles de projets individuels, MSBuild garantit que les projets référencés sont construits après les projets dont ils dépendent.

Exécution parallèle

Si vous utilisez la prise en charge du multiprocesseur (-maxCpuCount ou -m commutateur), MSBuild crée des nœuds, qui sont des processus MSBuild qui utilisent les cœurs de processeur disponibles. Chaque projet est soumis à un nœud disponible. Au sein d’un nœud, les générations de projets individuelles s’exécutent en série.

Les tâches peuvent être activées pour l’exécution parallèle en définissant une variable booléenne BuildInParallel, qui est définie en fonction de la valeur de la propriété $(BuildInParallel) dans MSBuild. Pour les tâches activées pour l’exécution parallèle, un planificateur de travail gère les nœuds et affecte du travail aux nœuds.

Consultez Génération de plusieurs projets en parallèle avec MSBuild

Importations standard

Microsoft.Common.props et Microsoft.Common.targets sont importés par les fichiers projet .NET (explicitement ou implicitement dans les projets de style SDK) et se trouvent dans le dossier MSBuild\Current\bin dans une installation de Visual Studio. Les projets C++ ont leur propre hiérarchie d’importations ; consultez msBuild Internals for C++ projects.

Le fichier Microsoft.Common.props définit les paramètres par défaut que vous pouvez remplacer. Il est importé (explicitement ou implicitement) au début d’un fichier projet. Ainsi, les paramètres de votre projet apparaissent après les valeurs par défaut, de sorte qu’ils les remplacent.

Le fichier Microsoft.Common.targets et les fichiers cibles qu’il importe définissent le processus de génération standard pour les projets .NET. Il fournit également des points d’extension que vous pouvez utiliser pour personnaliser la build.

En implémentation, Microsoft.Common.targets est un wrapper mince qui importe Microsoft.Common.CurrentVersion.targets. Ce fichier contient les paramètres des propriétés standard et définit les cibles réelles qui définissent le processus de génération. La cible Build est définie ici, mais elle est en fait vide. Toutefois, la cible Build contient l’attribut DependsOnTargets qui spécifie les cibles individuelles qui composent les étapes de génération réelles, qui sont BeforeBuild, CoreBuildet AfterBuild. La cible Build est définie comme suit :

  <PropertyGroup>
    <BuildDependsOn>
      BeforeBuild;
      CoreBuild;
      AfterBuild
    </BuildDependsOn>
  </PropertyGroup>

  <Target
      Name="Build"
      Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
      DependsOnTargets="$(BuildDependsOn)"
      Returns="@(TargetPathWithTargetPlatformMoniker)" />

BeforeBuild et AfterBuild sont des points d’extension. Ils sont vides dans le fichier Microsoft.Common.CurrentVersion.targets, mais les projets peuvent fournir leurs propres BeforeBuild et AfterBuild cibles avec des tâches qui doivent être effectuées avant ou après le processus de génération principal. AfterBuild est exécuté avant la cible no-op, Build, car AfterBuild apparaît dans l’attribut DependsOnTargets sur la cible Build, mais il se produit après CoreBuild.

La cible CoreBuild contient les appels aux outils de génération, comme suit :

  <PropertyGroup>
    <CoreBuildDependsOn>
      BuildOnlySettings;
      PrepareForBuild;
      PreBuildEvent;
      ResolveReferences;
      PrepareResources;
      ResolveKeySource;
      Compile;
      ExportWindowsMDFile;
      UnmanagedUnregistration;
      GenerateSerializationAssemblies;
      CreateSatelliteAssemblies;
      GenerateManifests;
      GetTargetPath;
      PrepareForRun;
      UnmanagedRegistration;
      IncrementalClean;
      PostBuildEvent
    </CoreBuildDependsOn>
  </PropertyGroup>
  <Target
      Name="CoreBuild"
      DependsOnTargets="$(CoreBuildDependsOn)">

    <OnError ExecuteTargets="_TimeStampAfterCompile;PostBuildEvent" Condition="'$(RunPostBuildEvent)'=='Always' or '$(RunPostBuildEvent)'=='OnOutputUpdated'"/>
    <OnError ExecuteTargets="_CleanRecordFileWrites"/>

  </Target>

Le tableau suivant décrit ces cibles ; certaines cibles s’appliquent uniquement à certains types de projets.

Cible Description
BuildOnlySettings Paramètres uniquement pour les builds réelles, et non lorsque MSBuild est invoqué lors du chargement du projet par Visual Studio.
PrepareForBuild Préparez les prérequis pour la construction
Événement de pré-construction (PreBuildEvent) Point d’extension pour les projets afin de définir les tâches à exécuter avant la compilation.
ResolveProjectReferences Analyser les dépendances de projet et générer des projets référencés
ResolveAssemblyReferences Localisez les assemblies référencés.
ResolveReferences Se compose de ResolveProjectReferences et de ResolveAssemblyReferences pour rechercher toutes les dépendances
Préparer les ressources Traiter les fichiers de ressources
ResolveKeySource Résolvez la clé de nom fort utilisée pour signer l’assembly et le certificat utilisé pour signer les manifestes ClickOnce.
Compile Appelle le compilateur
ExportWindowsMDFile Générez un fichier WinMD à partir des fichiers WinMDModule générés par le compilateur.
UnmanagedUnregistration Supprimer les entrées de registre COM Interop d'une version précédente.
GenerateSerializationAssemblies Générez un assembly de sérialisation XML à l’aide de sgen.exe.
CreateSatelliteAssemblies Créer un assembly satellite pour chaque culture unique dans les ressources.
Générer des manifestes Génère des manifestes de déploiement et d’application ClickOnce ou un manifeste natif.
GetTargetPath Retourne un élément contenant le produit de build (exécutable ou assembly) pour ce projet, avec des métadonnées.
PrepareForRun Copiez les sorties de build dans le répertoire final s’ils ont changé.
UnmanagedRegistration Définir des entrées de Registre pour COM Interop
IncrementalClean Supprimez les fichiers générés dans une build précédente, mais qui n’ont pas été produits dans la build actuelle. Cela est nécessaire pour que Clean fonctionne dans les builds incrémentielles.
PostBuildEvent Point d’extension pour les projets pour définir des tâches à exécuter après la compilation

La plupart des cibles du tableau précédent sont trouvées dans les importations spécifiques à la langue, telles que Microsoft.CSharp.targets. Ce fichier définit les étapes du processus de génération standard spécifique aux projets .NET C#. Par exemple, il contient la cible Compile qui appelle réellement le compilateur C#.

Importations configurables par l’utilisateur

Outre les importations standard, vous pouvez ajouter plusieurs importations pour personnaliser le processus de génération.

  • Directory.Build.props
  • Directory.Build.targets

Ces fichiers sont lus par les importations standard pour tous les projets dans n’importe quel sous-dossier auquel ils sont rattachés. Généralement, les paramètres se situent au niveau de la solution pour contrôler tous les projets de celle-ci, mais peuvent également être situés plus haut dans le système de fichiers, jusqu'à la racine du disque.

Le fichier Directory.Build.props est importé par Microsoft.Common.props, de sorte que les propriétés définies dans ce fichier sont disponibles dans le fichier projet. Ils peuvent être redéfinis dans le fichier projet pour personnaliser les valeurs par projet. Le fichier Directory.Build.targets est lu après le fichier projet. Il contient généralement des cibles, mais ici, vous pouvez également définir des propriétés que vous ne souhaitez pas que les projets individuels redéfinissent.

Personnalisations dans un fichier projet

Visual Studio met à jour vos fichiers projet lorsque vous apportez des modifications dans Explorateur de solutions, la fenêtre propriétés ou dans propriétés du projet, mais vous pouvez également apporter vos propres modifications en modifiant directement le fichier projet.

De nombreux comportements de génération peuvent être configurés en définissant des propriétés MSBuild, soit dans le fichier projet pour les paramètres locaux d’un projet, soit comme indiqué dans la section précédente, en créant un fichier Directory.Build.props pour définir des propriétés globalement pour les dossiers entiers des projets et solutions. Pour les builds ad hoc sur la ligne de commande ou les scripts, vous pouvez également utiliser l’option /p sur la ligne de commande pour définir des propriétés pour un appel particulier de MSBuild. Consultez propriétés de projet MSBuild courantes pour plus d’informations sur les propriétés que vous pouvez définir.