Anpassen des Builds nach Ordner

Sie können bestimmte Dateien hinzufügen, die von MSBuild importiert werden sollen, um Standardeigenschafteneinstellungen außer Kraft zu setzen und benutzerdefinierte Ziele hinzuzufügen. Der Umfang dieser Anpassungen kann auf Ordnerebene anhand des Speicherorts dieser Dateien gesteuert werden.

In diesem Artikel werden Anpassungen beschrieben, die für die folgenden Szenarien gelten:

  • Anpassen von Buildeinstellungen für viele Projekte in einer Projektmappe
  • Anpassen von Buildeinstellungen für viele Projektmappen unter einem gemeinsamen Dateiverzeichnis
  • Anpassen von Buildeinstellungen, die sich für Unterordner in einer komplexen Ordnerstruktur unterscheiden können
  • Außerkraftsetzen von Standardeinstellungen, Standardbuildordnern und anderen Verhalten, die von einem SDK festgelegt werden, z. B. Microsoft.Net.Sdk
  • Hinzufügen oder Anpassen von Buildzielen, die für eine beliebige Anzahl von Projekten oder Projektmappen gelten

Wenn Sie mit C++-Projekten arbeiten, können Sie auch die unter Anpassen von C++-Builds beschriebenen Methoden verwenden.

„Directory.Build.props“ und „Directory.Build.targets“

Sie können jedem Projekt eine neue Eigenschaft hinzufügen, indem Sie sie in einer einzigen Datei namens Directory.Build.props im Stammverzeichnis der Quelle definieren.

Wenn MSBuild ausgeführt wird, durchsucht Microsoft.Common.props Ihre Verzeichnisstruktur nach der Datei Directory.Build.props. Wenn eine Datei gefunden wird, wird sie importiert, und die darin definierten Eigenschaften werden gelesen. Bei Directory.Build.props handelt es sich um eine benutzerdefinierte Datei, die Anpassungen für Projekte in einem Verzeichnis bereitstellt.

Ebenso sucht Microsoft.Common.targets nach Directory.Build.targets.

Directory.Build.props wird in der Sequenz der importierten Dateien früh importiert. Das kann wichtig sein, wenn Sie bei Importen verwendete Eigenschaften festlegen müssen, insbesondere solche, die implizit mithilfe des Sdk-Attributs importiert werden, z. B. bei Verwendung des .NET SDK in den meisten .NET-Projektdateien.

Hinweis

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“

Wenn Sie beispielsweise für alle Ihre Projekte den Zugriff auf die neue Roslyn-Funktion /deterministic (die im CoreCompile-Ziel von Roslyn durch die Eigenschaft $(Deterministic) verfügbar gemacht wird) aktivieren möchten, können Sie wie folgt vorgehen.

  1. Erstellen Sie im Stammverzeichnis Ihres Repositorys eine neue Datei mit dem Namen Directory.Build.props.

  2. Fügen Sie der Datei das folgende XML hinzu.

    <Project>
     <PropertyGroup>
       <Deterministic>true</Deterministic>
     </PropertyGroup>
    </Project>
    
  3. Führen Sie MSBuild aus. Die vorhandenen Importe des Projekts von Microsoft.Common.props und Microsoft.Common.targets finden die Datei und importieren sie.

Suchbereich

Bei der Suche nach einer Datei Directory.Build.props durchläuft MSBuild die Verzeichnisstruktur vom Speicherort des Projekts $(MSBuildProjectFullPath) aufwärts und stoppt, wenn eine Datei Directory.Build.props gefunden wird. Wenn $(MSBuildProjectFullPath) zum Beispiel C:\users\username\code\test\case1 lautet, beginnt MSBuild an dieser Stelle und durchsucht die Verzeichnisstruktur aufwärts, bis wie in der folgenden Verzeichnisstruktur eine Datei Directory.Build.props gefunden wird.

c:\users\username\code\test\case1
c:\users\username\code\test
c:\users\username\code
c:\users\username
c:\users
c:\

Der Speicherort der Projektmappendatei ist für Directory.Build.props ohne Bedeutung.

Importreihenfolge

Directory.Build.props wird zu einem frühen Zeitpunkt in Microsoft.Common.props importiert, und später definierte Eigenschaften sind dafür nicht verfügbar. Vermeiden Sie deshalb das Verweisen auf Eigenschaften, die noch nicht definiert sind (und als leer ausgewertet werden).

Eigenschaften, die in Directory.Build.props festgelegt werden, 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 Standardeinstellungen für Ihre Projekte vorstellen.

Directory.Build.targets wird aus Microsoft.Common.targets importiert, nachdem .targets-Dateien aus NuGet-Paketen importiert wurden. Die Datei kann also Eigenschaften und Ziele, die in den meisten Buildlogiken definiert sind, außer Kraft setzen oder Eigenschaften für alle Ihre Projekte festlegen, und zwar unabhängig davon, was die einzelnen Projekte festlegen.

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 letzten Import in der Projektdatei. In einem SDK-Projekt müssen Sie dazu müssen zuerst das Attribut im SDK-Stil durch die entsprechenden Importe ersetzen. Weitere Informationen finden Sie unter Vorgehensweise: Verwenden von MSBuild-Projekt-SDKs.

