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 会提示出错,要求你手动指定目标项目文件。

以下是项目文件:

 1<project defaulttargets="Run">
 2<property bin="bin"></property>
 3<property outputassembly="HelloMSBuild"></property>
 4<item include="HelloMSBuild.cs" type="Source"></item>
 5<target name="Build">
 6<task condition="!Exists('$(Bin)')" directories="$(Bin)" name="MakeDir"></task>
 7<task name="Csc" outputassembly="$(Bin)\$(OutputAssembly).exe" sources="@(Source)" targettype="exe"></task>
 8</target>
 9<target dependsontargets="Build" name="Run">
10<task command="$(Bin)\$(OutputAssembly).exe" name="Exec"></task>
11</target>
12</project>

如果你此前没有过 NAnt 的开发经验,那么上面这些东西肯定看起来挺吓人。这个时候最好的办法是打开那篇 MSBuildFileFormat ,对照上面代码查找相应的项目元素的含义。下面我对其中重要的项目元素进行一下解释。

1. Project 元素。这是每一个项目文件的最外层元素,它表示了一个项目的范围。如果缺少了这一元素, MSBuild 会报错称 Target 元素无法识别或不被支持。

Project 元素拥有多个属性,其中最常用到的是 DefaultTargets 属性。我们都知道,在一个项目的生成过程中可能需要完成几项不同的任务(比如编译、单元测试、 check-in 到源代码控制服务器中等),其中每一项任务都可以用 Target 来表示。对于拥有多个 Target 的项目,你可以通过设置 Project 的 DefaultTargets (注意是复数)属性来指定需要运行哪(几)个 Target ,比如:

  1<project defaulttargets="”Build”">
  2
  3... 
  4
  5或者: 
  6
  7<project defaulttargets="”Build;Test;Run”">
  8
  9... 
 10
 11如果没有这个设置,  MSBuild  将只运行排在最前面的那个  Target  。 
 12
 132\.  Property  元素。在项目中你肯定需要经常访问一些信息,例如需要创建的路径名、最终生成的程序集名称等。这些信息你最好别  hard code  进项目中,除非你一次写过之后永不更改。这时  Property  就能派上用场了。你把上面提到的那些信息以  name/value  的形式添加进  Property  ,随后就可以以  $(PropertyName)  的形式访问。这样你就无须为了改动一个文件名称而让整个项目文件伤筋动骨了。比如上面代码中的  Bin  就是将要创建的路径名称,而  AssemblyName  则是最终要生成的程序集名称。这些属性的名称不是固定的,你完全可以按自己的习惯来进行命名。在使用时,你需要把属性名称放在  ”$(“  和  ”)”  对内(不包括引号),以表示这里将被替换成一个  Property  元素的值。 
 14
 15另外,如果  Property  元素数量比较多,你还可以把它们分门别类地放在不同的  PropertyGroup  里,以提高代码的可阅读性。这对  Property  本身没有任何影响。比如: 
 16
 17<propertygroup>
 18<property ...=""></property>
 19<property ...=""></property>
 20</propertygroup>
 21
 223\.  Item  元素。在整个项目文件中你肯定要提供一些可被引用的输入性资源  (inputs)  信息,比如源代码文件、引用的程序集名称、需要嵌入的图标资源等。它们应该被放在  Item  里,以便随时引用。语法是: 
 23
 24<item include="”NameOrPath”" type="”TheType”"></item>
 25
 26其中  Type  属性可以被看作是资源的类别名称,比如对于  .cs  源文件,你可以把它们的  Type  都设置为  Source  ,对于引用的程序集把  Type  都设置为  Reference  ,这样在随后想引用这一类别的资源时只要引用这个  Type  就可以了,方法是  @(TypeName)  。可千万别和  Property  的引用方法弄混了。 
 27
 28既然  Type  是资源的类名,那么  Include  就是具体的资源名称了,比如在上面的示例代码中,  Include  引用的就是  C#  源代码文件的名称。你也可以用使用通配符  *  来扩大引用范围。比如下面这行代码就指定了当前目录下的所有  C#  文件都可以通过  @(Source)  来引用: 
 29
 30<item include="”*.cs”" type="”Source”"></item>
 31
 32另外,你也可以通过与  PropertyGroup  类似的方法把相关的  Item  放在  ItemGroup  里。 
 33
 344\.  Target  元素。上面已经提到了,  Target  表示一个需要完成的虚拟的任务单元。每个  Project  可以包括一个或多个  Target  ,从而完成一系列定制的任务。你需要给每个  Target  设置一个  Name  属性(同一  Project  下的两个  Target  不能拥有同样的  Name  )以便引用和区别。 
 35
 36举例来说,在你的项目生成过程中可能需要完成三个阶段的任务:首先从  VSS  中  check-out  源代码,接下来编译这些代码并执行单元测试,最后把它们  check-in  回  VSS  。那么通常情况下你可以创建三个不同的  Target  以清晰划分三个不同的阶段: 
 37
 38<target name="”CheckOut”">
 39
 40... 
 41
 42</target>
 43<target dependsontargets="”CheckOut”" name="”Build”">
 44<task ...="" name="”Build”"></task>
 45<task ...="" name="”UnitTest”"></task>
 46</target>
 47<target dependsontargets="”CheckOut;Build”" name="”CheckIn”">
 48
 49... 
 50
 51</target>
 52
 53这样,你就可以非常清晰地控制整个生成过程。为了反应不同  Target  之间的依赖关系(只有  Check-in  后才能编译,只有编译完成才可能  Check-out  ……),你需要设置  Target  的  DependsOnTargets  属性(注意是复数),以表示仅当这些  Target  执行完成之后才能执行当前的  Target  。当  MSBuild  引擎开始执行某项  Target  时(别忘了  Project  的  DefaultTargets  属性),会自动检测它所依赖的那些  Target  是否已经执行完成,从而避免因为某个生成环节缺失而导致整个生成过程发生意外。 
 54
 55你可以通过  Project  的  DefaultTargets  属性指定  MSBuild  引擎从哪(几)个  Target  开始执行,也可以在调用  MSBuild.exe  时使用  t  开关来手动指定将要运行的  Target  ,方法如下: 
 56
 57MSBuild /t:CheckOut 
 58
 59这样,只有  CheckOut  (以及它所依赖的  Target  ,在上文中没有)会被执行。 
 60
 615\.  Task  元素。这可能是整个项目文件中最重要的,因为它才是真正可执行的部分(这也是为什么我在上面说  Target  是虚拟的)。你可以在  Target  下面放置多个  Task  来顺序地执行相应的任务,比如我在上面示例代码中就在两个不同的  Target  中安排了  MakeDir  、  Csc  和  Exec  三个不同的  Task  。这些  Task  通过  Name  属性来相互区分,并各自拥有不同的其它属性来完成不同的任务,比如  Csc  有  Sources  (源代码文件)、  TargetType  (目标类型)、  OutputAssembly  (生成程序集名称)等属性,而  MakeDir  则只需设置  Directories  (需要创建的路径名称列表)即可。 
 62
 63也许你会奇怪这些  Task  的名称和属性从哪里来。好吧,请用文本编译器打开  %windir%\Microsoft.NET\Framework\v1.2.30703\Microsoft.BuildTasks  文件,看到了吗?默认情况下里面应该是这样的(不同的版本可能会有细微差别): 
 64
 65<!-- This file lists all the tasks that ship by default with MSBuild -->
 66<defaulttasks>
 67<usingtask assemblyname="MSBuildTasks" taskname="Microsoft.Build.Tasks.Csc"></usingtask>
 68<usingtask assemblyname="MSBuildTasks" taskname="Microsoft.Build.Tasks.MSBuild"></usingtask>
 69<usingtask assemblyname="MSBuildTasks" taskname="Microsoft.Build.Tasks.Exec"></usingtask>
 70<usingtask assemblyname="MSBuildTasks" taskname="Microsoft.Build.Tasks.Vbc"></usingtask>
 71<usingtask assemblyname="MSBuildTasks" taskname="Microsoft.Build.Tasks.MakeDir"></usingtask>
 72<usingtask assemblyname="MSBuildTasks" taskname="Microsoft.Build.Tasks.ResGen"></usingtask>
 73<usingtask assemblyname="MSBuildTasks" taskname="Microsoft.Build.Tasks.Copy"></usingtask>
 74<usingtask assemblyname="MSBuildTasks" taskname="Microsoft.Build.Tasks.NetAssemblyResolver"></usingtask>
 75<usingtask assemblyname="MSBuildTasks" taskname="Microsoft.Build.Tasks.TransformPath"></usingtask>
 76</defaulttasks>
 77
 78你会注意到,在  DefaultTasks  元素下面排列的全是  UsingTask  ,其中指明每一个  Task  的  TaskName  (名称)和  AssemblyName  (程序集)。比如说第一个  UsingTask  就对应着我们上面用过的  Csc  任务,它的完整名称(  namespace+class  )是  Microsoft.Build.Tasks.Csc  ,位于  MSBuildTasks.dll  程序集中(请在同一目录下确认这一  .dll  文件的存在)。这样,  MSBuild  引擎在遇到对  Csc  任务的调用时就会通过这里的注册信息来确定  Csc  所在的程序集,从而最终运行相应的托管代码。这样,如果你自己也写了不同的  Task  ,请按同样的方式对它进行注册以便使用。如果你引用了一个还没有注册的  Target  ,那么  MSBuild  引擎将无法找到它的存在而导致生成失败。 
 79
 80当然,  MSBuild Task  的注册方式不止以上一种。以上注册方法的影响范围是全局,你可以在每一个  Project  里应用上面注册的那些  Task  。但你也可以选择在  Project  范围内注册  Task  ,这将对应着另外一种略有不同的方法。我会在后面的一篇文章里给出具体介绍。在这里,你只需明白你所需要的  Task  在哪里找到,而它们的具体用法可以通过参考  MSBuildTasks  一文来获得,在这里我就不细说了。 
 81
 82OK  ,介绍了一长串,还是快点把我们的  Build.csproj  运行起来吧。请在  shell  的同一目录下输入以下命令: 
 83
 84MSBuild 
 85
 86或者: 
 87
 88MSBuild Build.csproj 
 89
 90运行结果如下: 
 91
 92d:\Dev\MyMSBuildDemo&gt;msbuild Build.csproj 
 93
 94msbuild Build.csproj 
 95
 96Microsoft (R) .NET Build Engine version 1.2.30703.4 
 97
 98[Microsoft .Net Framework, Version 1.2.30703.4] 
 99
100Copyright (C) Microsoft Corporation 2003. All rights reserved. 
101
102Target "Build" in project "Build.csproj" 
103
104Task "MakeDir" 
105
106Creating directory "bin". 
107
108Task "Csc" 
109
110Csc.exe /out:"bin\HelloMSBuild.exe" /target:exe "HelloMSBuild.cs" 
111
112Target "Run" in project "Build.csproj" 
113
114Task "Exec" 
115
116Hello MSBuild! 
117
118&lt;P class=MsoNormal style="</project></project>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus