MSBuild 简解

从最原始的编译器,逐渐到 Shell 命令组合、Make工具,到现在的针对性 Build 工具,Rake、Ant、MSBuild,甚至于 PowerShell 这样的工具;都为我们软件开发以及系统管理做出了贡献,我甚至不能想象几十年前使用编译器生成程序的复杂步骤(其实就是把 makefile 拆开,相当恐怖)。

探讨 Make、Ant 以及 MSBuild 无太多意义,甚至从纯技术和扩展性上来说,我认为 MSBuild 弱于 NAnt,但是软件并不是这么简单,之所以深研 MSBuild,M$ 作为后盾让人信任的无奈。

本文主要讲述 MSBuild 的基本概念,以及如何使用它辅助开发。

首先我们来看一个最简单的 Build:

1
2
3
4
5
6
7
8
9
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <Welcome>Hello MSBuild!</Welcome>
  <PropertyGroup>

  <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:

1
2
3
4
<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 的执行。上面的 就是 MSBuild 内置的一个 Task,Text 则是参数。
Task 可以使用 Condition 属性。
通常情况下,MSBuild 自带的 Task 并不够用,有以下 MSBuild 扩展,可以几乎不需要自己写扩展:

Item

我们可以简单的把 Item 理解为 .Net 中的 Dictionary> 类型,内层的字典被称作元数据表。

1
2
3
4
5
6
7
8
9
10
<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# 来表述上述内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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:文章写的比较简单,不够全面,有问题请给我妹儿 Zealic ,我将详细为你解答。或访问我的 Twitter,里面有不少小技巧。

Zealic @ 2009-3-28

View Comments |
Categories: tech.dotnet
Tags:

Related posts