MSBuild 如何建置專案
MSBuild 實際上如何運作? 在本文中,您將瞭解 MSBuild 如何處理專案檔,無論是從 Visual Studio 叫用,還是從命令行或腳本叫用。 瞭解 MSBuild 的運作方式可協助您更妥善地診斷問題,並更妥善地自定義建置程式。 本文說明建置程式,而且主要適用於所有項目類型。
完整的建置程式包含 初始啟動、評估,以及建置專案的目標和工作 執行。 除了這些輸入之外,外部匯入也會定義建置程式的詳細數據,包括 標準匯入,例如 Microsoft.Common.targets,以及在方案或專案層級 用戶可設定的匯入。
新創公司
MSBuild 可以透過 Microsoft.Build.dll中的 MSBuild 物件模型從 Visual Studio 叫用,或直接在命令行上叫用可執行檔 (MSBuild.exe
或 dotnet build
),或在腳本中,例如 CI 系統中。 不論是哪種情況,會影響建置程序的輸入包括專案檔案(或 Visual Studio 內部的專案物件)、可能的方案檔案、環境變數,以及命令列開關或其對應的物件模型。 在啟動階段期間,可以使用命令列選項或物件模型等效項目來設定 MSBuild 設定,例如設定記錄器。 使用 -property
或 -p
參數在命令列上設定的屬性將被設定為全域屬性,這會覆寫專案檔中設定的任何值,即使專案檔稍後才被讀取也是如此。
下一節是關於輸入檔,例如方案檔或項目檔。
方案和專案
MSBuild 實例可能包含一個專案,或許多專案作為解決方案的一部分。 方案檔不是 MSBuild XML 檔案,但 MSBuild 會解譯它,以瞭解針對指定組態和平臺設定建置所需的所有專案。 當 MSBuild 處理此 XML 輸入時,即稱為解決方案組建。 它有一些可延伸的點,可讓您在每個方案組建上執行某些專案,但是由於此組建是個別專案組建的個別執行,因此方案組建中沒有任何屬性或目標定義的設定與每個專案建置相關。
您可以瞭解如何擴充方案組建,請參考 自訂方案組建。
Visual Studio 組建與 MSBuild.exe 組建
在 Visual Studio 中建置專案與直接透過 MSBuild 可執行檔或使用 MSBuild 物件模型進行建置之間,存在一些顯著的差異。 Visual Studio 會管理 Visual Studio 組建的專案建置順序;它只會在個別專案層級呼叫 MSBuild,而且當它執行時,會設定幾個布爾屬性 (BuildingInsideVisualStudio
, BuildProjectReferences
) 會大幅影響 MSBuild 的功能。 在每個專案中,執行會與透過 MSBuild 叫用時相同,但參考的專案會產生差異。 在 MSBuild 中,當需要參考的專案時,實際上會發生組建;也就是說,它會執行工作和工具,併產生輸出。 當 Visual Studio 組建找到參考的專案時,MSBuild 只會傳回所參考專案的預期輸出;它可讓Visual Studio控制這些其他專案的建置。 Visual Studio 會分別決定建置順序和呼叫 MSBuild(視需要),完全在 Visual Studio 的控制下。
當 MSBuild 使用方案檔叫用 MSBuild 時,就會發生另一個差異,MSBuild 會剖析方案檔、建立標準 XML 輸入檔、評估它,然後以專案的形式執行它。 方案建置會在任何專案之前執行。 從 Visual Studio 建置時,不會發生此情況;MSBuild 永遠不會看到方案檔。 因此,解決方案建置自定義專案(先前使用 。SolutionName.sln.targets 和之後 。SolutionName.sln.targets)僅適用於 MSBuild.exe、dotnet build
或物件模型驅動組建,而不是Visual Studio組建。
專案軟體開發工具包
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)
屬性值所設定。 針對啟用平行執行的工作,工作排程器會管理節點,並將工作指派給節點。
請參閱 使用 MSBuild 平行建置多個專案
標準匯入
Microsoft.Common.props 和 Microsoft.Common.targets 都是由 .NET 專案檔匯入的,且位於 Visual Studio 安裝中的 MSBuild\Current\bin 資料夾中。 C++專案有自己的匯入階層;請參閱 MSBuild Internals for C++ 項目。
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 指令清單的憑證。 |
編譯 | 叫用編譯程式 |
匯出Windows MD檔案 | 從編譯程式所產生的 WinMDModule 檔案產生 WinMD 檔案。 |
非管理式註銷 | 從先前組建中移除/清除 COM Interop 登錄項目 |
生成序列化程序集 | 使用 sgen.exe產生 XML 序列化元件。 |
CreateSatelliteAssemblies | 為資源中的每個獨特文化建立一個衛星組件。 |
產生清單 | 產生 ClickOnce 應用程式和部署指令清單或原生指令清單。 |
取得目標路徑 | 返回一個包含此專案的建置產品(可執行檔或組件)及詮釋資料的項目。 |
PrepareForRun | 如果組建輸出已變更,請將組建輸出複製到最終目錄。 |
未管理的註冊 | 設定 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 調用的屬性。 如需您可以設定的屬性相關信息,請參閱 Common MSBuild 專案屬性。