自訂依資料夾的組建

您可以新增 MSBuild 匯入的特定檔案,以覆寫預設屬性設定並新增自訂目標。 這些自訂的範圍可以透過放置這些檔案的資料夾層級來控制。

本文涵蓋適用于下列案例的自訂專案:

  • 自訂方案中許多專案的組建設定
  • 自訂常見檔案目錄下許多解決方案的組建設定
  • 自訂資料夾複雜結構中子資料夾可能不同的組建設定
  • 覆寫 SDK 所設定的預設設定、預設建置資料夾和其他行為,例如 Microsoft.Net.Sdk
  • 新增或自訂套用至任意數目專案或方案的建置目標

如果您使用 C++ 專案,此處所述的方法將無法運作。 相反地,您可以使用 自訂 C++ 組建中所述的方法。

Directory.Build.props 和 Directory.Build.targets

您可以在包含來源之根資料夾中的單一檔案中定義屬性,將它定義至每個專案,稱為 Directory.Build.props

當 MSBuild 執行時, Microsoft.Common.props 會搜尋目錄結構中的 Directory.Build.props 檔案。 如果找到檔案,它會匯入檔案,並讀取其中定義的屬性。 Directory.Build.props 是使用者定義的檔案,可提供目錄下專案的自訂。

同樣地, Microsoft.Common.targets 會尋找 Directory.Build.targets

Directory.Build.props 會在匯入的檔案序列中早期匯入,如果您需要匯入所使用的屬性,特別是使用 Sdk 屬性隱含匯入的屬性,例如在大部分 .NET 專案檔中使用 .NET SDK 時,這些屬性會很重要。

注意

以 Linux 為基礎的檔案系統會區分大小寫。 請確定 Directory.Build.props 檔案名稱的大小寫完全相符,否則在建置過程中將不會偵測到。

如需詳細資訊,請參閱此 GitHub 問題

Directory.Build.props 範例

例如,如果您想要讓所有專案存取新的 Roslyn /deterministic 功能 (透過 $(Deterministic) 屬性公開於 Roslyn CoreCompile 目標中),則可以執行下列動作。

  1. 在存放庫根目錄中建立名為 Directory.Build.props的新檔案。

  2. 將下列 XML 新增至檔案。

    <Project>
     <PropertyGroup>
       <Deterministic>true</Deterministic>
     </PropertyGroup>
    </Project>
    
  3. 執行 MSBuild。 您專案現有的 Microsoft.Common.propsMicrosoft.Common.targets 匯入會尋找檔案並加以匯入。

搜尋範圍

搜尋 Directory.Build.props 檔案時,MSBuild 會從專案位置 ($(MSBuildProjectFullPath)) 往上逐步瀏覽目錄結構,並在找到 Directory.Build.props 檔案之後停止。 例如,如果您的 $(MSBuildProjectFullPath)c:\users\username\code\test\case1,則 MSBuild 會在該處開始搜尋,然後往上搜尋目錄結構,直到找到下列目錄結構中的 Directory.Build.props 檔案。

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

方案檔的位置與 Directory.Build.props無關。

匯入順序

Microsoft.Common.props 中,Directory.Build.props 很早就會被匯入,因此它無法使用較晚才定義的屬性。 因此,請避免參考尚未定義的屬性 (將會評估為空的)。

在 Directory.Build.props中設定的屬性可以在專案檔或匯入的檔案中其他位置覆寫,因此您應該將Directory.Build.props中的設定視為指定專案的預設值。

從 NuGet 套件匯入檔案之後,Directory.Build.targets會從Microsoft.Common.targets匯入 .targets 。 因此,它可以覆寫大部分建置邏輯中定義的屬性和目標,或設定所有專案的屬性,而不論個別專案設定的內容為何。

當您需要設定屬性或定義覆寫任何先前設定之個別專案的目標時,請在最終匯入之後將該邏輯放在專案檔中。 若要在 SDK 樣式專案中執行這項操作,您必須先以對等的匯入取代 SDK 樣式屬性。 請參閱 如何使用 MSBuild 專案 SDK

注意

MSBuild 引擎會在評估期間讀取所有匯入的檔案,再開始執行專案 (包括任何 PreBuildEvent) ,因此這些檔案不會由 PreBuildEvent 建置程式或任何其他部分修改。 在下次叫用 MSBuild.exe 或下一個 Visual Studio 組建之前,任何修改都不會生效。 此外,如果您的建置套裝程式含許多專案建置 (,如同多目標或建置相依專案) ,則會讀取匯入的檔案,包括 Directory.build.props ,在針對每個個別專案組建進行評估時都會讀取。

