假定我们修改了一些代码, 然后在Visual Studio里面编译一下, 看到没有问题我们就把代码签入到代码仓库中; 或者做的更正规一点, 我们把修改做成一个搁置集(ShelveSet), 然后由我们的同伴给我们来做Buddy Build, 如果同伴的编译也是成功了, 那么我们就把修改签入到代码仓库中. 然后我们只能祈祷, 让这些修改的服务器生成结果完全正确吧.因为虽然修改在签入前后是完全一致的, 但是服务器的生成过程和Visual Studio的编译过程却不尽相同. 更确切的说, TFS服务器的生成过程, 包含了编译, 测试, 打包,分发这比较容易出错的几个过程, 而不单单是编译.
比较理想的实现是, 代码在真正进入代码仓库之前, 应该有机会通过一种和已有代码合并的集成性生成验证过程, 如果验证过程失败则拒绝签入. 有些源代码控制和生成平台提供了这样的选择. 但是TFS没有这样的功能. TFS的服务器生成过程, 总是以当前代码仓库中的代码为目标的. 值得注意的是, TFS在创建新的生成类型定义的向导中向我们提供触发器(Trigger)选择时, 给我们提供了一种"代码签入时引发生成"的选项. 这个选项所谓的生成过程, 是在签入后而不是签入前!
面对这样的功能性缺失, 而我们又有迫切的验证修改正确性的需求, 我们该怎么办呢?
答案就是Desktop Build.
在谈论TFS的生成过程时, 我们似乎陷入了一种定式: 说到TFS Build, 我们一定想到的是专门的生成服务器和在它上面运行的生成服务, 好像和自己的桌面电脑没有一点关联. 本系列前面几篇文章也都在连篇累牍地讲关于Team Build的各种实践中的问题. 但是实际上不是的. 团队生成基础默认的为我们提供了Desktop Build和Desktop Rebuild的流程定义. 顾名思义, Desktop Build, 是在本地(程序员的桌面工作环境)进行的生成过程, 它的目标, 就是为我们的桌面本地生成提供最基本的流程支持, 它用于生成的原材料, 是本地代码(当然就包含了您已经修改还未签入的代码). 它提取Team Build生成流程的核心和最容易验证错误的部分- 编译/测试/打包, 给开发者提供了在本地验证代码修改正确性的机会. Desktop Build的流程如图所示.
笔者强烈建议您将这个流程和本系列第一篇的服务器生成过程(EndToEndIteration build), 以及第四篇的EndToEndIteration流程树状图对比来看, 从而体会Desktop Build的意义和目的. 在TFS的平台上, Desktop Build相对于Visual Studio而言具有天生的优势: 它是Team Build过程核心部分的精准复制, 涵盖了代码修改最可能影响的部分. (Ps. 另有Msbuild xxx.sln的生成方式, 各位可以对照总结优缺点) 所以如果您使用的是TFS平台, 作者强烈建议您使用Desktop Build来取代Visual Studio的编译.
您可能有相当的理由来拒绝Desktop Build, 或者仅仅是把它当做锦上添花的工具使用. 譬如, 假设代码项目可能很大, 涵盖了50个不同的solution. 您修改了其中的一个solution. 使用Vusual Studio编译这个solution, 花费的时间会很少. 但是使用Desktop Build, 会编译所有的solution, 时间上的代价太大. 是的, 这也正是下一篇条件编译实践所要解决的问题. 条件编译实践会完美的解决上面的这个问题. 而且笔者接触过几个公司的生成系统, 无论使用的是否是TFS平台, 都毫无意外的采用了条件编译来提高工作效率.
应用Desktop Build的对象文件, 还是tfsbuild.proj. 微软把默认的Desktop Build流程定义在Microsoft.TeamFoudation.Build.targets中, 通过import语句导入到tfsbuild.proj文件中. 现在我们用实例看一下如何使用Desktop Build. 打开命令行工具(VS2008 command prompt), 定位到含有tfsbuild.proj的位置, 输入msbuild tfsbuild.proj, 回车:
等输出完毕, 一次Desktop Build就完成了. 如果您没有重写BuildRoot属性的话, 默认的BuildRoot就是您工作区的最顶目录, 在这个目录下, 您可以看到一个Binaries目录, 里面就是这次编译的输出并且包含了生成的安装包, 当然如果您在重写服务器生成流程的时候如果改写了一些位置属性, 那么就去对应的地方查找结果吧:
现在我们还不满足于这个基本的Desktop Build. 对于tfsbuild.proj和Microsoft.TeamFoudation.Build.targets文件中定义的所有属性, 都可以由这个命令行传入, 命令行传入的属性值具有最高优先级, 将覆盖它在所有别的地方的值, 例如(特别注意最后两个属性, 是为AssemblyInfo task 提供的, 默认的为空的这两个属性会直接破坏Desktop Build):
msbuild tfsbuild.proj
/p:Configuration=Debug;Platform=x64;BuildRoot=c:\bld;AssemblyBuildNumber=0;AssemblyRevisionNumber=0
具体的属性列表, 请查阅这两个文件以及MSDN
同样是一个TFSBuild.proj文件,为什么我们在本地生成就是Desktop Build, 而在服务器生成就是Team Build?
如果您看过上一篇文章, 你一定对一个大而完整的EndToEndIteration生成流程, 以树形结构被一步步分解成不能再分解的任务执行单元的层次结构而印象深刻. Desktop Build实际上也混迹其中, 采用了其中的某些叶节点和小的子树.
如果您仔细看到阅读了Microsoft.TeamFoudation.Build.targets和tfsbuild.proj, 您会注意到一个属性会被经常用来作为各个生成目标的条件, 这个属性就是IsDesktopBuild. 这个属性在文件中被赋予的默认值是True, 所以当我们在本地直接使用msbuild驱动tfsbuild.proj文件时, msbuild引擎会根据这个属性条件跳转到执行对应的Desktop Build的流程; 当服务器执行生成过程时, 服务器启动msbuild引擎执行的命令相当于: msbuild tfsbuild.proj /p:IsDesktopBuild=False, 所以msbuild引擎会根据条件跳转执行Team Build的生成流程.
在您的代码修改期间, 可能服务器上的同一处代码或者相关代码已经被修改过了. 执行一次GET操作, 能使您的本地及时获取最新的代码, 如果和您的修改有冲突, 也能让您及时的解决冲突, 以供Desktop Build使用. 如果您不执行GET操作, 而恰恰服务器代码已经被修改过了, 那么即使您通过了Desktop Build, 那也是毫无意义的. 因为在您签入的时候, 一定会拒绝签入并提示让您解决冲突从而迫使您本地代码再一次发生修改.
这是重定义生成流程最容易被忽视的地方. 见过很多修改tfsbuild.proj文件后team build能正常运行, 但是Desktop bulid却不能运行的情况. 这是因为重定义服务器生成流程的过程中修改了team build和Desktop build共同依赖的东西, 却忽视了修改Desktop build的对应流程. 这大概相当于"行百里者半九十", 哪怕team build方面做得再多, 也只是做了一半. 有时候会有一些第三方公司的consultant来帮我们做生成流程重定义. 评价他们工作的最简单的办法, 就是在他们面前打开命令行跑一下Desktop build给他们看.
我们将在本系列的最后一篇, 讨论一种可能的方案和实现的必要条件.