Hinweis

Die MSBuild-Engine liest alle importierten Dateien während der Auswertung ein, bevor mit der Buildausführung für ein Projekt (einschließlich aller PreBuildEvent-Vorgänge) begonnen wird, sodass nicht erwartet wird, dass diese Dateien durch das PreBuildEvent oder einen anderen Teil des Buildprozesses geändert werden. Änderungen werden erst nach dem nächsten Aufruf von MSBuild.exe oder dem nächsten Visual Studio-Buildvorgang wirksam. 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

Für diesen Anwendungsfall wird beispielhaft davon ausgegangen, dass Sie die folgende Standard-Projektmappenstruktur verwenden:

\
  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) mit der „äußeren“ Datei (1) korrekt zusammenführen kann, müssen Sie berücksichtigen, dass MSBuild nach der ersten gefundenen Datei Directory.Build.props den Suchvorgang abbricht. Zum Fortsetzen des Suchvorgangs und des Zusammenführens in der äußeren Datei ist es erforderlich, diesen Code in beide inneren Dateien einzufügen:

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />

Der allgemeine Ansatz von MSBuild lässt sich folgendermaßen zusammenfassen:

  • 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 ebenfalls Dateien importiert, die sich in der Projektstruktur oberhalb der Datei befinden, wird der Suchvorgang an dieser Stelle beendet.

Oder in einem Satz ausgedrückt: MSBuild beendet den Suchvorgang, sobald eine Directory.Build.props-Datei gefunden wurde, durch die keine weiteren Dateien importiert werden.

Um den Importvorgang expliziter zu steuern, verwenden Sie die Eigenschaften $(DirectoryBuildPropsPath), $(ImportDirectoryBuildProps), $(DirectoryBuildTargetsPath) und $(ImportDirectoryBuildTargets). Die Eigenschaft $(DirectoryBuildPropsPath) gibt den Pfad zur Datei Directory.Build.props an, die verwendet werden soll. Entsprechend gibt $(DirectoryBuildTargetsPath) den Pfad zur Datei Directory.Build.targets an.

Die booleschen Eigenschaften $(ImportDirectoryBuildProps) und $(ImportDirectoryBuildTargets) sind standardmäßig auf true festgelegt, daher sucht MSBuild normalerweise nach diesen Dateien, aber Sie können sie auf false festlegen, um zu verhindern, dass MSBuild sie importiert.

Beispiel

Dieses Beispiel zeigt die Verwendung der vorverarbeiteten Ausgabe, um zu bestimmen, wo eine Eigenschaft festgelegt werden soll.

Um die Verwendung einer bestimmten Eigenschaft zu analysieren, die Sie festlegen möchten, können Sie MSBuild mit dem Argument /preprocess oder /pp ausführen. Der Ausgabetext ist das Ergebnis aller Importvorgänge, einschließlich der impliziten Systemimportvorgänge wie Microsoft.Common.props und Ihrer eigenen Importvorgänge. 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 Konsolen-App-Projekt mit .NET Core oder .NET 5 oder höher, und Sie möchten den Zwischenausgabeordner anpassen, der normalerweise obj lautet. Dieser Pfad wird durch die Eigenschaft BaseIntermediateOutput angegeben. Wenn Sie versuchen, dies in einem PropertyGroup-Element in Ihrer Projektdatei festzulegen, zusammen mit den verschiedenen anderen Eigenschaften, die dort bereits festgelegt sind (z. B. TargetFramework), würden Sie beim Erstellen des Builds feststellen, dass die Eigenschaft keine Auswirkungen hat. Wenn Sie MSBuild mit der Option /pp ausführen und die Ausgabe nach BaseIntermediateOutputPath durchsuchen, können Sie sehen, warum das so ist. In diesem Fall wird BaseIntermediateOutput in Microsoft.Common.props gelesen und verwendet.

Ein Kommentar in Microsoft.Common.props besagt, dass die Eigenschaft BaseIntermediateOutput hier festgelegt werden muss, bevor sie von einer anderen Eigenschaft, MSBuildProjectExtensionsPath, verwendet wird. Sie können auch Folgendes sehen: Bei der anfänglichen Festlegung von BaseIntermediateOutputPath wird überprüft, ob bereits ein Wert vorhanden ist, und wenn kein Wert definiert ist, wird der Pfad 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. Damit wissen Sie, dass Sie BaseIntermediateOutputPath dort festlegen können, und die Festlegung erfolgt früh genug, um den gewünschten Effekt zu haben.

Die folgende abgekürzte vorverarbeitete Ausgabe zeigt das Ergebnis, wenn Sie die Einstellung BaseIntermediateOutput in Directory.Build.props einfügen. Die Kommentare oben bei den Standardimportvorgängen 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>
  ...