Anpassen des Builds nach Ordnern
Sie können bestimmte Dateien hinzufügen, die von MSBuild importiert werden sollen, um die Standardeigenschafteneinstellungen außer Kraft zu setzen und benutzerdefinierte Ziele hinzuzufügen. Der Umfang dieser Anpassungen kann auf Ordnerebene gesteuert werden, je nachdem, wo diese Dateien platziert werden.
In diesem Artikel werden Anpassungen behandelt, die für die folgenden Szenarien gelten:
- Anpassen von Buildeinstellungen für viele Projekte in einer Lösung
- Anpassen von Buildeinstellungen für viele Lösungen unter einem allgemeinen Dateiverzeichnis
- Anpassen von Buildeinstellungen, die sich für Unterordner in einer komplexen Ordnerstruktur unterscheiden können
- Standardmäßig festgelegte Einstellungen, Standardbuild-Ordner und andere Verhaltensweisen, die von einem SDK wie
Microsoft.Net.Sdk
festgelegt wurden, überschreiben. - Hinzufügen oder Anpassen von Build-Zielen, die für eine beliebige Anzahl von Projekten oder Lösungen gelten.
Wenn Sie mit C++-Projekten arbeiten, können Sie auch die unter Customize C++-Builds beschriebenen Methodenverwenden.
„Directory.Build.props“ und „Directory.Build.targets“
Sie können jedem Projekt eine neue Eigenschaft hinzufügen, indem Sie sie in einer einzelnen Datei namens Directory.Build.props im Stammordner definieren, der Ihre Quelle enthält.
Wenn MSBuild ausgeführt wird, durchsucht Microsoft.Common.props Ihre Verzeichnisstruktur nach der Datei Directory.Build.props. Wenn die Datei gefunden wird, importiert sie die Datei und liest die darin definierten Eigenschaften. Directory.Build.props ist eine benutzerdefinierte Datei, die Anpassungen für Projekte unter einem Verzeichnis bereitstellt.
Ebenso sucht Microsoft.Common.targets nach Directory.Build.targets.
Directory.Build.props wird frühzeitig in der Abfolge importierter Dateien importiert, was wichtig sein kann, wenn Sie eine Eigenschaft festlegen müssen, die von Importen verwendet wird, insbesondere diejenigen, die implizit mithilfe des attributs Sdk
importiert werden, z. B. bei Verwendung des .NET SDK in den meisten .NET-Projektdateien.
Anmerkung
Bei Linux-basierten Dateisystemen wird zwischen Groß- und Kleinschreibung unterschieden. Stellen Sie sicher, dass die Schreibweise des Dateinamens Directory.Build.props genau übereinstimmt, sonst wird die Datei während des Buildvorgangs nicht erkannt.
Weitere Informationen finden Sie in diesem GitHub-Issue.
Beispiel für Directory.Build.props
Hier ist beispielsweise eine Directory.Build.props-Datei, die das Ausgabeverzeichnis für alle Projekte in einer Visual Studio-Projektmappe festlegt. Die Ausgabe jedes Projekts wird unter dem eigenen Projektnamen platziert. In diesem Beispiel befindet sich die datei Directory.Build.props in einem Lösungsordner mit vielen Projekten in Unterordnern. Die $(MSBuildProjectName)
-Eigenschaft gibt den Namen jedes Projekts an. Da die Datei Directory.Build.props während des Builds in jedes Projekt importiert wird, wird sie für jedes einzelne Projekt in der Projektmappe auf den korrekten Wert ausgewertet.
Bereinigen Sie die Lösung, um alle alten Ausgabedateien zu entfernen.
msbuild /t:Clean SolutionName.sln
Erstellen Sie eine neue Datei im Stammverzeichnis Ihres Repositorys namens Directory.Build.props.
Fügen Sie der Datei den folgenden XML-Code hinzu.
<Project> <PropertyGroup> <OutDir>C:\output\$(MSBuildProjectName)</OutDir> </PropertyGroup> </Project>
Anmerkung
Die
$(OutDir)
-Eigenschaft ist ein absoluter Pfad zur Ausgabe und durch die Verwendung wird das Anlegen von Unterordnern für die Konfigurationsdateien, das Zielframework oder die Laufzeit vermieden, die normalerweise in .NET-Projekten verwendet werden. Versuchen Sie stattdessen, die EigenschaftBaseOutputPath
zu verwenden, wenn die üblichen Unterordner unter einem benutzerdefinierten Ausgabepfad erstellt werden sollen.Führen Sie MSBuild aus. Die vorhandenen Importe von Microsoft.Common.props und Microsoft.Common.targets suchen und importieren die Directory.Build.props-Datei, und der neue Ausgabeordner wird für alle Projekte unter diesem Ordner verwendet.
Suchbereich
Bei der Suche nach einer Directory.Build.props- Datei durchläuft MSBuild die Verzeichnisstruktur vom Projektspeicherort aus nach oben $(MSBuildProjectFullPath)
und stoppt, wenn eine Directory.Build.props Datei gefunden wurde. Wenn Ihre $(MSBuildProjectFullPath)
z. B. c:\users\username\code\test\case1war, startet MSBuild die Suche dort und durchsucht dann die Verzeichnisstruktur nach oben, bis sie eine Directory.Build.props Datei wie in der folgenden Verzeichnisstruktur befindet.
c:\users\username\code\test\case1
c:\users\username\code\test
c:\users\username\code
c:\users\username
c:\users
c:\
Der Speicherort der Lösungsdatei ist für Directory.Build.propsirrelevant.
Importreihenfolge
Directory.Build.props wird früh in Microsoft.Common.propsimportiert, und die später definierten Eigenschaften sind nicht verfügbar. Vermeiden Sie daher, auf Eigenschaften zu verweisen, die noch nicht definiert sind (und als leer ausgewertet werden).
Eigenschaften, die in Directory.Build.props festgelegt sind, können an anderer Stelle in der Projektdatei oder in importierten Dateien überschrieben werden. Daher sollten Sie sich die Einstellungen in Directory.Build.props als Angabe der Standardwerte für Ihre Projekte vorstellen.
Directory.Build.targets wird aus Microsoft.Common.targets importiert, nachdem .targets
Dateien aus NuGet-Paketen importiert wurden. Es kann also Eigenschaften und Ziele überschreiben, die in der meisten Build-Logik definiert sind, oder Eigenschaften für alle Ihre Projekte festlegen, unabhängig von den Einstellungen der einzelnen Projekte.
Wenn Sie eine Eigenschaft festlegen oder ein Ziel für ein einzelnes Projekt definieren müssen, das alle vorherigen Einstellungen überschreibt, platzieren Sie diese Logik nach dem endgültigen Import in der Projektdatei. Um dies in einem SDK-Formatprojekt zu tun, müssen Sie zuerst das SDK-Format-Attribut durch die entsprechenden Importe ersetzen. Weitere Informationen finden Sie unter Verwenden von MSBuild-Projekt-SDKs.
Anmerkung
Die MSBuild-Engine liest beim Evaluieren alle importierten Dateien ein, bevor die Buildausführung für ein Projekt startet (einschließlich PreBuildEvent
), sodass erwartet wird, dass diese Dateien nicht vom PreBuildEvent
oder einem anderen Teil des Buildprozesses verändert werden. Alle Änderungen werden erst wirksam, wenn der nächste Aufruf von MSBuild.exe oder dem nächsten Visual Studio-Build ausgeführt wird. Wenn Ihr Buildprozess viele Projektbuilds enthält (wie bei der Festlegung von Zielversionen oder Erstellung abhängiger Projekte), werden importierte Dateien, einschließlich Directory.build.props, bei der Auswertung für jeden einzelne Projektbuild gelesen.
Anwendungsfall: Zusammenführen mehrerer Ebenen
Angenommen, Sie haben diese Standardlösungsstruktur:
\
MySolution.sln
Directory.Build.props (1)
\src
Directory.Build.props (2-src)
\Project1
\Project2
\test
Directory.Build.props (2-test)
\Project1Tests
\Project2Tests
Es kann sinnvoll sein, gemeinsame Eigenschaften für alle Projekte (1), für src-Projekte (2-src) und für test-Projekte (2-test) zu verwenden.
Damit MSBuild die "inneren" Dateien (2-src- und 2-Test-) ordnungsgemäß mit der "äußeren" Datei (1) zusammenführt, müssen Sie berücksichtigen, dass, sobald MSBuild eine Directory.Build.props Datei findet, das weitere Scannen beendet. Wenn Sie mit dem Scannen und Zusammenführen in die äußere Datei fortfahren möchten, platzieren Sie diesen Code in beiden inneren Dateien:
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
Eine Zusammenfassung des allgemeinen Ansatzes von MSBuild lautet wie folgt:
- Für jedes vorhandene Projekt sucht MSBuild die erste Datei Directory.Build.props, die in der Projektmappenstruktur oberhalb der aktuellen Position gefunden wird, führt diese mit den Standardeinstellungen zusammen und beendet anschließend den Suchvorgang.
- Wenn mehrere Ebenen gefunden und zusammengeführt werden sollen,
<Import...>
dann (wie zuvor gezeigt) die „äußere“ Datei aus der „inneren“ Datei. - Wenn die "äußere" Datei nicht selbst auch etwas, das über ihr liegt, importiert, dann wird das Scannen dort gestoppt.
Oder einfacher: Die erste Directory.Build.props, die nichts importiert, stellt den Punkt dar, an dem MSBuild stoppt.
Um den Importvorgang explizit zu steuern, verwenden Sie die Eigenschaften $(DirectoryBuildPropsPath)
, $(ImportDirectoryBuildProps)
, $(DirectoryBuildTargetsPath)
und $(ImportDirectoryBuildTargets)
. Die Eigenschaft $(DirectoryBuildPropsPath)
gibt den Pfad zur zu verwendenden Directory.Build.props
Datei an; in ähnlicher Weise gibt $(DirectoryBuildTargetsPath)
den Pfad zur Directory.Build.targets
Datei an.
Die booleschen Eigenschaften $(ImportDirectoryBuildProps)
und $(ImportDirectoryBuildTargets)
sind standardmäßig auf true
festgelegt, sodass MSBuild normalerweise nach diesen Dateien sucht, aber Sie können sie auf false
festlegen, um zu verhindern, dass MSBuild sie importiert.
Beispiel
An diesem Beispiel wird veranschaulicht, wie die vorverarbeitete Ausgabe verwendet wird, um festzulegen, wo eine Eigenschaft gesetzt werden soll.
Zur Analyse der Verwendung einer bestimmten Eigenschaft, die Sie festlegen möchten, können Sie MSBuild mit dem Argument /preprocess
oder /pp
ausführen. Der Ausgabetext ist das Ergebnis aller Importe, einschließlich der Systemimporte wie Microsoft.Common.props, die implizit importiert werden, und eines Ihrer eigenen Importe. In dieser Ausgabe können Sie sehen, wo Ihre Eigenschaft relativ zu der Stelle festgelegt werden muss, an der der Wert verwendet wird.
Angenommen, Sie haben ein einfaches .NET Core- oder .NET 5- oder höher Konsolen-App-Projekt, und Sie möchten den Zwischenausgabeordner anpassen, normalerweise obj
. Die Eigenschaft, die diesen Pfad angibt, ist BaseIntermediateOutput
. Wenn Sie versuchen, dies in ein PropertyGroup
-Element in Ihrer Projektdatei zusammen mit den verschiedenen anderen Eigenschaften einzufügen, die bereits dort festgelegt sind, z. B. TargetFramework
, würden Sie feststellen, dass, wenn Sie das Projekt erstellen, die Eigenschaft nicht wirksam wird. Wenn Sie MSBuild mit der Option /pp
ausführen und die Ausgabe nach BaseIntermediateOutputPath
durchsuchen, können Sie sehen, warum. In diesem Fall wird BaseIntermediateOutput
in Microsoft.Common.props
gelesen und verwendet.
In Microsoft.Common.props gibt es einen Kommentar, der besagt, dass die Eigenschaft BaseIntermediateOutput
hier festgelegt werden muss, bevor sie von einer anderen Eigenschaft MSBuildProjectExtensionsPath
verwendet wird. Sie können auch sehen, dass beim anfänglichen Festlegen von BaseIntermediateOutputPath
eine Überprüfung auf einen bereits vorhandenen Wert vorhanden ist und wenn sie nicht definiert ist, wird sie auf obj
festgelegt.
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>
Diese Platzierung informiert Sie darüber, dass diese Eigenschaft bereits an früherer Stelle angegeben sein muss, damit sie festgelegt werden kann. Direkt vor diesem Code in der vorverarbeiteten Ausgabe können Sie sehen, dass Directory.Build.props
importiert wird, sodass Sie BaseIntermediateOutputPath
dort festlegen können und es früh genug festgelegt wird, um den gewünschten Effekt zu haben.
Die folgende abgekürzte vorverarbeitete Ausgabe zeigt das Ergebnis des Einfügens der BaseIntermediateOutput
Einstellung in Directory.Build.props
. Die Kommentare am Anfang der Standardimporte enthalten den Dateinamen und in der Regel einige hilfreiche Informationen dazu, warum diese Datei importiert wird.
<?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>
...