1. 程式人生 > 實用技巧 >VS 自定義生成 Directory.Build.props Directory.Build.targets

VS 自定義生成 Directory.Build.props Directory.Build.targets

源地址:自定義生成 - Visual Studio | Microsoft Docs

使用標準生成程序(匯入 Microsoft.Common.props 和 Microsoft.Common.targets)的 MSBuild 專案有多個可用於自定義生成過程的擴充套件性掛鉤 。

向專案的命令列 MSBuild 呼叫新增引數

源目錄中或之上的 Directory.Build.rsp 檔案將應用到專案的命令列生成。有關詳細資訊,請參閱MSBuild 響應檔案

Directory.Build.props 和 Directory.Build.targets

在 MSBuild 15 版之前,如果要向解決方案中的專案提供新的自定義屬性,必須手動向解決方案中的每個專案檔案新增一個針對該屬性的引用。另外,還必須在 .props 檔案中定義屬性,然後在解決方案的每個專案中顯式匯入該 .props 檔案。

但現在,通過在包含源的根資料夾的名為 Directory.Build.props 的單個檔案中定義一個新屬性,只需一步即可向每個專案新增該屬性。在 MSBuild 執行時,Microsoft.Common.props 會搜尋 Directory.Build.props 檔案的目錄結構(Microsoft.Common.targets 將查詢 Directory.Build.targets)。如果找到,就會匯入該屬性。Directory.Build.props 是使用者定義檔案,對目錄下的專案提供自定義選項。

備註

基於 Linux 的檔案系統區分大小寫。請確保 Directory.Build.props 檔名的大小寫完全匹配,否則將不會在生成流程中檢測到它。

有關詳細資訊,請參閱此 GitHub 問題

Directory.Build.props 示例

例如,如果想要使所有專案都可以訪問新的 Roslyn /deterministic 功能(屬性$(Deterministic)在 RoslynCoreCompile目標中公開了此功能),可以執行以下操作。

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

  2. 將以下 XML 新增到此檔案。

    XML
    <Project>
     <PropertyGroup>
       <Deterministic>true</Deterministic>
     </PropertyGroup>
    </Project>
    
  3. 執行 MSBuild。專案現有的 Microsoft.Common.props 和 Microsoft.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 無關。

匯入順序

Directory.Build.props 很早便已匯入 Microsoft.Common.props,因此它無法使用後來定義的屬性 。因此,請避免引用尚未定義的屬性(否則計算結果將為空)。

Directory.Build.props 中設定的屬性可能會在專案檔案或匯入檔案中的其他位置被覆蓋,因此,應考慮將 Directory.Build.props 中的設定指定為專案的預設值 。

從 NuGet 包匯入 .targets 檔案後,會從 Microsoft.Common.targets 匯入 Directory.Build.targets。因此,它可以覆蓋大多數生成邏輯中定義的屬性和目標,或者為所有專案設定屬性,而不考慮各個專案的設定。

如果需要為單個專案設定覆蓋先前所有設定的屬性和目標,請在最終匯入後將該邏輯放在專案檔案中。要在 SDK 樣式的專案中執行此操作,必須先將 SDK 樣式的屬性替換為等效的匯入項。檢視如何使用 MSBuild 專案 SDK

備註

MSBuild 引擎在評估期間讀取所有匯入檔案,然後才開始對任何專案(包括任何PreBuildEvent)執行生成操作,以此確保PreBuildEvent或生成過程的任何其他部分都不會修改這些檔案。在下次呼叫 Msbuild.exe 或 Visual Studio 下次生成之後,修改才會生效。

用例:多級別合併

假設你具有此標準解決方案結構:

\
  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-src 和 2-test)和“外部”檔案 (1),必須考慮到 MSBuild 找到 Directory.Build.props 檔案後會立即停止進一步的掃描 。要繼續掃描併合併到外部檔案,請將此程式碼置於這兩個內部檔案中:

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

