從最原始的編譯器,逐漸到 Shell 命令組合、Make工具,到現在的針對性 Build 工具,Rake、Ant、MSBuild,甚至於 PowerShell 這樣的工具;都為我們 軟件開發以及系統管理做出了貢獻,我甚至不能想象幾十年前使用編譯器生成程 序的復雜步驟(其實就是把 makefile 拆開,相當恐怖)。
探討 Make、Ant 以及 MSBuild 無太多意義,甚至從純技術和擴展性上來說, 我認為 MSBuild 弱於 NAnt,但是軟件並不是這麼簡單,之所以深研 MSBuild, M$ 作為後盾讓人信任的無奈。
本文主要講述 MSBuild 的基本概念,以及如何使用它輔助開發。
首先我們來看一個最簡單的 Build:
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Welcome>Hello MSBuild! </Welcome>
</Property>
<Target Name="Build">
<Message Text="$(Hello)" />
</Target>
</Project>
假設我們把這個文件保存到 D:\build.proj ,然後在命令行切換工作目錄到 D:\,運行 msbuild.exe(msbuild 默認直接運行當前目錄下的 *.sln 或 *.*proj 文件),則命令行將會顯示詳細的 build 信息,當然也包括我們的消息 'Hello MSBuild!' 。
概念
Project
Project 代表一個 Build,有屬性 ToolsVersion、DefaultTargets、 InitialTargets,分別為 依賴的 MSBuild 版本、默認 Build 目標,初始化目標 。
Property
Property 是 MSBuild 中的基本單元,可以理解為變量,我們可以在大多數地 方使用它作為 Task 的參數以完成我們預期的目標。
下面的語句示范如何聲明 Property:
<PropertyGroup>
<Name Condition="$(Name) == ''">Kate</Name>
<WorkPath>D:\Workspace</WorkPath>
</PropertyGroup>
這裡定義了兩個 Property,我們可以使用 $(Name) 和 $(WorkPath) 來引用 它們,就和前面的范例一樣。
Property 可以使用 MSBuild 的 /p 參數定義,這裡就使用 Condition 屬性 判斷是否存在外部 Name,如果不存在則使用自定義的 "Kate"。
Target
Target 是 Build 的基本單元,也對應 MSBuild 的 /t 參數,可用參數有 DependOnTargets,代表依賴的目標。
Task
Task 即任務,Build 的過程就是若干 Task 的執行。上面的 <Message Text="..." /> 就是 MSBuild 內置的一個 Task,Text 則是參數。
Task 可以使用 Condition 屬性。
通常情況下,MSBuild 自帶的 Task 並不夠用,有以下 MSBuild 擴展,可以 幾乎不需要自己寫擴展:
* MSBuild Community Tasks
* SDC Tasks Library
* MSBuild Extension Pack
Item
我們可以簡單的把 Item 理解為 .Net 中的 Dictionary<string,Dictionary<string,string>> 類型,內層的字 典被稱作元數據表。
<ItemGroup>
<Table Include="A;B;C;D" />
<Game Include="StarCraft" />
<Game Include="WarCraft" />
<Game Include="CoderCraft" />
<Program Include="MyApp">
<Developer>Zealic</Developer>
<Timestamp>2009-01-01T11:22:33</Timestamp>
</Program>
</ItemGroup>
為方便理解,我們可以用 C# 來表述上述內容。
var Table = new Dictionary<string,Dictionary<string,string>
{
{"A", new Dictionary<string,string>},
{"B", new Dictionary<string,string>},
{"C", new Dictionary<string,string>},
{"D", new Dictionary<string,string>}
};
var Game = new Dictionary<string,Dictionary<string,string>
{
{"StarCraft", new Dictionary<string,string>},
{"WarCraft", new Dictionary<string,string>},
{"CoderCraft", new Dictionary<string,string>}
}
var Program = new Dictionary<string,Dictionary<string,string>
{
{"MyApp", new Dictionary<string,string>
{
"Developer", "Zealic",
"Timestamp", "2009-01- 01T11:22:33"
}
}
};
和使用 Property 不同,Item 有如下用法:
* @(Table) : 直接傳遞 Item 或展開為 A;B;C;D (視 Task 參數類型而定)。
* @(Table, '+') : 以指定的分隔符展開 Item,結果為 A+B+C+D。
* @(Table -> '%(Identity).dll') : 轉換 Item 為 A.dll;B.dll;C.dll;D.dll
* %(Program.Developer) : 引用 Program Item 的元數據 "Developer";此 外,以這種方式使用 Item 都會導致循環所有 Item 成員。比如 <Message Text="%(Game.Identity)"/>,會導致三次 Task 調用,分別輸出 StarCraft, WarCraft 以及 CoderCraft;Identity 代表 Item 的名稱,有關 Item 的更多預 定義元數據,請參考 MSDN。
Item 可以使用 Condition 屬性。
結語
可能有人會問,既然 MSBuild 和批處理能做的事都差不多,為什麼不直接使 用批處理呢?
有以下幾點原因:
* 與 .Net Framework 緊密集成。
* 易於擴展:你可以使用任何 CLR 上的語言編寫 MSBuild Task 進行擴展。
* 高效率:多個解決方案/項目可以在一個解決方案中編譯,甚至支持多核下 的並行編譯。
* 利於診斷:MSBuild 可擴展的日志系統使得 build 過程幾乎和白盒沒有區 別,你可以看到 build 的每一步細節。這也是 VS2003 轉到 VS2005 之間最大的 轉變 - IDE 對編譯過程的感知從黑盒轉變到白盒。
此外,利用眾多第三方 Task,MSBuild 可以完成諸如持續集成、管理服務器 、自動化部署等任務,解放我們的時間,讓我們可以有更多的時間 Coding 或 偷懶 -_-。
了解到這些內容以後,聰明的讀者,你是否已經打開一個正在開發的項目的 *.csproj 文件,用文本編輯器看看 MSBuild 究竟有什麼魔法呢?
PS:文章寫的比較簡單,不夠全面,有問題請給我妹兒 ,我將詳細為你解答。或訪問我的 Twitter,裡面有不少小技巧。