MSBuild入門
2024-07-21 02:17:15
供稿:網友
 
本文來源于網頁設計愛好者web開發社區http://www.html.org.cn收集整理,歡迎訪問。如果你和我一樣一直都在用nant管理生成過程的話,那么你一定會高度關注msbuild。原因很簡單,因為它屬于微軟,你可以不喜歡它,但你一定要學會用它。
 
在熬過了幾個晚上以后,我終于讓自己適應了msbuild的語法。這可真不容易,特別是當自己已經習慣了nant的小寫規范之后。不過這不成問題,因為隨著自己對msbuild的理解一點點加深,自己還真的喜歡上它了。
 
好吧,下面就讓我來簡單地介紹一下我在學習msbuild使用過程中的一點經驗。如果你還在msbuild的大門外徘徊,那么希望這篇東西能帶你進入那扇門。
 
準備工作
首先要提到的是有關如何使用msbuild的一些重要資源。它們是:
 
1. alex kipman的msdntv show:
http://msdn.microsoft.com/msdntv/episode.aspx?xml=episodes/en/20040122vsnetak/manifest.xml
2. alex kipman和rajeev goel在pdc2003上的演講:
http://microsoft.sitestream.com/pdc2003/tls/tls347.htm
上面這兩項出自msbuild開發組的alex kipman,從理論上說他應該是了解msbuild的第一人,他給出的幾個演示的確給了我非常大的幫助。(不過我非常不喜歡他的聲音,又尖又細。)
 
3. msbuild doc
http://msdn.microsoft.com/longhorn/toolsamp/default.aspx
這是最重要的,其中包括alex kipman主筆的五份重要文檔:msbuildfileformat、msbuildwalkthrough、msbuildtasks、howtowriteatask以及msbuildcommandline。這可能是目前情況下外界能獲得的有關msbuild最詳細的文檔。
 
 
demo
好了,一切準備工作就緒,讓我們以一個簡單的示例開始吧。
 
首先寫一個簡單的c# console程序(你也可以把它改成vb.net):
 
// hellomsbuild.cs
using system;
 
class hellomsbuild
 {
 public static void main()
 {
 console.writeline("hello msbuild!");
 }
 }
 
下面我們就要寫一個.csproj文件來控制整個生成過程。值得注意的是,如果在調用msbuild.exe時沒有指定具體的項目文件,msbuild引擎會在當前目錄下查找一個名為*.*proj的項目文件。如果你在同一目錄中寫了多個這樣的項目文件,那么需要手動指定msbuild.exe的目標文件,方法是:
 
msbuild a.csproj
 
否則msbuild會提示出錯,要求你手動指定目標項目文件。
 
以下是項目文件:
 
<!-- build.csproj -->
<project defaulttargets="run">
 
 <property bin="bin" />
 <property outputassembly="hellomsbuild" />
 
 <item type="source" include="hellomsbuild.cs" />
 
 <target name="build">
 <task name="makedir"
 directories="$(bin)"
 condition="!exists('$(bin)')" />
 <task name="csc"
 sources="@(source)"
 targettype="exe"
 outputassembly="$(bin)/$(outputassembly).exe" />
 </target>
 
 <target name="run" dependsontargets="build">
 <task name="exec"
 command="$(bin)/$(outputassembly).exe" />
 </target>
</project>
 
如果你此前沒有過nant的開發經驗,那么上面這些東西肯定看起來挺嚇人。這個時候最好的辦法是打開那篇msbuildfileformat,對照上面代碼查找相應的項目元素的含義。下面我對其中重要的項目元素進行一下解釋。
 
1. project元素。這是每一個項目文件的最外層元素,它表示了一個項目的范圍。如果缺少了這一元素,msbuild會報錯稱target元素無法識別或不被支持。
 
project元素擁有多個屬性,其中最常用到的是defaulttargets屬性。我們都知道,在一個項目的生成過程中可能需要完成幾項不同的任務(比如編譯、單元測試、check-in到源代碼控制服務器中等),其中每一項任務都可以用target來表示。對于擁有多個target的項目,你可以通過設置project的defaulttargets(注意是復數)屬性來指定需要運行哪(幾)個target,比如:
 
<project defaulttargets=”build” >
...
 
或者:
 
<project defaulttargets=”build;test;run” >
...
 