MSBuild 的常規方法彙總如下:

  • 對於任何給定的專案,MSBuild 在解決方案結構中向上查詢第一個 Directory.Build.props,將其與預設項合併,然後停止掃描
  • 如果要找到併合並多個級別,則從“內部”檔案<Import...>(如上所示)“外部”檔案
  • 如果“外部”檔案本身不會再匯入其上的內容,則掃描在此處停止
  • 要控制掃描/合併過程,請使用$(DirectoryBuildPropsPath)$(ImportDirectoryBuildProps)

或再簡單點:不能匯入任何內容的第一個 Directory.Build.props 即為 MSBuild 停止的位置。

選擇將屬性新增到 .props 檔案或 .targets 檔案

MSBuild 依賴於匯入順序,屬性(或UsingTask或目標)的最後一個定義是使用的定義。

使用顯式匯入時,可以隨時從 .props 或 .targets 檔案匯入。下面介紹廣泛使用的約定:

  • .props 檔案在匯入順序的早期匯入。

  • .targets 檔案在生成順序的後期匯入。

此約定由<Project Sdk="SdkName">匯入強制執行(即,在檔案的所有內容之前首先匯入 Sdk.props,然後在檔案的所有內容之後最後匯入 Sdk.targets)。

在決定在何處放置屬性後,使用以下通用原則:

  • 對於許多屬性,在何處定義它們並不重要,因為它們不會被覆蓋,只能在執行時讀取。

  • 對於可能在單個專案中自定義的行為,請在 .props 檔案中設定預設值。

  • 通過讀取可能自定義屬性的值,避免在 .props 檔案中設定依賴屬性,因為在 MSBuild 讀取使用者專案之前不會進行自定義。

  • 在 .targets 檔案中設定依賴屬性,因為它們將從單個專案中提取自定義項。

  • 如果需要覆蓋屬性,請在所有使用者專案自定義項生效後,在 .targets 檔案中執行此操作。使用派生屬性時務必小心;還可能需要覆蓋派生屬性。

  • 包括 .props 檔案中的專案(以屬性為條件)。在任何專案之前都要考慮所有屬性,因此可以提取使用者專案屬性自定義項,這使使用者的專案有機會RemoveUpdate匯入所引入的任何專案。

  • 定義 .targets 檔案中的目標。但是,如果 SDK 匯入了 .targets 檔案,請記住此方案使得覆蓋目標更加困難,因為預設情況下使用者的專案沒有可以覆蓋它的地方。

  • 如果可能,寧可在評估時自定義屬性,也不更改目標內的屬性。此原則可以更輕鬆地載入專案並瞭解正在執行的操作。

MSBuildProjectExtensionsPath

預設情況下,Microsoft.Common.props 匯入$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.props,Microsoft.Common.targets 匯入$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).*.targetsMSBuildProjectExtensionsPath的預設值是$(BaseIntermediateOutputPath)obj/NuGet 用此機制來引用隨包提供的生成邏輯,也就是說,在還原時,它會建立引用包內容的{project}.nuget.g.props檔案。

可以通過在 Directory.Build.props 中或者在匯入 Microsoft.Common.props 前將屬性ImportProjectExtensionProps設為false來禁用此擴充套件性機制 。

備註

禁用 MSBuildProjectExtensionsPath 匯入將阻止在 NuGet 包中提供的生成邏輯應用到你的專案。一些 NuGet 包需要生成邏輯來執行其功能,並且在禁用該功能時會呈現不可用。

.user 檔案

Microsoft.Common.CurrentVersion.targets 會匯入$(MSBuildProjectFullPath).user(如果存在),因此可以使用其他副檔名在你的專案旁建立一個檔案。對於計劃簽入原始碼管理的長期更改,最好更改專案本身,以便將來的維護人員不必瞭解此擴充套件機制。

MSBuildExtensionsPath 和 MSBuildUserExtensionsPath

警告

如果使用這些擴充套件機制,則較難獲取計算機上的可重複生成。嘗試使用可以簽入原始碼管理系統並在基本程式碼的所有開發人員之間共享的配置。

按照慣例,許多核心生成邏輯檔案

XML
$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\{TargetFileName}\ImportBefore\*.targets

會在其內容前後各匯入一次

XML
$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\{TargetFileName}\ImportAfter\*.targets

這一約定使已安裝的 SDK 可以增強常見專案型別的生成邏輯。

