未标明原创的文章皆为转载~

(转)MSBuild入门

假如您和我相同一直都在用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进了“黑盒子”,根本无法提取)。

好了,一个简单的msbuild文档用法示例就到这儿了。假如您此前还没接触过msbuild或nant,那么希望这篇文章能让您对msbuild的用法有个初步的了解。更有很多的细节我在文中没有涉及,假如您感兴趣的话就请下载前面我提到的那些msbuild文档来自己研究吧。我会在下一篇文章里介绍如何研发自己的msbuild task。

posted @ 2009-08-06 10:42  CodeYu  阅读(987)  评论(0编辑  收藏  举报