如果沒有這個設置,msbuild將只運行排在最前面的那個target。
 
2. property元素。在項目中你肯定需要經常訪問一些信息,例如需要創建的路徑名、最終生成的程序集名稱等。這些信息你最好別hard code進項目中,除非你一次寫過之后永不更改。這時property就能派上用場了。你把上面提到的那些信息以name/value的形式添加進property,隨后就可以以$(propertyname)的形式訪問。這樣你就無須為了改動一個文件名稱而讓整個項目文件傷筋動骨了。比如上面代碼中的bin就是將要創建的路徑名稱,而assemblyname則是最終要生成的程序集名稱。這些屬性的名稱不是固定的,你完全可以按自己的習慣來進行命名。在使用時,你需要把屬性名稱放在”$(“和”)”對內(不包括引號),以表示這里將被替換成一個property元素的值。
 
另外,如果property元素數量比較多,你還可以把它們分門別類地放在不同的propertygroup里,以提高代碼的可閱讀性。這對property本身沒有任何影響。比如:
 
<propertygroup>
 <property ... />
 <property ... />
</propertygroup>
 
3. item元素。在整個項目文件中你肯定要提供一些可被引用的輸入性資源(inputs)信息,比如源代碼文件、引用的程序集名稱、需要嵌入的圖標資源等。它們應該被放在item里,以便隨時引用。語法是:
 
<item type=”thetype” include=”nameorpath” />
 
其中type屬性可以被看作是資源的類別名稱,比如對于.cs源文件,你可以把它們的type都設置為source,對于引用的程序集把type都設置為reference,這樣在隨后想引用這一類別的資源時只要引用這個type就可以了,方法是@(typename)。可千萬別和property的引用方法弄混了。
 
既然type是資源的類名,那么include就是具體的資源名稱了,比如在上面的示例代碼中,include引用的就是c#源代碼文件的名稱。你也可以用使用通配符*來擴大引用范圍。比如下面這行代碼就指定了當前目錄下的所有c#文件都可以通過@(source)來引用:
 
<item type=”source” include=”*.cs” />
 
另外,你也可以通過與propertygroup類似的方法把相關的item放在itemgroup里。
 
4. target元素。上面已經提到了,target表示一個需要完成的虛擬的任務單元。每個project可以包括一個或多個target,從而完成一系列定制的任務。你需要給每個target設置一個name屬性(同一project下的兩個target不能擁有同樣的name)以便引用和區別。
 
舉例來說,在你的項目生成過程中可能需要完成三個階段的任務:首先從vss中check-out源代碼,接下來編譯這些代碼并執行單元測試,最后把它們check-in回vss。那么通常情況下你可以創建三個不同的target以清晰劃分三個不同的階段:
 
<target name=”checkout” >
...
</target>
 
<target name=”build” dependsontargets=”checkout”>
 <task name=”build” .../>
 <task name=”unittest” ... />
</target>
 
<target name=”checkin” dependsontargets=”checkout;build”>
...
</target>
 
這樣,你就可以非常清晰地控制整個生成過程。為了反應不同target之間的依賴關系(只有check-in后才能編譯,只有編譯完成才可能check-out……),你需要設置target的dependsontargets屬性(注意是復數),以表示僅當這些target執行完成之后才能執行當前的target。當msbuild引擎開始執行某項target時(別忘了project的defaulttargets屬性),會自動檢測它所依賴的那些target是否已經執行完成,從而避免因為某個生成環節缺失而導致整個生成過程發生意外。
 
你可以通過project的defaulttargets屬性指定msbuild引擎從哪(幾)個target開始執行,也可以在調用msbuild.exe時使用t開關來手動指定將要運行的target,方法如下:
 
msbuild /t:checkout
 
這樣,只有checkout(以及它所依賴的target,在上文中沒有)會被執行。
 
5. task元素。這可能是整個項目文件中最重要的,因為它才是真正可執行的部分(這也是為什么我在上面說target是虛擬的)。你可以在target下面放置多個task來順序地執行相應的任務,比如我在上面示例代碼中就在兩個不同的target中安排了makedir、csc和exec三個不同的task。這些task通過name屬性來相互區分,并各自擁有不同的其它屬性來完成不同的任務,比如csc有sources(源代碼文件)、targettype(目標類型)、outputassembly(生成程序集名稱)等屬性,而makedir則只需設置directories(需要創建的路徑名稱列表)即可。
 
