Visual Studio 建置程式是由匯入至項目檔的一系列 MSBuild .targets
檔案所定義。 如果您使用 SDK 來作為 Visual Studio 專案的一部分,則這些匯入通常是隱含的。 您可以擴充其中一個已匯入的檔案,Microsoft.Common.targets,讓您在建置過程中的多個步驟執行自訂工作。 本文說明可用來擴充Visual Studio建置程式的三種方法:
建立自定義目標,並指定何時應該使用
BeforeTargets
和AfterTargets
屬性執行。DependsOn
覆寫通用目標中定義的屬性。覆寫通用目標中定義的特定預先定義目標(Microsoft.Common.targets 或其匯入的檔案)。
AfterTargets 和 BeforeTargets
您可以在 AfterTargets
自訂目標上使用 和 BeforeTargets
屬性來指定何時應該執行。
下列範例示範如何使用 AfterTargets
屬性來新增自定義目標,以使用輸出檔案執行某些動作。 在此情況下,它會將輸出檔案複製到新的資料夾 CustomOutput。 此範例還示範如何透過使用CustomClean
屬性清理由BeforeTargets
目標創建的檔案,並指定自定義清除作業在CoreClean
目標之前運行。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<_OutputCopyLocation>$(OutputPath)..\..\CustomOutput\</_OutputCopyLocation>
</PropertyGroup>
<Target Name="CustomAfterBuild" AfterTargets="Build">
<ItemGroup>
<_FilesToCopy Include="$(OutputPath)**\*"/>
</ItemGroup>
<Message Text="_FilesToCopy: @(_FilesToCopy)" Importance="high"/>
<Message Text="DestFiles:
@(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
<Copy SourceFiles="@(_FilesToCopy)"
DestinationFiles=
"@(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>
<Target Name="CustomClean" BeforeTargets="CoreClean">
<Message Text="Inside Custom Clean" Importance="high"/>
<ItemGroup>
<_CustomFilesToDelete Include="$(_OutputCopyLocation)**\*"/>
</ItemGroup>
<Delete Files='@(_CustomFilesToDelete)'/>
</Target>
</Project>
警告
請務必使用不同於預先定義目標的名稱(例如,此處自定義的建置目標為CustomAfterBuild
,而不是AfterBuild
),因為這些預先定義的目標會被 SDK 匯入覆蓋,並且 SDK 也會定義它們。 如需預先定義的目標清單,請參閱本文結尾的 數據表 。
擴充 DependsOn 屬性
擴充建置程式的另一種方式是使用 DependsOn
屬性 (例如 BuildDependsOn
, ),指定應在標準目標之前執行的目標。
建議使用這個方法,而非覆寫預先定義的目標,下一節會進一步討論。 覆寫預先定義的目標是仍然支援的較舊方法,但是,因為 MSBuild 會循序評估目標的定義,因此無法防止另一個專案匯入專案覆寫您已經覆寫的目標。 因此,例如,在匯入所有其他項目之後,專案檔中定義的最後 AfterBuild
一個目標將會是建置期間所使用的目標。
您可以通過覆寫DependsOn
屬性中使用的DependsOnTargets
屬性來防止目標的非預期覆寫,這些屬性遍佈於各個通用目標。 例如, Build
目標包含 DependsOnTargets
屬性的 "$(BuildDependsOn)"
值。 考慮:
<Target Name="Build" DependsOnTargets="$(BuildDependsOn)"/>
這個 XML 片段表示,必須先執行屬性中指定的所有目標 Build
,才能執行目標 BuildDependsOn
。 屬性 BuildDependsOn
定義為:
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);
BeforeBuild;
CoreBuild;
AfterBuild
</BuildDependsOn>
</PropertyGroup>
您只需在專案檔結尾宣告一個名為 BuildDependsOn
的屬性,即可覆寫這個屬性值。 在 SDK 樣式專案中,這表示您必須使用明確的匯入。 請參閱 隱含和明確匯入,讓您可以在上次匯入之後放置 DependsOn
屬性。 藉由在新的 屬性中包含上一 BuildDependsOn
個屬性,您可以將新的目標新增至目標清單的開頭和結尾。 例如:
<PropertyGroup>
<BuildDependsOn>
MyCustomTarget1;
$(BuildDependsOn);
MyCustomTarget2
</BuildDependsOn>
</PropertyGroup>
<Target Name="MyCustomTarget1">
<Message Text="Running MyCustomTarget1..."/>
</Target>
<Target Name="MyCustomTarget2">
<Message Text="Running MyCustomTarget2..."/>
</Target>
匯入項目檔的專案可以進一步擴充這些屬性,而不覆寫您所做的自定義。
覆寫 DependsOn 屬性
識別您要覆寫之通用目標中的預定義的
DependsOn
屬性。 請參閱下表以查看常常被覆蓋的DependsOn
屬性清單。在專案檔結尾定義屬性或多個屬性的另一個實例。 在新的 屬性中包含原始屬性,例如
$(BuildDependsOn)
。在屬性定義之前或之後定義您的自訂目標。
建置項目檔。
常被覆蓋的 DependsOn 屬性
屬性名稱 | 已新增的目標會在此點之前執行: |
---|---|
BuildDependsOn |
主要建置入口。 如果您希望在整個建置過程之前或之後插入自定義目標,請覆蓋此屬性。 |
RebuildDependsOn |
Rebuild
|
RunDependsOn |
最終建置結果的執行(如果是.EXE) |
CompileDependsOn |
編譯 (Compile 目標)。 如果您想要在編譯步驟之前或之後插入自定義流程,請重寫此屬性。 |
CreateSatelliteAssembliesDependsOn |
建立衛星組件 |
CleanDependsOn |
Clean 目標(刪除所有中繼和最終組建輸出)。 如果您要清除自訂建置程序的輸出,請覆蓋此屬性。 |
PostBuildEventDependsOn |
目標PostBuildEvent |
PublishBuildDependsOn |
組建發佈 |
ResolveAssemblyReferencesDependsOn |
ResolveAssemblyReferences 目標(尋找給定依賴關係之依賴關係的傳遞閉包)。 請參閱 ResolveAssemblyReference 。 |
範例:BuildDependsOn 和 CleanDependsOn
下列範例與 BeforeTargets
和 AfterTargets
範例類似,但示範如何達到類似的功能。 它使用 BuildDependsOn
來擴充組建,新增您自己的工作 CustomAfterBuild
,以在建置後複製輸出檔案,並使用 CustomClean
來新增對應的 CleanDependsOn
工作。
在此範例中,這是 SDK 樣式專案。 如本文稍早的 SDK 樣式專案附注所述,您必須使用手動匯入方法,而不是 Sdk
Visual Studio 在產生項目檔時所使用的屬性。
<Project>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"/>
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk"/>
<PropertyGroup>
<BuildDependsOn>
$(BuildDependsOn);CustomAfterBuild
</BuildDependsOn>
<CleanDependsOn>
$(CleanDependsOn);CustomClean
</CleanDependsOn>
<_OutputCopyLocation>$(OutputPath)..\..\CustomOutput\</_OutputCopyLocation>
</PropertyGroup>
<Target Name="CustomAfterBuild">
<ItemGroup>
<_FilesToCopy Include="$(OutputPath)**\*"/>
</ItemGroup>
<Message Importance="high" Text="_FilesToCopy: @(_FilesToCopy)"/>
<Message Text="DestFiles:
@(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
<Copy SourceFiles="@(_FilesToCopy)"
DestinationFiles="@(_FilesToCopy->'$(_OutputCopyLocation)%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>
<Target Name="CustomClean">
<Message Importance="high" Text="Inside Custom Clean"/>
<ItemGroup>
<_CustomFilesToDelete Include="$(_OutputCopyLocation)**\*"/>
</ItemGroup>
<Delete Files="@(_CustomFilesToDelete)"/>
</Target>
</Project>
元素的順序很重要。 和 BuildDependsOn
CleanDependsOn
元素必須出現在匯入標準 SDK 目標檔案之後。
覆蓋預設的目標
通用 .targets
檔案包含一組預先定義的空白目標,這些目標會在建置程式中的一些主要目標前後呼叫。 例如,MSBuild 在 BeforeBuild
目標之前呼叫 CoreBuild
主要目標,並在 AfterBuild
目標之後呼叫 CoreBuild
目標。 根據預設,通用目標中的空白目標不會執行任何動作,但您可以定義項目檔中所需的目標來覆寫其默認行為。 本文稍早所述的方法是慣用的方法,但您可能會遇到使用此方法的較舊程序代碼。
如果您的專案使用 SDK (例如 Microsoft.Net.Sdk
),您必須從隱含匯入變更為明確匯入,如 明確和隱含匯入中所述。
覆寫預先定義的目標
如果專案使用
Sdk
屬性,請將它變更為明確的匯入語法。 請參閱 明確和隱含匯入。在您想要覆寫的常見目標中識別預先定義的目標。 如需您可以安全地覆寫的目標完整清單,請參閱下表。
在你的項目檔末尾定義目標或目標,應在
</Project>
標籤前面,並在顯式導入 SDK 之後。 例如:<Project> <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" /> ... <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" /> <Target Name="BeforeBuild"> <!-- Insert tasks to run before build here --> </Target> <Target Name="AfterBuild"> <!-- Insert tasks to run after build here --> </Target> </Project>
請注意,頂層
Sdk
元素上的Project
屬性已被移除。建置項目檔。
預先定義目標的數據表
下表顯示您可以在通用目標中覆寫的所有目標。
目標名稱 | 說明 |
---|---|
BeforeCompile 、AfterCompile |
在其中一個目標中插入的工作會在核心編譯完成之前或之後執行。 大部分的自定義是在這兩個目標之一中進行。 |
BeforeBuild 、AfterBuild |
插入其中一個目標的工作將會在組建中其他所有專案之前或之後執行。
注意:BeforeBuild 和 AfterBuild 目標已在大部分項目檔結尾的批注中定義,可讓您輕鬆地將建置前和建置後事件新增至項目檔。 |
BeforeRebuild 、AfterRebuild |
在其中一個目標中插入的工作會在叫用核心重建功能之前或之後執行。
Microsoft.Common.targets 中的目標執行順序為:BeforeRebuild 、、Clean Build 、 和 。AfterRebuild |
BeforeClean 、AfterClean |
在其中一個目標中插入的工作會在呼叫核心清理功能之前或之後執行。 |
BeforePublish 、AfterPublish |
在其中一個目標中插入的工作會在叫用核心發佈功能之前或之後執行。 |
BeforeResolveReferences 、AfterResolveReferences |
插入這些目標之一的任務在解析組件引用之前或之後執行。 |
BeforeResGen 、AfterResGen |
在其中一個目標中插入的工作會在產生資源之前或之後執行。 |
建置系統和 .NET SDK 中有更多目標,請參閱 MSBuild 目標 - SDK 和預設建置目標。
自定義目標的最佳做法
屬性 DependsOnTargets
和 BeforeTargets
可以同時指定目標必須在另一個目標之前執行,但在不同的案例中都需要它們。 它們在指定相依性需求的目標上有所不同。 您只能控制自己的目標,且無法安全地修改系統目標或其他匯入的目標,讓條件約束您所選擇的方法。
撰寫自定義目標時,請遵循這些一般指導方針,以確保您的目標是以預定順序執行。
DependsOnTargets
使用屬性來指定目標執行之前必須完成的目標。 對於您控制的目標鏈結,每個目標都可以在 中DependsOnTargets
指定鏈結的上一個成員。針對您不控制但必須事先執行的任何目標,請使用
BeforeTargets
(例如BeforeTargets="PrepareForBuild"
,用於需要在構建中提前執行的目標)。針對
AfterTargets
您不控制的任何目標使用,以確保您所需的輸出可供使用。 例如,針對將修改參考清單的專案指定AfterTargets="ResolveReferences"
。您可以使用這些組合。 例如:
DependsOnTargets="GenerateAssemblyInfo" BeforeTargets="BeforeCompile"
。
明確和隱含匯入
Visual Studio 產生的專案通常會在專案元素上使用Sdk
屬性。 這些類型的專案稱為 SDK 樣式專案。 請參閱 使用 MSBuild 專案 SDK。 以下為範例:
<Project Sdk="Microsoft.Net.Sdk">
當您的專案使用 Sdk
屬性時,會隱含新增兩個匯入,一個是在專案檔開頭,另一個是在結尾。
隱含匯入等同於在 Project
元素後的專案檔第一行使用匯入語句,如下所示:
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
和下列 import 語句作為項目檔中的最後一行:
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
此語法稱為 明確 SDK 匯入。 當您使用此明確的語法時,應該省略 Sdk
項目元素上的屬性。
隱含 SDK 匯入相當於匯入舊版項目檔中一般建構的特定「通用」 .props
或 .targets
檔案,例如:
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
和
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
任何這類舊參考都應改為本節先前所示的明確 SDK 語法。
使用明確的 SDK 語法表示您可以在第一次匯入之前或最終的 SDK 匯入之後新增自己的程式代碼。 這表示您可以在第一次匯入前先設定屬性,這些屬性會在匯入的 .props
檔案中生效。同時,您可以在最終匯入後覆寫在其中一個 SDK .targets
檔案中定義的目標。 使用此方法時,您可以覆寫 BeforeBuild
或 AfterBuild
,如下所述。
後續步驟
使用 MSBuild,您可以完成更多自定義組建的操作。 請參閱 自訂組建。