.NET 工具生成引擎概述

FrankYou發表於2017-02-27

Mark Michaelis 微軟中國MSDN

過去幾年大家一直都在使用 .NET Core(有這麼久嗎?)並且都知道“生成系統”經歷了重大改變,不論是終止對 Gulp 的內建支援,還是放棄 Project.json。對於我這個專欄作家來說,這些變化一直很棘手,因為我不想我親愛的讀者花了很多時間來了解這些功能和詳情,最後卻只能使用短短几個月。

 

這就是為什麼我所有的 .NET Core 相關文章都以基於 Visual Studio .NET 4.6 的 *.CSPROJ 檔案為基礎撰寫,這些檔案引用 .NET Core 中的 NuGet 包,而非實際編譯的 .NET Core 專案。

 

本月,我很榮幸向大家宣佈 .NET Core 專案的專案檔案已經確定為(你會相信嗎) MSBuild 檔案。但這是與前幾代 Visual Studio 不同的 MSBuild 檔案,是經過改進和簡化的 MSBuild 檔案。

 

這是一個包括 Project.json 所有功能(而不陷入波形括號與尖括號的宗教戰爭),但是配備自 Visual Studio 2005 以來我們知曉(並且可能喜愛?)的傳統 MSBuild 檔案的工具支援的檔案。

 

總之,這些功能包括開源、跨平臺相容性、簡化的人類可編輯的格式以及完全現代的 .NET 工具支援(包括萬用字元檔案引用)。

 

工具支援

要清楚,MSBuild 始終支援萬用字元等功能,但是現在 Visual Studio 工具也同樣支援。換句話說,MSBuild 最大的亮點在於它被緊密整合為所有新 .NET 工具—DotNet.exe、Visual Studio 2017、Visual Studio Code 和 Visual Studio for Mac—的生成系統基礎並且支援 .NET Core 1.0 和 .NET Core 1.1 執行時。

 

.NET 工具和 MSBuild 之間強大耦合的巨大優勢在於你建立的任何 MSBuild 檔案與所有 .NET 工具相容,並且可從任何系統生成。

 

用於 MSBuild 整合的 .NET 工具通過 MSBuild API 而非命令列過程耦合。例如,執行 .NET CLI command Dotnet.exe Build 不會在內部生成 msbuild.exe 過程。但是,這不會呼叫過程中的 MSBuild API 來執行工作(MSBuild.dll 和 Microsoft.Build.* 程式集)。即便如此,不論工具如何,輸出在各平臺之間均相似,因為存在共享的日誌記錄框架,所有 .NET 工具均在其中註冊。

 

*.CSPROJ/MSBuild 檔案結構

正如我所提到的,檔案格式本身被簡化為極簡。它支援萬用字元、專案和 NuGet 包引用以及多個框架。此外,在 Visual Studio 過去建立的專案檔案中發現的專案型別 GUID 已經消失。

 

圖 1 顯示 *.CSPROJ/MSBuild 檔案示例。

圖 1 CSProj/MSBuild 檔案基本示例

<Project>  <PropertyGroup>    <TargetFramework>netcoreapp1.0</TargetFramework>  </PropertyGroup>  <ItemGroup>    <Compile Include="**\*.cs" />    <EmbeddedResource Include="**\*.resx" />  </ItemGroup>  <ItemGroup>    <PackageReference Include="Microsoft.NETCore.App">      <Version>1.0.1</Version>    </PackageReference>    <PackageReference Include="Microsoft.NET.Sdk">      <Version>1.0.0-*</Version>      <PrivateAssets>All</PrivateAssets>    </PackageReference>  </ItemGroup>  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /></Project>

讓我們來詳細地回顧下結構和功能:

簡化了的標頭: 首先要注意的是根元素僅僅是一個專案元素。甚至對名稱空間和版本屬性的需要也已經消失:

 

ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"

 

