Personnaliser la compilation pour chaque dossier
Vous pouvez ajouter certains fichiers à importer par MSBuild pour remplacer les paramètres de propriété par défaut et ajouter des cibles personnalisées. L’étendue de ces personnalisations peut être contrôlée au niveau du dossier par où ces fichiers sont placés.
Cet article traite des personnalisations applicables aux scénarios suivants :
- Personnaliser les paramètres de génération pour de nombreux projets dans une solution
- Personnaliser les paramètres de génération pour de nombreuses solutions sous un répertoire de fichiers commun
- Personnaliser les paramètres de build qui peuvent être différents pour les sous-dossiers dans une structure complexe de dossiers
- Remplacer les paramètres par défaut, les dossiers de build par défaut et d’autres comportements définis par un KIT de développement logiciel (SDK) tel que
Microsoft.Net.Sdk
- Ajouter ou personnaliser des cibles de build qui s’appliquent à un nombre quelconque de projets ou de solutions
Si vous utilisez des projets C++, vous pouvez également utiliser les méthodes décrites dans Personnaliser les builds C++.
Directory.Build.props et Directory.Build.targets
Vous pouvez ajouter une nouvelle propriété à chaque projet en le définissant dans un fichier unique appelé Directory.Build.props dans le dossier racine qui contient votre source.
Lorsque MSBuild s’exécute, Microsoft.Common.props recherche votre structure de répertoires dans le fichier Directory.Build.props. S’il en trouve un, il importe le fichier et lit les propriétés définies dans celui-ci. Directory.Build.props est un fichier défini par l’utilisateur qui fournit des personnalisations aux projets sous un répertoire.
De même, Microsoft.Common.targets recherche Directory.Build.targets.
Directory.Build.props est importé tôt dans la séquence de fichiers importés, ce qui peut être important si vous devez définir une propriété utilisée par les importations, en particulier celles qui sont importées implicitement à l’aide de l’attribut Sdk
, par exemple lors de l’utilisation du Kit de développement logiciel (SDK) .NET dans la plupart des fichiers projet .NET.
Remarque
Les systèmes de fichiers Linux respectent la casse. Vérifiez que la casse du nom de fichier Directory.Build.props correspond exactement, sans quoi il ne sera pas détecté pendant le processus de build.
Pour plus d’informations, consultez ce problème GitHub.
Exemple avec Directory.Build.props
Par exemple, voici un fichier Directory.Build.props qui définit le répertoire de sortie de tous les projets d’une solution Visual Studio. La sortie de chaque projet est placée sous son propre nom de projet. Dans cet exemple, le fichier Directory.Build.props se trouve dans un dossier de solution, avec de nombreux projets dans les sous-dossiers. La propriété $(MSBuildProjectName)
donne le nom de chaque projet. Étant donné que le fichier Directory.Build.props est importé dans chaque projet pendant sa propre build, il est évalué à la valeur appropriée pour chaque projet individuel de la solution.
Nettoyez la solution pour supprimer les anciens fichiers de sortie.
msbuild /t:Clean SolutionName.sln
Créez un fichier à la racine de votre dépôt appelé Directory.Build.props.
Ajoutez le code XML suivant au fichier.
<Project> <PropertyGroup> <OutDir>C:\output\$(MSBuildProjectName)</OutDir> </PropertyGroup> </Project>
Remarque
La propriété
$(OutDir)
est un chemin absolu de la sortie et l’utilisation de celle-ci contourne la création de sous-dossiers pour la configuration, l’infrastructure cible ou le runtime qui sont normalement utilisés dans les projets .NET. Essayez d’utiliser la propriétéBaseOutputPath
à la place si vous souhaitez que les sous-dossiers habituels soient créés sous un chemin de sortie personnalisé.Exécutez MSBuild. Les importations existantes de votre projet de Microsoft.Common.props et Microsoft.Common.targets recherchent le fichier Directory.Build.props et l'importent, et le nouveau dossier de sortie est utilisé pour tous les projets de ce dossier.
Étendue de recherche
Lors de la recherche d’un fichier Directory.Build.props, MSBuild guide la structure du répertoire vers le haut à partir de l’emplacement de votre projet $(MSBuildProjectFullPath)
, s’arrêtant après avoir localisé un fichier Directory.Build.props. Par exemple, si votre $(MSBuildProjectFullPath)
était c :\users\username\code\test\case1, MSBuild commence à effectuer une recherche là-bas, puis recherche la structure de répertoire vers le haut jusqu’à ce qu’elle trouve un fichier Directory.Build.props, comme dans la structure de répertoire suivante.
c:\users\username\code\test\case1
c:\users\username\code\test
c:\users\username\code
c:\users\username
c:\users
c:\
L’emplacement du fichier solution n’est pas pertinent pour Directory.Build.props.
Ordre d’importation
Directory.Build.props est importé tôt dans Microsoft.Common.props, et les propriétés définies ultérieurement ne sont pas disponibles. Évitez donc de faire référence à des propriétés qui ne sont pas encore définies (et qui seront évaluées à vides).
Les propriétés définies dans Directory.Build.props peuvent être remplacées ailleurs dans le fichier projet ou dans les fichiers importés. Vous devez donc considérer les paramètres dans Directory.Build.props comme spécifiant les valeurs par défaut de vos projets.
Directory.Build.targets est importé à partir de Microsoft.Common.targets après l’importation de fichiers .targets
à partir de packages NuGet. Par conséquent, il peut remplacer les propriétés et les cibles définies dans la plupart de la logique de génération, ou définir des propriétés pour tous vos projets, indépendamment de ce que définissent les projets individuels.
Lorsque vous devez définir une propriété ou définir une cible pour un projet individuel qui remplace tous les paramètres précédents, placez cette logique dans le fichier projet après l’importation finale. Pour ce faire dans un projet de style SDK, vous devez d’abord remplacer l’attribut de style SDK par les importations équivalentes. Consultez Guide pratique pour utiliser des kits SDK de projet MSBuild.
Remarque
Le moteur MSBuild lit tous les fichiers importés lors de l'évaluation, avant de commencer l'exécution de build pour le projet (y compris tout PreBuildEvent
), il n’est pas prévu que ces fichiers soient modifiés par le PreBuildEvent
ou par toute autre partie du processus de build. Toutes les modifications ne prennent effet qu’à l’appel suivant de MSBuild.exe ou de la prochaine build Visual Studio. En outre, si votre processus de génération contient de nombreuses builds de projet (comme avec le multitargeting ou la génération de projets dépendants), les fichiers importés, y compris Directory.build.props, sont lus lorsque l’évaluation se produit pour chaque build de projet individuelle.
Cas d’usage : fusion à plusieurs niveaux
Supposons que vous disposez de cette structure de solution standard :
\
MySolution.sln
Directory.Build.props (1)
\src
Directory.Build.props (2-src)
\Project1
\Project2
\test
Directory.Build.props (2-test)
\Project1Tests
\Project2Tests
Il peut être souhaitable d’avoir des propriétés communes pour tous les projets (1), des propriétés communes pour les projets src(2-src) et des propriétés communes pour les projets test(2-test).
Pour que MSBuild fusionne correctement les fichiers « internes » (2-src et de test 2) avec le fichier « externe » (1), vous devez prendre en compte qu’une fois que MSBuild trouve un fichier Directory.Build.props, il cesse d’analyser davantage. Pour continuer l’analyse et la fusion dans le fichier externe, placez ce code dans les deux fichiers internes :
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
Voici un résumé de l’approche générale de MSBuild :
- Pour un projet donné, MSBuild recherche la première Directory.Build.props vers le haut dans la structure de la solution, la fusionne avec les valeurs par défaut et arrête l’analyse pour plus d’informations.
- Si vous souhaitez que plusieurs niveaux soient trouvés et fusionnés, alors
<Import...>
(comme indiqué précédemment) montre le fichier « externe » par rapport au fichier « interne ». - Si le fichier « externe » n’importe pas lui-même également quelque chose au-dessus, l’analyse s’arrête là.
La première fois qu'un Directory.Build.props n'importe rien, c'est là que MSBuild s'arrête.
Pour contrôler plus explicitement le processus d’importation, utilisez les propriétés $(DirectoryBuildPropsPath)
, $(ImportDirectoryBuildProps)
, $(DirectoryBuildTargetsPath)
et $(ImportDirectoryBuildTargets)
. La propriété $(DirectoryBuildPropsPath)
spécifie le chemin d’accès au fichier Directory.Build.props
à utiliser ; de même, $(DirectoryBuildTargetsPath)
spécifie le chemin d’accès au fichier Directory.Build.targets
.
Les propriétés booléennes $(ImportDirectoryBuildProps)
et $(ImportDirectoryBuildTargets)
sont définies sur true
par défaut. MSBuild recherche normalement ces fichiers, mais vous pouvez les définir sur false
pour empêcher l’importation de MSBuild.
Exemple
Cet exemple montre l’utilisation de la sortie prétraite pour déterminer où définir une propriété.
Pour vous aider à analyser l’utilisation d’une propriété particulière que vous souhaitez définir, vous pouvez exécuter MSBuild avec l’argument /preprocess
ou /pp
. Le texte de sortie est le résultat de toutes les importations, y compris les importations système comme Microsoft.Common.props qui sont implicitement importées et l’une de vos propres importations. Avec ce résultat, vous pouvez voir où votre propriété doit être positionnée par rapport à l'endroit où sa valeur est utilisée.
Par exemple, supposons que vous disposez d’un projet d’application console .NET Core ou .NET 5 ou ultérieur simple et que vous souhaitez personnaliser le dossier de sortie intermédiaire, normalement obj
. La propriété qui spécifie ce chemin est BaseIntermediateOutput
. Si vous essayez de le placer dans un élément PropertyGroup
dans votre fichier projet, ainsi que les différentes autres propriétés déjà définies ici, telles que TargetFramework
, vous découvrirez quand vous générez le projet que la propriété ne prend pas effet. Si vous exécutez MSBuild avec l’option /pp
et recherchez la sortie de BaseIntermediateOutputPath
, vous pouvez voir pourquoi. Dans ce cas, BaseIntermediateOutput
est lu et utilisé dans Microsoft.Common.props
.
Il existe un commentaire dans Microsoft.Common.props qui indique que la propriété BaseIntermediateOutput
doit être définie ici, avant qu’elle soit utilisée par une autre propriété, MSBuildProjectExtensionsPath
. Vous pouvez également voir que lorsque BaseIntermediateOutputPath
est initialement défini, il existe une vérification d’une valeur préexistante et, si elle n’est pas définie, elle est définie sur obj
.
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
Ainsi, ce placement vous indique que pour définir cette propriété, vous devez la spécifier « plus tôt »que cela. Juste avant ce code dans la sortie prétraite, vous pouvez voir que Directory.Build.props
est importé, de sorte que vous pouvez définir BaseIntermediateOutputPath
là-bas et il sera défini suffisamment tôt pour avoir l’effet souhaité.
La sortie prétraitée abrégée suivante montre le résultat quand le paramètre BaseIntermediateOutput
est placé dans Directory.Build.props
. Les commentaires en haut des importations standard incluent le nom de fichier et généralement quelques informations utiles sur la raison pour laquelle ce fichier est importé.
<?xml version="1.0" encoding="IBM437"?>
<!--
============================================================================================================================================
c:\source\repos\ConsoleApp9\ConsoleApp9\ConsoleApp9.csproj
============================================================================================================================================
-->
<Project DefaultTargets="Build">
<!--
============================================================================================================================================
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk">
This import was added implicitly because the Project element's Sdk attribute specified "Microsoft.NET.Sdk".
C:\Program Files\dotnet\sdk\7.0.200-preview.22628.1\Sdks\Microsoft.NET.Sdk\Sdk\Sdk.props
============================================================================================================================================
-->
<!--
***********************************************************************************************
Sdk.props
WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
created a backup copy. Incorrect changes to this file will make it
impossible to load or build your projects from the command-line or the IDE.
Copyright (c) .NET Foundation. All rights reserved.
***********************************************************************************************
-->
<PropertyGroup xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!--
Indicate to other targets that Microsoft.NET.Sdk is being used.
This must be set here (as early as possible, before Microsoft.Common.props)
so that everything that follows can depend on it.
In particular, Directory.Build.props and nuget package props need to be able
to use this flag and they are imported by Microsoft.Common.props.
-->
<UsingMicrosoftNETSdk>true</UsingMicrosoftNETSdk>
<!--
Indicate whether the set of SDK defaults that makes SDK style project concise are being used.
For example: globbing, importing msbuild common targets.
Similar to the property above, it must be set here.
-->
<UsingNETSdkDefaults>true</UsingNETSdkDefaults>
</PropertyGroup>
<PropertyGroup Condition="'$(MSBuildProjectFullPath)' == '$(ProjectToOverrideProjectExtensionsPath)'" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<MSBuildProjectExtensionsPath>$(ProjectExtensionsPathForSpecifiedProject)</MSBuildProjectExtensionsPath>
</PropertyGroup>
<!--<Import Project="$(AlternateCommonProps)" Condition="'$(AlternateCommonProps)' != ''" />-->
<!--
============================================================================================================================================
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="'$(AlternateCommonProps)' == ''">
C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Microsoft.Common.props
============================================================================================================================================
-->
<!--
***********************************************************************************************
Microsoft.Common.props
WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
created a backup copy. Incorrect changes to this file will make it
impossible to load or build your projects from the command-line or the IDE.
Copyright (C) Microsoft Corporation. All rights reserved.
***********************************************************************************************
-->
<PropertyGroup>
<ImportByWildcardBeforeMicrosoftCommonProps Condition="'$(ImportByWildcardBeforeMicrosoftCommonProps)' == ''">true</ImportByWildcardBeforeMicrosoftCommonProps>
<ImportByWildcardAfterMicrosoftCommonProps Condition="'$(ImportByWildcardAfterMicrosoftCommonProps)' == ''">true</ImportByWildcardAfterMicrosoftCommonProps>
<ImportUserLocationsByWildcardBeforeMicrosoftCommonProps Condition="'$(ImportUserLocationsByWildcardBeforeMicrosoftCommonProps)' == ''">true</ImportUserLocationsByWildcardBeforeMicrosoftCommonProps>
<ImportUserLocationsByWildcardAfterMicrosoftCommonProps Condition="'$(ImportUserLocationsByWildcardAfterMicrosoftCommonProps)' == ''">true</ImportUserLocationsByWildcardAfterMicrosoftCommonProps>
<ImportDirectoryBuildProps Condition="'$(ImportDirectoryBuildProps)' == ''">true</ImportDirectoryBuildProps>
</PropertyGroup>
<!--
Determine the path to the directory build props file if the user did not disable $(ImportDirectoryBuildProps) and
they did not already specify an absolute path to use via $(DirectoryBuildPropsPath)
-->
<PropertyGroup Condition="'$(ImportDirectoryBuildProps)' == 'true' and '$(DirectoryBuildPropsPath)' == ''">
<_DirectoryBuildPropsFile Condition="'$(_DirectoryBuildPropsFile)' == ''">Directory.Build.props</_DirectoryBuildPropsFile>
<_DirectoryBuildPropsBasePath Condition="'$(_DirectoryBuildPropsBasePath)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), '$(_DirectoryBuildPropsFile)'))</_DirectoryBuildPropsBasePath>
<DirectoryBuildPropsPath Condition="'$(_DirectoryBuildPropsBasePath)' != '' and '$(_DirectoryBuildPropsFile)' != ''">$([System.IO.Path]::Combine('$(_DirectoryBuildPropsBasePath)', '$(_DirectoryBuildPropsFile)'))</DirectoryBuildPropsPath>
</PropertyGroup>
<!--
============================================================================================================================================
<Import Project="$(DirectoryBuildPropsPath)" Condition="'$(ImportDirectoryBuildProps)' == 'true' and exists('$(DirectoryBuildPropsPath)')">
c:\source\repos\ConsoleApp9\Directory.Build.props
============================================================================================================================================
-->
<!-- Directory.build.props
-->
<PropertyGroup>
<BaseIntermediateOutputPath>myBaseIntermediateOutputPath</BaseIntermediateOutputPath>
</PropertyGroup>
<!--
============================================================================================================================================
</Import>
C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Microsoft.Common.props
============================================================================================================================================
-->
<!--
Prepare to import project extensions which usually come from packages. Package management systems will create a file at:
$(MSBuildProjectExtensionsPath)\$(MSBuildProjectFile).<SomethingUnique>.props
Each package management system should use a unique moniker to avoid collisions. It is a wild-card import so the package
management system can write out multiple files but the order of the import is alphabetic because MSBuild sorts the list.
-->
<PropertyGroup>
<!--
The declaration of $(BaseIntermediateOutputPath) had to be moved up from Microsoft.Common.CurrentVersion.targets
in order for the $(MSBuildProjectExtensionsPath) to use it as a default.
-->
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
<BaseIntermediateOutputPath Condition="!HasTrailingSlash('$(BaseIntermediateOutputPath)')">$(BaseIntermediateOutputPath)\</BaseIntermediateOutputPath>
<_InitialBaseIntermediateOutputPath>$(BaseIntermediateOutputPath)</_InitialBaseIntermediateOutputPath>
<MSBuildProjectExtensionsPath Condition="'$(MSBuildProjectExtensionsPath)' == '' ">$(BaseIntermediateOutputPath)</MSBuildProjectExtensionsPath>
<!--
Import paths that are relative default to be relative to the importing file. However, since MSBuildExtensionsPath
defaults to BaseIntermediateOutputPath we expect it to be relative to the project directory. So if the path is relative
it needs to be made absolute based on the project directory.
-->
<MSBuildProjectExtensionsPath Condition="'$([System.IO.Path]::IsPathRooted($(MSBuildProjectExtensionsPath)))' == 'false'">$([System.IO.Path]::Combine('$(MSBuildProjectDirectory)', '$(MSBuildProjectExtensionsPath)'))</MSBuildProjectExtensionsPath>
<MSBuildProjectExtensionsPath Condition="!HasTrailingSlash('$(MSBuildProjectExtensionsPath)')">$(MSBuildProjectExtensionsPath)\</MSBuildProjectExtensionsPath>
<ImportProjectExtensionProps Condition="'$(ImportProjectExtensionProps)' == ''">true</ImportProjectExtensionProps>
<_InitialMSBuildProjectExtensionsPath Condition=" '$(ImportProjectExtensionProps)' == 'true' ">$(MSBuildProjectExtensionsPath)</_InitialMSBuildProjectExtensionsPath>
</PropertyGroup>
...