$(MSBuildUserExtensionsPath)中搜索相同的目錄結構,即按使用者資料夾 %LOCALAPPDATA%\Microsoft\MSBuild。放置在該資料夾中的檔案將被匯入該使用者憑據下執行的相應專案型別的所有生成。通過在模式ImportUserLocationsByWildcardBefore{ImportingFileNameWithNoDots}中設定以匯入檔案命名的屬性,可以禁用使用者擴充套件。例如,將ImportUserLocationsByWildcardBeforeMicrosoftCommonProps設定為false會阻止匯入$(MSBuildUserExtensionsPath)\$(MSBuildToolsVersion)\Imports\Microsoft.Common.props\ImportBefore\*

自定義解決方案生成

重要

以這種方式自定義解決方案生成將僅適用於帶有 MSBuild.exe 的命令列生成。它不適用於 Visual Studio 中的生成。出於此原因,不建議將自定義項置於解決方案級別。自定義一個解決方案中所有專案的更好方法是在解決方案資料夾中使用 Directory.Build.props 和 Directory.build.targets 檔案,如本文其他部分所述。

當 MSBuild 生成解決方案檔案時,它首先在內部轉換為專案檔案,然後再生成它。已生成的專案檔案在定義任何目標前匯入before.{solutionname}.sln.targets,在匯入目標後匯入after.{solutionname}.sln.targets,其中包括安裝到$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportBefore$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\SolutionFile\ImportAfter目錄的目標。

例如,可以在包含以下內容的名為 after.MyCustomizedSolution.sln.targets 的相同目錄中建立檔案,從而定義在生成 MyCustomizedSolution.sln 後寫自定義日誌訊息的新目標

XML
<Project>
 <Target Name="EmitCustomMessage" AfterTargets="Build">
   <Message Importance="High" Text="The solution has completed the Build target" />
 </Target>
</Project>

解決方案生成與專案生成分開進行,因此,此處的設定不會影響專案生成。

自定義所有 .NET 生成

