依資料夾自定義組建
您可以新增 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 範例
例如,以下是 Directory.Build.props 檔案,可設定 Visual Studio 解決方案中所有項目的輸出目錄。 每個項目的輸出會放在它自己的項目名稱之下。 在此範例中,Directory.Build.props 檔案位於方案資料夾中,子資料夾中有許多專案。
$(MSBuildProjectName)
屬性會提供每個項目的名稱。 因為 Directory.Build.props 檔案會在每個專案執行自己的組建期間載入,所以它會評估為解決方案中每個個別專案的正確值。
清除解決方案以移除任何舊的輸出檔案。
msbuild /t:Clean SolutionName.sln
在存放庫的根目錄中建立名為 Directory.Build.props的新檔案。
將下列 XML 新增至 檔案。
<Project> <PropertyGroup> <OutDir>C:\output\$(MSBuildProjectName)</OutDir> </PropertyGroup> </Project>
注意
$(OutDir)
屬性是輸出的絕對路徑,而且使用它會略過針對通常用於 .NET 專案的組態、目標架構或運行時間建立子資料夾。 如果您想要在自定義輸出路徑下建立一般子資料夾,請嘗試改用 屬性BaseOutputPath
。執行 MSBuild。 您專案現有對 Microsoft.Common.props 和 Microsoft.Common.targets 的匯入會找到並匯入 Directory.Build.props 檔案,然後,新輸出資料夾將應用於該資料夾下的所有專案。
搜尋範圍
搜尋 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
檔案之後,Directory.Build.targets 會從 Microsoft.Common.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)設置通用屬性,以及為 測試 專案 (2-test)設置通用屬性。
若要讓 MSBuild 正確地將 "inner" 檔案 (2-src 和 2-test) 合併到 "outer" 檔案 (1) 中,您必須考慮一旦 MSBuild 找到 Directory.Build.props 檔案,它就會停止進一步掃描。 若要繼續掃描並合併至外部檔案,請將此程式代碼放入兩個內部檔案:
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)../'))" />
MSBuild 的一般方法摘要如下:
- 針對任何指定的專案,MSBuild 會在方案結構中從底向上尋找第一個 Directory.Build.props,將其與預設值合併,然後停止繼續掃描。
- 如果您想要找到並合併多個層級,請將
<Import...>
(先前顯示)的“outer”檔案從“inner”檔案中分離出來。 - 如果「上層」檔案本身並未匯入更上層的內容,則掃描會在此停止。
或者更簡單地說:第一個不匯入任何內容的 Directory.Build.props 就是 MSBuild 停止的地方。
若要更明確地控制匯入程式,請使用 屬性 $(DirectoryBuildPropsPath)
、$(ImportDirectoryBuildProps)
、$(DirectoryBuildTargetsPath)
和 $(ImportDirectoryBuildTargets)
。 屬性 $(DirectoryBuildPropsPath)
指定要使用之 Directory.Build.props
檔案的路徑;同樣地,$(DirectoryBuildTargetsPath)
會指定 Directory.Build.targets
檔案的路徑。
布爾值屬性 $(ImportDirectoryBuildProps)
和 $(ImportDirectoryBuildTargets)
預設會設定為 true
,因此 MSBuild 通常會搜尋這些檔案,但您可以將它們設定為 false
,以防止 MSBuild 匯入這些檔案。
例
此範例示範如何使用前置處理的輸出來判斷要設定屬性的位置。
為了協助您分析所要設定之特定屬性的使用方式,您可以使用 /preprocess
或 /pp
自變數執行 MSBuild。 輸出文本是所有導入的結果,包括系統導入,例如隱式導入的 Microsoft.Common.props,以及您自己導入的任何項目。 透過此輸出,您可以看到屬性需設定的位置與其值的使用位置的相對關係。
例如,假設您有簡單的 .NET Core 或 .NET 5 或更新版本的主控台應用程式專案,而且您想要自定義中繼輸出資料夾,通常 obj
。 用來指定此路徑的屬性是 BaseIntermediateOutput
。 如果您嘗試將此專案屬性放入您的專案檔案中的 PropertyGroup
元素,以及像 TargetFramework
這樣已經在那裡設定的其他屬性,當您建置專案時,會發現這個屬性沒有生效。 如果您使用 [/pp
] 選項執行 MSBuild,並搜尋 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>
...
相關內容
- 自訂設置。