Étendre le processus de génération Visual Studio

Le processus de génération Visual Studio est défini par une série de fichiers .targets MSBuild importés dans votre fichier projet. Ces importations sont implicites, si vous utilisez un Kit de développement logiciel (SDK) comme c'est généralement le cas pour les projets Visual Studio.. Parmi ces fichiers importés, Microsoft.Common.targets peut être étendu de manière à exécuter des tâches personnalisées à différentes étapes du processus de génération. Cet article décrit deux méthodes permettant d’étendre le processus de génération de la version de Visual Studio :

  • Créez une cible personnalisée et spécifiez quand elle doit s’exécuter à l’aide des attributs BeforeTargets et AfterTargets.

  • Remplacez les propriétés DependsOn définies dans les cibles courantes.

  • Remplacement de cibles prédéfinies spécifiques définies dans les cibles courantes (Microsoft.Common.targets ou les fichiers importés).

AfterTargets et BeforeTargets

Vous pouvez utiliser les attributs AfterTargets et BeforeTargets sur votre cible personnalisée pour spécifier quand elle doit s’exécuter.

L’exemple suivant montre comment utiliser l’attribut AfterTargets pour ajouter une cible personnalisée qui effectue une action avec les fichiers de sortie. Dans ce cas, les fichiers de sortie sont copiés dans un nouveau dossier CustomOutput. L’exemple montre également comment nettoyer les fichiers créés par l’opération de build personnalisée avec une cible CustomClean à l’aide d’un attribut BeforeTargets et en spécifiant que l’opération de nettoyage personnalisée s’exécute avant la cible CoreClean.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
     <TargetFramework>netcoreapp3.1</TargetFramework>
     <_OutputCopyLocation>$(OutputPath)..\..\CustomOutput\</_OutputCopyLocation>
  </PropertyGroup>

  <Target Name="CustomAfterBuild" AfterTargets="Build">
    <ItemGroup>
      <_FilesToCopy Include="$(OutputPath)**\*"/>
    </ItemGroup>
    <Message Text="_FilesToCopy: @(_FilesToCopy)" Importance="high"/>

    <Message Text="DestFiles:
        @(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>

    <Copy SourceFiles="@(_FilesToCopy)"
          DestinationFiles=
          "@(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
  </Target>

  <Target Name="CustomClean" BeforeTargets="CoreClean">
    <Message Text="Inside Custom Clean" Importance="high"/>
    <ItemGroup>
      <_CustomFilesToDelete Include="$(_OutputCopyLocation)**\*"/>
    </ItemGroup>
    <Delete Files='@(_CustomFilesToDelete)'/>
  </Target>
</Project>

Avertissement

Veillez à utiliser des noms différents de ceux des cibles prédéfinies (nous avons par exemple nommé la cible de version personnalisée CustomAfterBuild, et non AfterBuild), car ces cibles prédéfinies sont remplacées lors de l’importation du kit SDK qui les définit également. Reportez-vous au tableau à la fin de cet article pour obtenir la liste des cibles prédéfinies.

Étendre les propriétés DependsOn

Une autre façon d’étendre le processus de génération consiste à utiliser les propriétés DependsOn (par exemple, BuildDependsOn) pour spécifier des cibles qui doivent être exécutées avant une cible standard.

Cette méthode est préférable à la substitution de cibles prédéfinies, décrites dans la section suivante. Remplacer les cibles prédéfinies est une ancienne méthode qui est toujours supportée, mais, parce que MSBuild évalue la définition des cibles de manière séquentielle, il n'y a aucun moyen d'empêcher un autre projet qui importe votre projet de remplacer les cibles que vous avez déjà remplacées. Ainsi, par exemple, la dernière cible AfterBuild définie dans le fichier projet, une fois que tous les autres projets ont été importés, sera celle utilisée pour la génération.

Vous pouvez empêcher la substitution involontaire des cibles en écrasant les propriétés DependsOn qui sont utilisées dans les attributs DependsOnTargets des cibles courantes. Par exemple, la cible Build contient une valeur d’attribut DependsOnTargets égale à "$(BuildDependsOn)". Considérez les aspects suivants :

<Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>

Ce code XML indique que pour exécuter la cible Build, vous devez d’abord exécuter toutes les cibles spécifiées dans la propriété BuildDependsOn. La propriété BuildDependsOn est définie de la manière suivante :

<PropertyGroup>
    <BuildDependsOn>
        $(BuildDependsOn);
        BeforeBuild;
        CoreBuild;
        AfterBuild
    </BuildDependsOn>
</PropertyGroup>

Vous pouvez remplacer cette valeur de propriété en déclarant une autre propriété nommée BuildDependsOn à la fin de votre fichier projet. Dans un projet de style SDK, cela signifie que vous devez utiliser des importations explicites. Consultez les importations implicites et explicites pour pouvoir placer la propriété DependsOn après la dernière importation. En incluant la propriété BuildDependsOn précédente dans la nouvelle propriété, vous pouvez ajouter de nouvelles cibles au début et à la fin de la liste de cibles. Par exemple :

<PropertyGroup>
    <BuildDependsOn>
        MyCustomTarget1;
        $(BuildDependsOn);
        MyCustomTarget2
    </BuildDependsOn>
</PropertyGroup>

<Target Name="MyCustomTarget1">
    <Message Text="Running MyCustomTarget1..."/>
</Target>
<Target Name="MyCustomTarget2">
    <Message Text="Running MyCustomTarget2..."/>
</Target>

Les projets qui importent votre fichier de projet peuvent étendre ces propriétés sans remplacer les personnalisations que vous avez effectuées.

Pour substituer une propriété DependsOn

  1. Identifiez une propriété DependsOn prédéfinie dans les cibles communes que vous souhaitez remplacer. Voir le tableau suivant pour une liste des propriétés DependsOn couramment remplacées.

  2. Définissez une autre instance de la ou des propriétés à la fin de votre fichier projet. Incluez la propriété d’origine (par exemple $(BuildDependsOn)) dans la nouvelle propriété.

  3. Définissez vos cibles personnalisées avant ou après la définition de la propriété.

  4. Générez le fichier projet.

Propriétés DependsOn communément substituées

Nom de la propriété Les cibles ajoutées s’exécutent avant ce point :
BuildDependsOn Le point d'entrée principal de la version. Propriété à remplacer si vous souhaitez insérer des cibles personnalisées avant ou après l’intégralité du processus de génération de la version.
RebuildDependsOn Rebuild.
RunDependsOn Exécution de la sortie de version finale (s’il s’agit d’un .EXE)
CompileDependsOn La compilation (Compile cible). Propriété à remplacer si vous souhaitez insérer des processus personnalisés avant ou après l’étape de compilation.
CreateSatelliteAssembliesDependsOn Création des assemblys satellites
CleanDependsOn La cible Clean (Suppression de toutes les sorties de build intermédiaires et finales). Propriété à remplacer si vous souhaitez nettoyer la sortie de votre processus de génération de la version.
PostBuildEventDependsOn La cible PostBuildEvent
PublishBuildDependsOn Publication de version
ResolveAssemblyReferencesDependsOn La cible ResolveAssemblyReferences (recherche de la fermeture transitive des dépendances pour une dépendance donnée). Consultez l’article ResolveAssemblyReference.

Exemple : BuildDependsOn et CleanDependsOn

L’exemple suivant est similaire à l’exemple BeforeTargets et AfterTargets, mais il montre comment obtenir des fonctionnalités similaires. Il étend la build en utilisant BuildDependsOn pour ajouter votre propre tâche CustomAfterBuild qui copie les fichiers de sortie après la build, et ajoute également la tâche CustomClean correspondante à l’aide de CleanDependsOn.

Dans cet exemple, il s’agit d’un projet de type SDK. Comme indiqué précédemment dans la remarque sur les projets de type SDK dans cet article, vous devez utiliser la méthode d’importation manuelle au lieu de l’attribut Sdk utilisé par Visual Studio quand il génère des fichiers projet.

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"/>

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk"/>

  <PropertyGroup>
    <BuildDependsOn>
      $(BuildDependsOn);CustomAfterBuild
    </BuildDependsOn>

    <CleanDependsOn>
      $(CleanDependsOn);CustomClean
    </CleanDependsOn>

    <_OutputCopyLocation>$(OutputPath)..\..\CustomOutput\</_OutputCopyLocation>
  </PropertyGroup>

  <Target Name="CustomAfterBuild">
    <ItemGroup>
      <_FilesToCopy Include="$(OutputPath)**\*"/>
    </ItemGroup>
    <Message Importance="high" Text="_FilesToCopy: @(_FilesToCopy)"/>

    <Message Text="DestFiles:
      @(_FilesToCopy-&gt;'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>

    <Copy SourceFiles="@(_FilesToCopy)"
          DestinationFiles="@(_FilesToCopy-&gt;'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
  </Target>

  <Target Name="CustomClean">
    <Message Importance="high" Text="Inside Custom Clean"/>
    <ItemGroup>
      <_CustomFilesToDelete Include="$(_OutputCopyLocation)**\*"/>
    </ItemGroup>
    <Delete Files="@(_CustomFilesToDelete)"/>
  </Target>
</Project>

L’ordre des éléments est important. Les éléments BuildDependsOn et CleanDependsOn doivent s’afficher après l’importation du fichier cible de kit SDK standard.

Substituer des cibles prédéfinies

Les fichiers .targets courants contiennent un ensemble de cibles vides prédéfinies qui sont appelées avant et après certaines cibles majeures du processus de génération de la version. Par exemple, MSBuild appelle la cible BeforeBuild avant la cible principale CoreBuild et appelle la cible AfterBuild après la cible CoreBuild. Par défaut, les cibles vides dans les cibles communes ne font rien, mais vous pouvez modifier leur comportement par défaut en définissant les cibles que vous souhaitez dans un fichier de projet. Les méthodes décrites plus haut dans cet article sont préférées, mais vous pouvez rencontrer un code plus ancien qui utilise cette méthode.

Si votre projet utilise un SDK (par exemple Microsoft.Net.Sdk), vous devez passer des importations implicites aux importations explicites, comme indiqué dans les importations explicites et implicites.

Pour substituer une cible prédéfinie

  1. Si le projet utilise l’attribut Sdk , remplacez-le par la syntaxe d’importation explicite. Voir Importations explicites et implicites.

  2. Identifiez une cible prédéfinie dans les cibles courantes que vous souhaitez écraser. Voir le tableau suivant pour la liste complète des cibles que vous pouvez remplacer en toute sécurité.

  3. Définissez la ou les cibles à la fin de votre fichier de projet, immédiatement avant la balise </Project> et après l'importation explicite du SDK. Par exemple :

    <Project>
        <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
        ...
        <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
        <Target Name="BeforeBuild">
            <!-- Insert tasks to run before build here -->
        </Target>
        <Target Name="AfterBuild">
            <!-- Insert tasks to run after build here -->
        </Target>
    </Project>
    

    Notez que l’attribut Sdk sur l’élément de niveau Project supérieur a été supprimé.

  4. Générez le fichier projet.

Tableau des cibles prédéfinies

Le tableau suivant montre l’ensemble des cibles dans les cibles courantes que vous pouvez remplacer en toute sécurité.

Nom de la cible Description
BeforeCompile, AfterCompile Les tâches insérées dans l’une de ces cibles sont exécutées avant ou après la compilation principale. La plupart des personnalisations sont effectuées dans l’une de ces deux cibles.
BeforeBuild, AfterBuild Les tâches insérées dans l’une de ces cibles s’exécutent avant ou après tout le reste lors de la génération. Remarque : les cibles BeforeBuild et AfterBuild sont déjà définies dans les commentaires à la fin de la plupart des fichiers projet. Vous pouvez ainsi ajouter facilement des événements pré-build et post-build à votre fichier projet.
BeforeRebuild, AfterRebuild Les tâches insérées dans l’une de ces cibles sont exécutées avant ou après l’appel de la fonctionnalité de regénération principale. L’ordre d’exécution des cibles dans Microsoft.Common.targets est le suivant : BeforeRebuild, Clean, Build, puis AfterRebuild.
BeforeClean, AfterClean Les tâches insérées dans l’une de ces cibles sont exécutées avant ou après l’appel de la fonctionnalité de nettoyage principale.
BeforePublish, AfterPublish Les tâches insérées dans l’une de ces cibles sont exécutées avant ou après l’appel de la fonctionnalité de publication principale.
BeforeResolveReferences, AfterResolveReferences Les tâches insérées dans l’une de ces cibles sont exécutées avant ou après la résolution des références d’assembly.
BeforeResGen, AfterResGen Les tâches insérées dans l’une de ces cibles sont exécutées avant ou après la génération des ressources.

Il existe de nombreuses cibles supplémentaires dans le système de génération et le Kit de développement logiciel (SDK) .NET, consultez les cibles MSBuild - SDK et cibles de version par défaut.

Meilleures pratiques pour les cibles personnalisées

Les propriétés DependsOnTargets et BeforeTargets peuvent spécifier qu’une cible doit s’exécuter avant une autre cible, mais elles sont toutes les deux nécessaires dans différents scénarios. Elles diffèrent selon lesquelles la spécification de dépendance est spécifiée. Vous n’avez que le contrôle de vos propres cibles et ne pouvez pas modifier en toute sécurité les cibles système ou d’autres cibles importées, ce qui limite votre choix de méthodes.

Lors de la création d’une cible personnalisée, suivez ces instructions générales pour vous assurer que votre cible est exécutée dans l’ordre prévu.

  1. Utilisez l’attribut DependsOnTargets pour spécifier des cibles que vous devez effectuer avant l’exécution de votre cible. Pour une chaîne de cibles que vous contrôlez, chaque cible peut spécifier le membre précédent de la chaîne dans DependsOnTargets.

  2. Utilisez BeforeTargets pour toute cible que vous ne contrôlez pas que vous devez exécuter avant (comme BeforeTargets="PrepareForBuild" pour une cible qui doit s’exécuter tôt dans la version).

  3. Utilisez AfterTargets pour toute cible que vous ne contrôlez pas qui garantit que les sorties dont vous avez besoin sont disponibles. Par exemple, spécifiez AfterTargets="ResolveReferences" pour un élément qui modifiera une liste de références.

  4. Vous pouvez les utiliser en combinaison. Par exemple : DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile".

Importations explicites et implicites

Les projets générés par Visual Studio utilisent généralement l’attribut Sdk sur l’élément de projet. Ces projets sont appelés projets de style SDK. Voir Utiliser les kits SDK de projet MSBuild. Voici un exemple :

<Project Sdk="Microsoft.Net.Sdk">

Lorsque votre projet utilise l’attribut Sdk , deux importations sont implicitement ajoutées, une au début de votre fichier projet et une à la fin.

Les importations implicites sont équivalentes à avoir une instruction d’importation comme celle-ci en première ligne du fichier de projet, après l'élément Project :

<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

et l’instruction import suivante comme dernière ligne dans le fichier projet :

<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />

Cette syntaxe est appelée importation explicite du Kit de développement logiciel (SDK). Lorsque vous utilisez cette syntaxe explicite, vous devez omettre l’attribut Sdk sur l’élément de projet.

L’importation implicite du Kit de développement logiciel (SDK) équivaut à importer les fichiers spécifiques « communs » .props ou .targets qui constituent une construction classique dans des fichiers projet plus anciens, tels que :

<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />

et

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

Toutes ces anciennes références doivent être remplacées par la syntaxe explicite du Kit de développement logiciel (SDK) indiquée précédemment dans cette section.

L’utilisation de la syntaxe explicite du Kit de développement logiciel (SDK) signifie que vous pouvez ajouter votre propre code avant la première importation ou après l’importation finale du SDK. Cela signifie que vous pouvez modifier le comportement en définissant des propriétés avant la première importation qui prendra effet dans le fichier importé .props , et vous pouvez remplacer une cible définie dans l’un des fichiers SDK .targets après l’importation finale. À l’aide de cette méthode, vous pouvez remplacer BeforeBuild ou AfterBuild comme indiqué ci-dessous.

Étapes suivantes

Vous pouvez faire beaucoup plus avec MSBuild pour personnaliser la version. Consultez Personnaliser votre build.