維護生成伺服器時,可能需要為伺服器上的所有生成全域性配置 MSBuild 設定。原則上,可以修改全域性 Microsoft.Common.Targets 或 Microsoft.Common.Props 檔案,但有一種更好的方法。可以通過使用特定的 MSBuild 屬性並新增某些自定義.targets.props檔案,來影響特定專案型別的所有生成(如所有 C# 專案)。

若要影響通過安裝 MSBuild 或 Visual Studio 控制的所有 C# 或 Visual Basic 的生成,請建立 Custom.Before.Microsoft.Common.Targets 或 Custom.After.Microsoft.Common.Targets檔案(其目標將在 Microsoft.Common.targets 之前或之後執行),或建立 Custom.Before.Microsoft.Common.Props 或 Custom.After.Microsoft.Common.Props 檔案 (將在 Microsoft.Common.props 之前或之後進行處理其屬性)。

可以使用以下 MSBuild 屬性指定這些檔案的位置:

  • CustomBeforeMicrosoftCommonProps
  • CustomBeforeMicrosoftCommonTargets
  • CustomAfterMicrosoftCommonProps
  • CustomAfterMicrosoftCommonTargets
  • CustomBeforeMicrosoftCSharpProps
  • CustomBeforeMicrosoftVisualBasicProps
  • CustomAfterMicrosoftCSharpProps
  • CustomAfterMicrosoftVisualBasicProps
  • CustomBeforeMicrosoftCSharpTargets
  • CustomBeforeMicrosoftVisualBasicTargets
  • CustomAfterMicrosoftCSharpTargets
  • CustomAfterMicrosoftVisualBasicTargets

這些屬性的通用版本都會影響 C# 和 Visual Basic 專案。可以在 MSBuild 命令列中設定這些屬性。

cmd
msbuild /p:CustomBeforeMicrosoftCommonTargets="C:\build\config\Custom.Before.Microsoft.Common.Targets" MyProject.csproj

可以針對不同的應用場景使用最適合的方法。憑藉 Visual Studio 擴充套件性,你可以自定義生成系統,並提供安裝和管理自定義項的機制。

如果你有一個專用的生成伺服器,並且需要確保特定目標始終在該伺服器上執行的相應專案型別的所有生成上執行,則適合使用全域性自定義.targets.props檔案。如果需要讓自定義目標僅在某些條件適用時執行,可使用其他檔案位置,並(僅在需要時)通過在 MSBuild 命令列中設定相應的 MSBuild 屬性設定該檔案的路徑。

警告

每當 Visual Studio 生成匹配型別的任何專案時,只要它能在 MSBuild 資料夾中找到自定義檔案.targets.props,就能使用它們。這可能會帶來意想不到的後果,如果操作不正確,可能會導致 Visual Studio 無法在你的計算機上進行生成。

自定義 C++ 生成

對於 C++ 專案,無法按相同的方式使用前面提到的自定義 .targets 和 .props 檔案來覆蓋預設設定 。Directory.Build.props 由 Microsoft.Common.props 匯入Microsoft.Cpp.Default.props,而大多數預設設定都在 Microsoft.Cpp.props 中定義;並且,由於已經定義,許多屬性都不能使用“如果尚未定義”條件,但是對於在PropertyGroup中使用Label="Configuration"定義的特殊專案屬性,預設設定必須不同(請參閱.vcxproj 和 .props 檔案結構) 。

但是,你可以使用以下屬性來指定要在 Microsoft.Cpp.* 檔案之前/之後自動匯入的 .props 檔案 :

  • ForceImportAfterCppDefaultProps
  • ForceImportBeforeCppProps
  • ForceImportAfterCppProps
  • ForceImportBeforeCppTargets
  • ForceImportAfterCppTargets

要自定義所有 C++ 生成的預設屬性值,請建立另一個 .props 檔案(例如 MyProps.props),然後在指向該檔案的Directory.Build.props中定義ForceImportAfterCppProps屬性 :

$(MsbuildThisFileDirectory)\MyProps.props

MyProps.props 會自動匯入到 Microsoft.Cpp.props 的最末尾 。

自定義所有 C++ 生成

不建議自定義 Visual Studio 安裝,因為無法輕鬆跟蹤此類自定義項,但如果要擴充套件 Visual Studio 以自定義特定平臺的 C++ 生成,則可以為每個平臺建立.targets檔案,並將這些檔案作為 Visual Studio 擴充套件的一部分放在適用於這些平臺的相應匯入資料夾中。

Win32 平臺的.targets檔案 (Microsoft.Cpp.Win32.targets) 包含以下Import元素:

XML
<Import Project="$(VCTargetsPath)\Platforms\Win32\ImportBefore\*.targets"
        Condition="Exists('$(VCTargetsPath)\Platforms\Win32\ImportBefore')"
/>

同一檔案的末尾附近有一個相似的元素:

XML
<Import Project="$(VCTargetsPath)\Platforms\Win32\ImportAfter\*.targets"
        Condition="Exists('$(VCTargetsPath)\Platforms\Win32\ImportAfter')"
/>

*%ProgramFiles32%\MSBuild\Microsoft.Cpp\v{version}\Platforms* 中的其他目標平臺也存在類似的匯入元素。

根據平臺將.targets檔案放在適當的ImportAfter資料夾中後,MSBuild 會將檔案匯入到該平臺的每個 C++ 生成中。如果需要,可以將多個.targets檔案放在那裡。

使用 Visual Studio 擴充套件性可以進行進一步的自定義,例如定義新平臺。有關詳細資訊,請參閱C++ 專案擴充套件性

在命令列上指定自定義匯入

對於要針對某個 C++ 專案的特定生成加入的自定義.targets,請在命令列上設定ForceImportBeforeCppTargets和/或ForceImportAfterCppTargets屬性。

cmd
msbuild /p:ForceImportBeforeCppTargets="C:\build\config\Custom.Before.Microsoft.Cpp.Targets" MyCppProject.vcxproj

對於全域性設定(例如,要影響生成伺服器上某個平臺的所有 C++ 生成),有兩種方法。首先,可以使用始終設定的系統環境變數來設定這些屬性。之所以可行,是因為 MSBuild 始終讀取環境併為所有環境變數建立(或覆蓋)屬性。