MSBuild 如何建置專案
MSBuild 實際上如何運作? 在本文中,您將了解 MSBuild 如何處理專案檔,無論是從 Visual Studio 叫用,還是從命令列或指令碼叫用。 了解 MSBuild 的運作方式可協助您更妥善地診斷問題,並更妥善地自訂建置處理序。 本文說明建置處理序,且主要適用於所有專案類型。
完整的建置處理序包含初始啟動、評估和執行建置專案的目標和工作。 除了這些輸入之外,外部匯入也會定義建置處理序的詳細資料,包括標準匯入 (例如 Microsoft.Common.targets) 以及解決方案或專案層級的使用者可設定匯入。
啟動
您可以透過 Microsoft.Build.dll 中的 MSBuild 物件模型從 Visual Studio 叫用 MSBuild,或是直接在命令列上或指令碼中 (例如 CI 系統中) 叫用可執行檔 (MSBuild.exe
或 dotnet build
)。 不論是哪一種情況,影響建置處理序的輸入都包含專案檔 (或 Visual Studio 內部的專案物件),可能是方案檔、環境變數和命令列參數,或其物件模型對等項目。 在啟動階段期間,命令列選項或物件模型對等項目可用來設定 MSBuild 設定,例如設定記錄器。 使用 -property
或 -p
參數在命令列上設定的屬性會設定為全域屬性,這會覆寫專案檔中設定的任何值,即使稍後會讀取專案檔也一樣。
下一節是關於輸入檔,例如解決方案檔或專案檔。
方案和專案
MSBuild 執行個體可能包含一個專案,或許多專案做為解決方案的一部分。 解決方案檔不是 MSBuild XML 檔案,但 MSBuild 會加以解譯,以了解針對指定組態和平台設定建置所需的所有專案。 當 MSBuild 處理此 XML 輸入時,即稱為解決方案組建。 其有一些可延伸的點,可讓您在每個解決方案組建上執行某些項目,但是由於此組建是個別專案組建的個別執行,因此沒有任何屬性的設定或解決方案組建中的目標定義與每個專案建置相關。
您可以在自訂解決方案組建中了解如何擴充解決方案組建。
Visual Studio 組建與 MSBuild.exe 組建
當您透過 MSBuild 可執行檔,或是使用 MSBuild 物件模型啟動建置時,在 Visual Studio 中專案建置與直接叫用 MSBuild 之間會有一些顯著差異。 Visual Studio 會管理 Visual Studio 組建的專案建置順序;其只會在個別專案層級呼叫 MSBuild,且當其執行時,會設定幾個布林屬性 (BuildingInsideVisualStudio
,BuildProjectReferences
),而會大幅影響 MSBuild 的功能。 在每個專案中,執行會與透過 MSBuild 叫用時相同,但參考的專案會產生差異。 在 MSBuild 中,當需要參考的專案時,實際上會發生組建;也就是說,其會執行工作和工具,並產生輸出。 當 Visual Studio 組建找到參考的專案時,MSBuild 只會傳回所參考專案的預期輸出;其可讓 Visual Studio 控制這些其他專案的建置。 Visual Studio 會分別決定建置順序和呼叫 MSBuild (視需要),全都在 Visual Studio 的控制下。
當您使用解決方案檔叫用 MSBuild 時,就會發生另一個差異,MSBuild 會剖析解決方案檔、建立標準 XML 輸入檔、加以評估,然後以專案的形式加以執行。 會在任何專案之前執行解決方案組建。 從 Visual Studio 建置時,不會發生此情況;MSBuild 永遠不會看到解決方案檔。 因此,方案建置自訂專案 (使用 before.SolutionName.sln.targets 和 after.SolutionName.sln.targets) 僅適用於 MSBuild.exe、dotnet build
或物件模型導向的組建,而不適用於 Visual Studio 組建。
專案 SDK
MSBuild 專案檔的 SDK 功能相對較新。 在此變更之前,專案檔會明確匯入定義特定專案類型建置處理序的 .targets 和 .props 檔案。
.NET Core 專案會匯入適合這些檔案的 .NET SDK 版本。 請參閱概觀、.NET Core 專案 SDK,以及屬性的參考。
評估階段
本節討論如何處理及剖析這些輸入檔,以產生記憶體內建物件,決定要建置的項目。
評估階段的目的是根據輸入 XML 檔案和本機環境,在記憶體中建立物件結構。 評估階段包含六個階段,可處理輸入檔 (例如專案 XML 檔案) 和匯入的 XML 檔案,一般命名為 .props 或 .targets 檔案,視其主要設定屬性或定義建置目標而定。 每個階段都會建置記憶體內建物件的一部分,這些物件稍後會用於執行階段來建置專案,但在評估階段期間不會執行實際的建置動作。 在每個階段內,元素會依其出現的順序進行處理。
評估階段中的階段如下所示:
- 評估環境變數
- 評估匯入和屬性
- 評估項目定義
- 評估項目
- 評估 UsingTask 元素
- 評估目標
匯入和屬性在同一通道中按顯示順序進行評估,就像匯入就地擴展一樣。 因此,先前匯入檔案中的屬性設定在稍後匯入的檔案中可用。
這些階段的順序具有重大影響,且在自訂專案檔時務必知悉。 請參閱屬性和項目評估順序。
評估環境變數
在這個階段中,環境變數可用來設定對等的屬性。 例如,PATH 環境變數會以屬性 $(PATH)
的形式提供。 從命令列或指令碼執行時,會以正常方式使用命令環境,而從 Visual Studio 執行時,則會使用 Visual Studio 啟動時生效的環境。
評估匯入和屬性
在這個階段中,會讀取整個輸入 XML,包括專案檔和整個匯入鏈結。 MSBuild 會建立記憶體內部 XML 結構,此結構代表專案的 XML 和所有匯入的檔案。 目前,會評估及設定不在目標中的屬性。
由於 MSBuild 會在其程序中早期讀取所有 XML 輸入檔,因此在建置處理序期間對這些輸入所做的任何變更都不會影響目前的組建。
任何目標以外的屬性會以不同於目標內的屬性進行處理。 在此階段中,只會評估任何目標以外定義的屬性。
因為屬性會依屬性階段的順序進行處理,因此輸入中任何時間點的屬性都可以存取稍早出現在輸入中的屬性值,但無法存取稍後顯示的屬性。
由於屬性會在評估項目之前進行處理,因此您無法在屬性階段的任何部分期間存取任何項目的值。
評估項目定義
在這個階段中,會解譯項目定義,並建立這些定義的記憶體內部標記法。
評估項目
在目標內定義的項目會以不同於任何目標以外的項目來處理。 在這個階段中,會處理任何目標以外的項目及其相關聯的中繼資料。 依項目定義所設定的中繼資料會由項目上設定的中繼資料覆寫。 由於項目是依照其顯示的順序進行處理,因此您可以參考先前定義的項目,但不能參考稍後出現的項目。 因為項目階段是在屬性階段之後,所以若是在任何目標之外定義項目,則項目可以存取任何屬性,而不論屬性定義是否在稍後出現。
評估 UsingTask
元素
在此階段中,會讀取 UsingTask 元素,並宣告工作以供稍後在執行階段使用。
評估目標
在這個階段中,所有目標物件結構都會建立在記憶體中,以準備執行。 不會進行實際執行。
執行階段
在執行階段中,會排序及執行目標,並且執行所有工作。 但首先,目標內所定義的屬性和項目會以其出現的順序一併在單一階段中進行評估。 處理順序明顯不同於非位於目標中的屬性和項目的處理方式:先處理所有屬性,然後在個別階段中處理所有項目。 在變更屬性和項目的目標位置之後,可以觀察到目標內屬性和項目的變更。
目標組建順序
在單一專案中,會以序列方式執行目標。 主要問題是如何判斷要建置所有項目的順序,使相依性以正確的順序建置目標。
目標建置順序取決於在每個目標上使用 BeforeTargets
、DependsOnTargets
和 AfterTargets
屬性。 如果先前的目標修改了這些屬性中所參考的屬性,則在先前目標執行期間可能會影響後續目標的順序。
決定目標建置順序中描述了排序的規則。 此處理序是由包含要建置目標的堆疊結構所決定。 此工作頂端的目標會開始執行,如果其相依於其他任何項目,則這些目標會推送到堆疊頂端,並開始執行。 當目標沒有任何相依性時,其會執行以完成,並繼續其父代目標。
專案參考
MSBuild 可以採用兩個程式碼路徑:一般程式碼路徑 (如這裡所述),以及圖表選項 (如下一節所述)。
個別專案會透過 ProjectReference
項目指定與其他專案的相依性。 當堆疊頂端的專案開始建置時,其會到達 ResolveProjectReferences
目標執行所在的點,也就是在通用目標檔案中定義的標準目標。
ResolveProjectReferences
會使用 ProjectReference
項目的輸入來叫用 MSBuild 工作,以取得輸出。 ProjectReference
項目會轉換成本機項目,例如 Reference
。 目前專案的 MSBuild 執行階段會在執行階段開始處理所參考的專案時暫停 (會視需要先完成評估階段)。 僅在您開始建置相依專案之後,才會建置參考的專案,因此這會建立專案建置的樹狀結構。
Visual Studio 允許在解決方案 (.sln) 檔案中建立專案相依性。 相依性會在解決方案檔中指定,且只有在建置解決方案或在 Visual Studio 內建置時才會予以遵循。 如果您建置單一專案,則會忽略此類型的相依性。 解決方案參考會由 MSBuild 轉換成 ProjectReference
項目,之後會以相同方式處理。
圖形選項
如果您指定圖形建置參數 (-graphBuild
或 -graph
),則 ProjectReference
會變成 MSBuild 所使用的第一級概念。 MSBuild 會剖析所有專案並建構建置順序圖表,這是專案的實際相依性關係圖,然後周遊以判斷建置順序。 如同個別專案中的目標,MSBuild 可確保參考的專案是在其所相依的專案之後建置。
平行執行
如果使用多處理器支援 (-maxCpuCount
或 -m
參數),則 MSBuild 會建立節點,這些節點是使用可用 CPU 核心的 MSBuild 處理序。 每個專案都會提交至可用的節點。 在節點內,會以序列方式執行個別專案組建。
您可以設定布林變數 BuildInParallel 針對平行執行啟用工作,而此變數是根據 MSBuild 中 $(BuildInParallel)
屬性的值進行設定。 對於針對平行執行啟用的工作,工作排程器會管理節點,並將工作指派給節點。
標準匯入
Microsoft.Common.props 和 Microsoft.Common.targets 都是由 .NET 專案檔所匯入 (明確或隱含在 SDK 樣式專案中),且位於 Visual Studio 安裝中的 MSBuild\Current\bin 資料夾中。 C++ 專案有自己的匯入階層;請參閱適用於 C++ 專案的 MSBuild 內部項目。
Microsoft.Common.props 檔案會設定您可覆寫的預設值。 專案檔開頭會加以匯入 (明確或隱含)。 如此一來,專案的設定就會出現在預設值之後,使其得以覆寫。
Microsoft.Common.targets 檔案及其匯入的目標檔案會定義 .NET 專案的標準建置處理序。 其也提供可用來自訂組建的擴充點。
在實作中,Microsoft.Common.targets 是匯入 Microsoft.Common.CurrentVersion.targets 的精簡包裝函式。 此檔案包含標準屬性的設定,並且會定義對建置處理序進行定義的實際目標。 會在這裡定義 Build
目標,但實際上本身是空的。 不過,Build
目標包含 DependsOnTargets
屬性,會指定組成實際建置步驟的個別目標,也就是 BeforeBuild
、CoreBuild
和 AfterBuild
。 Build
目標定義如下:
<PropertyGroup>
<BuildDependsOn>
BeforeBuild;
CoreBuild;
AfterBuild
</BuildDependsOn>
</PropertyGroup>
<Target
Name="Build"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="$(BuildDependsOn)"
Returns="@(TargetPathWithTargetPlatformMoniker)" />
BeforeBuild
和 AfterBuild
是擴充點。 這些檔案在 Microsoft.Common.CurrentVersion.targets 檔案中是空的,但專案可以提供自己的 BeforeBuild
和 AfterBuild
目標,以及必須在主要建置處理序之前或之後執行的工作。 AfterBuild
會在 no-op 目標 (Build
) 之前執行,因為 AfterBuild
會出現在 Build
目標上的 DependsOnTargets
屬性中,但在 CoreBuild
之後發生。
CoreBuild
目標包含對建置工具的呼叫,如下所示:
<PropertyGroup>
<CoreBuildDependsOn>
BuildOnlySettings;
PrepareForBuild;
PreBuildEvent;
ResolveReferences;
PrepareResources;
ResolveKeySource;
Compile;
ExportWindowsMDFile;
UnmanagedUnregistration;
GenerateSerializationAssemblies;
CreateSatelliteAssemblies;
GenerateManifests;
GetTargetPath;
PrepareForRun;
UnmanagedRegistration;
IncrementalClean;
PostBuildEvent
</CoreBuildDependsOn>
</PropertyGroup>
<Target
Name="CoreBuild"
DependsOnTargets="$(CoreBuildDependsOn)">
<OnError ExecuteTargets="_TimeStampAfterCompile;PostBuildEvent" Condition="'$(RunPostBuildEvent)'=='Always' or '$(RunPostBuildEvent)'=='OnOutputUpdated'"/>
<OnError ExecuteTargets="_CleanRecordFileWrites"/>
</Target>
下表描述這些目標;某些目標僅適用於特定專案類型。
目標 | 描述 |
---|---|
BuildOnlySettings | 設定僅適用於實際組建,而不適用於 Visual Studio 在專案載入時叫用 MSBuild 的情況。 |
PrepareForBuild | 準備建置的必要條件 |
PreBuildEvent | 專案定義在建置前所要執行工作的擴充點 |
ResolveProjectReferences | 分析專案相依性並建置參考的專案 |
ResolveAssemblyReferences | 找出參考的組件。 |
ResolveReferences | 包含 ResolveProjectReferences 和 ResolveAssemblyReferences 以尋找所有相依性 |
PrepareResources | 處理資源檔 |
ResolveKeySource | 解析用來簽署組件的強式名稱金鑰,以及用來簽署 ClickOnce 資訊清單的憑證。 |
編譯 | 叫用編譯器 |
ExportWindowsMDFile | 從編譯器所產生的 WinMDModule 檔案產生 WinMD 檔案。 |
UnmanagedUnregistration | 從上一個組建移除/清除 COM Interop 登錄 |
GenerateSerializationAssemblies | 使用 sgen.exe 產生 XML 序列化組件。 |
CreateSatelliteAssemblies | 針對資源中的每個唯一文化特性建立一個附屬組件。 |
產生資訊清單 | 產生 ClickOnce 應用程式和部署資訊清單或原生資訊清單。 |
GetTargetPath | 傳回包含此專案的組建產品 (可執行檔或組件) 的項目,其中包含中繼資料。 |
PrepareForRun | 如果組建輸出已變更,請將組建輸出複製到最終目錄。 |
UnmanagedRegistration | 設定 COM Interop 的登錄項目 |
IncrementalClean | 移除在先前組建中產生但未在目前組建中產生的檔案。 這是在累加建置中運作 Clean 工作的必要項目。 |
PostBuildEvent | 專案定義在建置後所要執行工作的擴充點 |
上表中的許多目標可在語言特定匯入中找到,例如 Microsoft.CSharp.targets。 此檔案會定義特定於 C# .NET 專案標準建置處理序的步驟。 例如,其包含實際呼叫 C# 編譯器的 Compile
目標。
使用者可設定的匯入
除了標準匯入之外,您還可以新增數個匯入來自訂建置處理序。
- Directory.Build.props
- Directory.Build.targets
這些檔案會透過標準匯入來讀取其底下任何子資料夾中的任何專案。 這通常位於解決方案層級,可讓設定控制解決方案中的所有專案,但在檔案系統中也可能較高,最高到磁碟機的根目錄。
Directory.Build.props 檔案是由 Microsoft.Common.props 匯入,因此這裡定義的屬性可在專案檔中使用。 可在專案檔中加以重新定義,根據每個專案自訂值。 在專案檔之後,會讀取 Directory.Build.targets 檔案。 其通常包含目標,但在這裡,您也可以定義您不希望個別專案重新定義的屬性。
專案檔中的自訂
當您在 [方案總管]、[屬性] 視窗或 [專案屬性] 中進行變更時,Visual Studio 會更新專案檔,但您也可以直接編輯專案檔來進行自己的變更。
您可以設定 MSBuild 屬性來設定許多建置行為,不論是在專案本機設定的專案檔中,或如上一節所述,建立 Directory.Build.props 檔案,以全域設定專案和解決方案資料夾的屬性。 針對命令列或指令碼上的臨機操作組建,您也可以使用命令列上的 /p
選項來設定 MSBuild 特定叫用的屬性。 如需您可以設定屬性的相關資訊,請參閱一般 MSBuild 專案屬性。