也許你會奇怪這些task的名稱和屬性從哪里來。好吧,請用文本編譯器打開%windir%/microsoft.net/framework/v1.2.30703/microsoft.buildtasks文件,看到了嗎?默認情況下里面應該是這樣的(不同的版本可能會有細微差別):
 
<!-- this file lists all the tasks that ship by default with msbuild -->
<defaulttasks>
 <usingtask taskname="microsoft.build.tasks.csc" assemblyname="msbuildtasks"/>
 <usingtask taskname="microsoft.build.tasks.msbuild" assemblyname="msbuildtasks"/>
 <usingtask taskname="microsoft.build.tasks.exec" assemblyname="msbuildtasks"/>
 <usingtask taskname="microsoft.build.tasks.vbc" assemblyname="msbuildtasks"/>
 <usingtask taskname="microsoft.build.tasks.makedir" assemblyname="msbuildtasks"/> 
 <usingtask taskname="microsoft.build.tasks.resgen" assemblyname="msbuildtasks"/> 
 <usingtask taskname="microsoft.build.tasks.copy" assemblyname="msbuildtasks"/> 
 <usingtask taskname="microsoft.build.tasks.netassemblyresolver" assemblyname="msbuildtasks"/> 
 <usingtask taskname="microsoft.build.tasks.transformpath" assemblyname="msbuildtasks"/> 
</defaulttasks>
 
你會注意到,在defaulttasks元素下面排列的全是usingtask,其中指明每一個task的taskname(名稱)和assemblyname(程序集)。比如說第一個usingtask就對應著我們上面用過的csc任務,它的完整名稱(namespace+class)是microsoft.build.tasks.csc,位于msbuildtasks.dll程序集中(請在同一目錄下確認這一.dll文件的存在)。這樣,msbuild引擎在遇到對csc任務的調用時就會通過這里的注冊信息來確定csc所在的程序集,從而最終運行相應的托管代碼。這樣,如果你自己也寫了不同的task,請按同樣的方式對它進行注冊以便使用。如果你引用了一個還沒有注冊的target,那么msbuild引擎將無法找到它的存在而導致生成失敗。
 
當然,msbuild task的注冊方式不止以上一種。以上注冊方法的影響范圍是全局,你可以在每一個project里應用上面注冊的那些task。但你也可以選擇在project范圍內注冊task,這將對應著另外一種略有不同的方法。我會在后面的一篇文章里給出具體介紹。在這里,你只需明白你所需要的task在哪里找到,而它們的具體用法可以通過參考msbuildtasks一文來獲得,在這里我就不細說了。
 
ok,介紹了一長串,還是快點把我們的build.csproj運行起來吧。請在shell的同一目錄下輸入以下命令:
 
msbuild
 
或者:
 
msbuild build.csproj
 
運行結果如下:
 
d:/dev/mymsbuilddemo>msbuild build.csproj
msbuild build.csproj
microsoft (r) .net build engine version 1.2.30703.4
[microsoft .net framework, version 1.2.30703.4] 
copyright (c) microsoft corporation 2003. all rights reserved.
 
target "build" in project "build.csproj"
 task "makedir"
 creating directory "bin".
 task "csc"
 csc.exe /out:"bin/hellomsbuild.exe" /target:exe "hellomsbuild.cs"
 
target "run" in project "build.csproj"
 task "exec"
 hello msbuild!
 
可見,在build.csproj指定的兩個target和三個task均按相應的順序依次運行,在csc執行時msbuild還顯示出了當前執行的具體命令,而在原來的visual studio .net年代,你是無法獲知當前正在執行的編譯命令是什么(據alex kipman稱,連visual studio .net自己也不知道正在執行的具體命令,因為那些命令已經被hard code進了“黑盒子”,根本無法提?。?br>
 
好了,一個簡單的msbuild文件用法示例就到這兒了。如果你此前還沒接觸過msbuild或者nant,那么希望這篇文章能讓你對msbuild的用法有個初步的了解。還有很多的細節我在文中沒有涉及,如果你感興趣的話就請下載前面我提到的那些msbuild文檔來自己研究吧。我會在下一篇文章里介紹如何開發自己的msbuild task。