(儘管它們是通過候選釋出版本工具建立的。)  同樣地,甚至匯入常見屬性的需要也是可選的:

 

<Import Project=  "$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />

 

專案引用: 從專案檔案可以將條目新增到專案組元素:

  • NuGet 包:

<PackageReference Include="Microsoft.Extensions.Configuration">  <Version>1.1.0</Version></PackageReference>
  • 專案引用:

<ProjectReference Include="..\ClassLibrary\ClassLibrary.csproj" />
  • 程式集引用:

<Reference Include="MSBuild">  <HintPath>...</HintPath></Reference>

 

直接的程式集引用應該屬於例外情況,因為通常優先採用 NuGet 引用。

萬用字元包括: 可以通過萬用字元將編譯的程式碼檔案和資原始檔均包括在內。

 

<Compile Include="**\*.cs" /><EmbeddedResource Include="**\*.resx" /><Compile Remove="CodeTemplates\**" /><EmbeddedResource Remove="CodeTemplates\**" />

 

但是,你仍可使用刪除屬性選擇要忽略的特定檔案。(注意對萬用字元的支援很多時候被稱為通配。)

 

多重目標: 要找出你所針對的平臺,可以使用屬性組、TargetFramework 元素以及輸出型別(可選):

 

<PropertyGroup>  <TargetFramework>netcoreapp1.0</TargetFramework>  <TargetFramework>netstandard1.3</TargetFramework></PropertyGroup>

 

鑑於這些條目,每個目標的輸出將被生成到 bin\Debug or bin\Release 目錄(取決於你指定的配置)。如果目標不止一個,則生成執行會將輸出置於與目標框架名稱對應的資料夾中。

 

無專案型別 GUID: 注意不再需要包括識別專案型別的專案型別 GUID。

 

Visual Studio 2017 整合

至於 Visual Studio 2017,Microsoft 繼續提供用於編輯 CSPROJ/MSBuild 專案檔案的富 UI。

 

例如,圖 2 顯示了使用 CSPROJ 檔案列表載入的 Visual Studio 2017,它在圖 1 的基礎上稍做修改,包括 netcoreapp1.0 和 net45 的目標框架元素、Microsoft.Extensions.Configuration、Microsoft.NETCore.App 和 Microsoft.NET.Sdk 的包引用以及對 MSBuild 的程式集引用和對 SampleLib 的專案引用。                        

                                                                                   

 
圖 2 解決方案資源管理器是基於 CSProj 檔案的富 UI

 

注意在解決方案資源管理器中的依賴項樹中,每個依賴項型別—程式集、NuGet 包或專案引用—如何擁有相應的組節點。

 

此外,Visual Studio 2017 支援專案和解決方案的動態重新載入。例如,如果新檔案被載入到專案目錄—與其中一個萬用字元相匹配的目錄—Visual Studio 2017 會自動檢測更改並在解決方案資源管理器中顯示檔案。

 

同樣地,如果你從 Visual Studio 專案排除一個檔案(通過 Visual Studio 選單選項或 Visual Studio 屬性視窗),Visual Studio 將相應地自動更新專案檔案。(例如,它將新增一個 <Compile Remove="CommandLine.cs" /> 元素來避免編譯專案中的 CommandLine.cs 檔案。) 

 

此外,將自動檢測對專案檔案的編輯並將其重新載入到 Visual Studio 2017。事實上,解決方案資源管理器中的 Visual Studio 專案節點現在支援內建編輯 <Project File> 選單選項,通過此選項可在 Visual Studio 編輯視窗中開啟專案檔案,而無需首先請求解除安裝專案。

 