使用案例:多層級合併

假設您有這種標準方案結構:

\
  MySolution.sln
  Directory.Build.props     (1)
  \src
    Directory.Build.props   (2-src)
    \Project1
    \Project2
  \test
    Directory.Build.props   (2-test)
    \Project1Tests
    \Project2Tests

使用者可能需要所有專案 (1) 的通用屬性、src 專案 (2-src) 的通用屬性和 test 專案 (2-test) 的通用屬性。

若要讓 MSBuild 正確合併「內部」檔案 (2-src2-test) 與「外部」檔案 (1),您必須注意,一旦 MSBuild 找到 Directory.Build.props 檔案,就會停止進一步掃描。 若要繼續掃描並合併至外部檔案,請將此程式碼放入這兩個內部檔案中:

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

MSBuild 的一般方法摘要如下:

  • 對於任何指定的專案,MSBuild 會在方案結構中向上尋找第一個 Directory.Build.props 、將其合併為預設值,並停止掃描其他專案。
  • 如果您想要找到併合並多個層級,則 <Import...> (顯示于上方) 來自「內部」檔案的「外部」檔案。
  • 如果「外部」檔案本身也不會匯入其上方的內容,則掃描會停止該處。

或更簡單的做法:第一個不會匯入任何項目的 Directory.Build.props,則為 MSBuild 停止的位置。

若要更明確地控制匯入程式,請使用 、、 $(DirectoryBuildTargetsPath)$(ImportDirectoryBuildTargets) 屬性 $(ImportDirectoryBuildProps)$(DirectoryBuildPropsPath) 。 屬性 $(DirectoryBuildPropsPath) 會指定要使用之 Directory.Build.props 檔案的路徑;同樣地, $(DirectoryBuildTargetsPath) 會指定檔案的路徑 Directory.Build.targets

布林屬性 $(ImportDirectoryBuildProps)$(ImportDirectoryBuildTargets) 預設會設定為 true ,因此 MSBuild 通常會搜尋這些檔案,但您可以將它們設定為 false ,以防止 MSBuild 匯入這些檔案。

範例

此範例示範如何使用前置處理的輸出來判斷要設定屬性的位置。

為了協助您分析您想要設定之特定屬性的使用方式,您可以使用 或 /pp 引數來執行 MSBuild /preprocess 。 輸出文字是所有匯入的結果,包括系統匯入,例如隱含匯入的 Microsoft.Common.props ,以及您自己的任何匯入。 透過此輸出,您可以看到屬性必須設定相對於其值使用位置的位置。

例如,假設您有簡單的 .NET Core 或 .NET 5 或更新版本的主控台應用程式專案,而且您想要自訂中繼輸出檔案夾,通常是 obj 。 指定此路徑的屬性為 BaseIntermediateOutput 。 如果您嘗試將這個專案 PropertyGroup 放在專案檔中的元素,以及已經設定在該處的各種其他屬性,例如 TargetFramework ,當您建置屬性不會生效的專案時,就會發現。 如果您使用 選項執行 MSBuild /pp 並搜尋輸出 BaseIntermediateOutputPath ,您可以查看原因。 在此情況下, BaseIntermediateOutput 會在 中 Microsoft.Common.props 讀取並使用 。

Microsoft.Common.props中有一個批註,指出屬性 BaseIntermediateOutput 必須在這裡設定,然後再由另一個屬性 MSBuildProjectExtensionsPath 使用。 您也可以看到一開始設定時 BaseIntermediateOutputPath ,會檢查預先存在的值,如果尚未定義,則會將 它設定為 obj

<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">obj\</BaseIntermediateOutputPath>

因此,這會告訴您,若要設定此屬性,它必須早于此位置指定。 剛好在預處理器輸出中的這個程式碼之前,您可以看到 Directory.Build.props 已匯入,因此這會告訴您您可以在那裡設定 BaseIntermediateOutputPath ,且其設定時間夠早,足以產生所需的效果。

下列縮寫的前置處理輸出會顯示將 BaseIntermediateOutput 設定 Directory.Build.props 放入 的結果。 標準匯入頂端的批註包括檔案名,而且通常有一些有關該檔案匯入原因的實用資訊。

<?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>
  ...

下一步

自訂群組建中探索其他可能的自訂案例。