還支援在 Visual Studio 2017 中進行內建遷移,將專案轉換為新的 MSBuild 格式。如果你接受其提示,你的專案將自動從 Project.json/*.XPROJ 型別升級到 MSBUILD/*.CSPROJ 型別。

 

注意此類升級將破壞與 Visual Studio 2015 .NET Core 的向後相容性,所以在他人使用 Visual Studio 2015 的同時,不能讓團隊成員在 Visual Studio 2017 中處理同一個 .NET Core 專案。

 

MSBuild

有一點需要提醒你注意的是,2016 年 3 月,Microsoft 在 GitHub 上將 MSBuild 釋出為開源 (github.com/Microsoft/msbuild) 並將其貢獻給了 .NET Foundation (dotnetfoundation.org)。

 

對於到 Mac 和 Linux 的平臺可移植性,將 MSBuild 設為開源有利於回到正軌,最終允許其成為所有 .NET 工具的基礎生成引擎。

 

與之前發現的 CSPROJ\MSBuild 檔案 PackageReference 元素不同,MSBuild 版本 15 除了開源和跨平臺未引入太多其他功能。

 

實際上,通過比較命令列幫助,結果顯示選項相同。對於那些尚不熟悉它的人來說,下面列出了你應熟悉的 syntax MSBuild.exe [options] [project file] 的最常用選項:

 

/target:<target>:在專案檔案中找出要與其可能擁有的任何依賴項一起執行的生成目標(縮寫為 /t)。

 

/property:<n>=<v>: 設定或替代任何專案屬性(在專案檔案的 ProjectGroup 元素中找到)。例如,你可以使用該屬性來更改配置或輸出目錄,如在 /property:Configuration=Release;OutDir=bin\ 中(縮寫為 /p)。

 

/maxcpucount[:n]: 指定要使用的 CPU 的數量。預設情況下,msbuild 在單個 CPU 上執行(單執行緒)。如果可以同步的話,則可以通過指定併發級別來增加數量。如果在不提供值的情況下指定 /maxcpucount 選項,msbuild 將使用計算機上的處理器數量。

 

/preprocess[:file]: 通過初始化所有包含的目標來生成聚合的專案檔案。這樣在出現問題時有利於除錯。

 

@file: 提供一個(或多個)包含選項的響應檔案。此類檔案在單獨的行上擁有命令列選項(註釋要新增“#”字首)。

 

預設情況下,MSBuild 將從生成的第一個專案或解決方案匯入名為 msbuild.rsp 的檔案。響應檔案有助於找出不同的生成屬性和目標,具體取決於生成的環境(研發、測試、生產)等。

 

Dotnet.exe

大約一年前引入了 .NET 的 dotnet.exe 命令列並將其作為生成、構建和執行基於 .NET Core 的專案的跨平臺機制。正如我所提到的,dotnet.exe 已經得到更新,現在很大程度上依賴於MSBuild 作為內部引擎來在可行的情況下處理大量工作。

 

以下是對各種命令的概述:

dotnet new: 建立你的初始專案。此專案生成器開箱即用,支援 Console、Web、Lib、MSTest 和 XUnitTest 專案型別。但是將來有望提供自定義模板,從而能夠生成你自己的專案型別。(這一功能可用時,新的命令將不再依賴於 MSBuild 生成專案。)

 

dotnet restore: 讀取在專案檔案中指定的專案依賴項並下載任何缺失的 NuGet 包和其中發現的工具。專案檔案本身可以被指定為引數或從當前目錄暗示(如果當前目錄中的專案檔案不止一個,則要求指定要使用哪個)。注意因為恢復利用 MSBuild 引擎處理工作,所以 dotnet 命令允許其他 MSBuild 命令列選項。

 

dotnet build: 在專案檔案中呼叫 MSBuild 引擎執行生成目標(預設情況下)。如同恢復命令一樣,你可以將 MSBuild 引數傳遞到 dotnet 生成命令。例如,命令 dotnet build /property:configuration=Release 將觸發要輸出的 Release 生成,而不是 Debug 生成(預設情況下)。

 

同樣地,你可以使用 /target(或 /t)指定 MSBuild 目標。例如,dotnet build /t:compile 命令將執行編譯目標。

 

dotnet clean: 刪除所有生成輸出,以便執行完全生成而非增量生成。

 

dotnet migrate: 將基於 Project.json/*.XPROJ 的專案升級為 *.CSPROJ/MSBuild 格式。

 

dotnet publish: 連同任何依賴項將所有生成輸出組合到單個資料夾,從而將其暫存以部署到另一臺計算機。

 

這對不僅包含編譯輸出和依賴包,而且包含 .NET Core 執行時本身的自包含部署來說尤其有用。自包含應用程式沒有設定特定版本的 .NET 平臺已經安裝到了目標計算機上的任何前提條件。

 

dotnet run: 啟動 .NET 執行時並託管專案和/或編譯的程式集以執行你的程式。注意對於 ASP.NET 來說,編譯並不如可以託管專案本身一樣必要。

 

在執行 msbuild.exe 和 dotnet.exe 之間存在重大重疊,需要你選擇執行哪個。如果你生成的是預設 msbuild 目標,則僅可從專案目錄內執行命令“msbuild.exe”,它將為你編譯並輸出目標。等同的 dotnet.exe 命令為“otnet.exe msbuild”。 

 

另一方面,如果你執行的是“清理”目標,則命令是“msbuild.exe /t:clean”(對於 MSBuild)和“dotnet.exe clean”(對於 dotnet)。此外,兩個工具均支援副檔名。MSBuild 擁有在專案檔案本身內和通過 .NET 程式集的綜合性擴充套件性框架(請參閱bit.ly/2flUBza)。

 

同樣地,可以擴充套件 dotnet,但對此的建議基本都涉及擴充套件 MSBuild 和一些規定的步驟。

 

雖然我喜歡 dotnet.exe 這個想法,但是最後它似乎未提供太多的 MSBuild 優勢,MSBuild 不支援的除外(其中 dotnet new 和 dotnet run 可能是最重要的)。

 

最後,我認為通過 MSBuild,可以輕鬆地執行簡單的操作,同時在需要時仍可執行復雜的操作。此外,通過提供合理的預設值,即使是 MSBuild 中複雜的操作也可以被簡化。

 

最後,是 dotnet 還是 MSBuild 更可取由個人喜好決定,而時間將會告訴我們通常會為 CLI 前端選用哪個開發社群。

 

global.json:

雖然 Project.json 功能已遷移到 CSPROJ,但仍完全支援 global.json。檔案允許指定專案目錄和包目錄並識別要使用的 SDK 版本。下面是 global.json 檔案的示例:

 


{
  "projects": [ "src", "test" ],
  "packages": "packages",
  "sdk": {
    "version": "1.0.0-preview3",
    "runtime": "clr",
    "architecture": "x64"  }
}

 

有三個部分與 global.json 檔案的主要目的一致:

 

  • projects: 識別 .NET 專案所在的根目錄。專案節點對於除錯 .NET Core 原始碼來說極為關鍵。在克隆原始碼後,你可以將目錄新增到專案節點,然後 Visual Studio 將在解決方案中自動將其作為專案上載。

 

  • packages: 指示 NuGet 包資料夾的位置。

 

  • sdk: 指定要使用的執行時的版本。

 

總結

在本文中,我寬泛地概述了在 .NET 工具套件中使用 MSBuild 的所有情形。結束之前,請允許我基於我使用成千上萬行 MSBuild 處理專案的經驗,為大家提供些許建議。別落入使用 XML MSBuild 架構等鬆散型別宣告性語言編寫指令碼的圈套。這不是它的用途。

專案檔案應該是相對較薄的包裝器,能夠識別生成目標之間的順序和依賴項。如果你的 MSBuild 專案檔案太大,那麼維護起來可能會比較痛苦。請勿耽擱太久,立即將其重構為可除錯和可輕鬆通過裝置測試的 C# MSBuild 任務。

相關文章