分类: 专题开发 自动化 2009-01-20 11:56 5711人阅读 评论(1) 收藏 举报

最近在从事自动构造工作的过程中,对MSBuild本身有了一些更加深入的认识。MSBuild不仅仅是一个构造工具,应该称之为拥有相当强大扩展能力的自动化平台。

按照笔者现在的理解,MSBuild平台的主要涉及到三部分:执行引擎、构造工程、任务。其中最核心的就是执行引擎,它包括定义构造工程的规范,解释构造工程,执行“构造动作”;构造工程是用来描述构造任务的,大多数情况下我们使用MSBuild就是遵循规范,编写一个构造工程;MSBuild引擎执行的每一个“构造动作”就是通过任务实现的 ,任务就是MSBuild的扩展机制,通过编写新的任务就能够不断扩充MSBuild的执行能力。

所以这三部分分别代表了引擎、脚本和扩展能力。

1、构造工程(脚本文件)

先说说构造工程,只要通过Notepad打开任何一个VS2005(也就是支持CLR 2.0)下的C#工程(csproj)文件,就知道构造工程到底是怎么回事了。

如果说脚本,我们立刻想到的是VBScript或者JavaScript,构造工程内描述的内容,和常见的脚本语言的源文件之间还是有蛮大差距的,为什么也称之为“脚本”呢?因为笔者觉得没啥区别。脚本不就是纯文本形式保存,不经编译解释执行,可以实现一定逻辑分支的程序么?

再看构造工程,在构造工程中我们我们可以定义和使用变量(通过Property/PropertyGourp/Item/ItemGroup等元素),可以使用条件分支(通过Choose/When/Otherwise等元素)、能够在运行时给变量赋值(通过执行任务,获取其返回类型参数的方式)、能够定义执行块(通过Target元素,相当于函数)、能够进行异常处理(通过OnError元素)、还可以复用已有工程定义的内容(通过Import元素)。拥有这些能力和高级语言已经相差无几了,所以笔者认为构造工程不是描述性语言,而是脚本语言。

这里还需要强调一点的是,项目级元素(Property)可以在<PropertyGroup>元素下定义,也可以在构造过程中作为外部参数传入(具体参见《MSBuild命令行参考》)。这是一个非常有用的特性,一般编译时选择配置项(Debug或者Release)就是利用这个特性实现的。

有关构造工程的编写规范可以参考《MSBuild项目文件引用》。

2、执行引擎

接下来看执行引擎,通常我们使用下面的命令行开始执行构造:

MSBuild.exe <ProjectFile>

其中<ProjectFile>是前面提到的构造工程,也就是脚本文件,那么MSBuild.exe就应当是执行引擎了。

没错,不过看一下源代码就会发现MSBuild.exe非常简单,其实主要做的工作就是命令行解析、构造环境的准备(如生成日志记录模块准备一些全局变量),然后就是创建Microsoft.Build.BuildEngine.Engine类的实例,然后调用其BuildProjectFile方法来完成。所以真正的构造逻辑是在Microsoft.Build.Engine.dll中定义并且实现的。下面简单的代码就模拟了MSBuild.exe的工作。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using Microsoft.Build.BuildEngine;
  5. namespace BuildAProjectCS
  6. {
  7. class Program
  8. {
  9. static void Main(string[] args)
  10. {
  11. // Instantiate a new Engine object
  12. Engine engine = new Engine();
  13. // Point to the path that contains the .NET Framework 2.0 CLR and tools
  14. engine.BinPath = @"c:/windows/microsoft.net/framework/v2.0.xxxxx";
  15. // Instantiate a new FileLogger to generate build log
  16. FileLogger logger = new FileLogger();
  17. // Set the logfile parameter to indicate the log destination
  18. logger.Parameters = @"logfile=C:/temp/build.log";
  19. // Register the logger with the engine
  20. engine.RegisterLogger(logger);
  21. // Build a project file
  22. bool success = engine.BuildProjectFile(@"c:/temp/validate.proj");
  23. //Unregister all loggers to close the log file
  24. engine.UnregisterAllLoggers();
  25. if (success)
  26. Console.WriteLine("Build succeeded.");
  27. else
  28. Console.WriteLine(@"Build failed. View C:/temp/build.log for details");
  29. }
  30. }
  31. }

具体的对象模型参见CLR类库参考中的《Microsoft.Build.Framework命名空间》和《Microsoft.Build.BuildEngine命令空间》。

笔者简单地分析了一下MSBuild.exe和Microsoft.Build.Engine.dll的源代码,MSBuild的构造过程大致如下:

a)      先创建一个构造请求(BuildRequest,构造请求是用来记录构造状态的数据结构),创建完毕之后将构造请求投递到请求队列中。

b)      在执行模块中,从请求队列中获取请求,然后开始处理。

c)      通过Project类加载构造工程,加载过程中检查是Sulotion、VC工程还是其它语言的工程。如果是Solution的话,生成一个临时的包装工程,逐一构造Solution中包含的工程;如果是VC工程的话,也生成一个包装工程,在这个工程中直接执行VCBuild任务来执行构造。否则直接通过XmlDocument加载项目文件,解析其中的元素,识别Property、Item、Target之类元素。

d)      工程解析完毕后按照Target的顺序逐一执行。

e)      在执行Target的过程中先解析是否存在依赖的Target以及OnError子句(即产生错误时需要执行的Target)。

f)       先执行Target依赖的Target,然后通过TaskEngine执行本Target中的每一个任务。如果Target每一个任务都正确执行的话,那么执行下一个Target;否则执行错误处理的Target。

g)      执行Task的过程就是实例化注册为Task的类,然后调用其Execute方法。

h)      所有Target执行完毕,则本次构造也执行完毕。

以上仅仅为了便于理解概念进行的描述,实际的构造过程可能是考虑到多CPU以及内联编译,内部逻辑相当复杂,很多地方应用了Proxy模式。

3、任务(Task)

通过对执行引擎的描述可以发现执行引擎主要是维护执行流程以及记录执行流程中各类变量(Property和Item),具体构造过程中的每一个动作,都是通过Task实现的。也就是说单靠Microsoft.Build.Engine.dll虽然可以加载并且解析构造工程,但是无法完成构造动作。之所以MSBuild能够完成编译、链接、创建目录、复制文件等一系列工作,都是因为在Microsoft.Build.Tasks.dll中实现了与之对应的一个个任务。具体请参考《MSBuild任务参考》以及CLR类库参考中的《Microsoft.Build.Task命名空间

通过观察MSBuild自带的这些常用任务,可以发现其中分为两类:一类从ToolTask继承,这类任务基本上就是直接调用外部二进制文件执行完成某个特定的动作,例如VCBuild和Exec等;另一类直接从Task继承,是通过内部代码逻辑完成特定动作,例如Copy和MSBuild等。那些直接执行外部文件的任务虽然功能强大,但是有比较大的局限性,执行结果的反馈非常有限,通常只有ExitCode,很难获得其内部执行的更多信息,例如日志输出或者操作影响的结果。

大部分情况下我们只需要一个Exec任务就能够完成全部的构造动作,但是这样做的结果和我们写一个命令行的批处理文件没什么区别了。MSBuild平 台和命令行批处理最大不同在于,它是一个更紧密的工作环境,任务之间通过一系列自定义的全局参数互相协同工作,比较灵活并且移植性高;同时共享日志模块统 一输出执行过程中的各类信息,便于观察和分析。而批处理中各个命令之间几乎是完全孤立的,只能通过硬编码的方式进行协同,协作能力比较差。

举个最简单的例子,编译三个工程,然后复制编译结果到目标目录下。如果用批处理可能会写成这个样子:“编译工程1、复制编译结果、编译工程2、复制结果、编译工程3、复制结果”。而利用MSBuild就可以简化很多工作,例如“申明需要编译的工程(工程1、工程2、工程3、...)、编译需要编译的工程、复制编译结果”。

顺便说一句,MSBuild这个任务比较有趣,在内部直接调用了构造引擎的方法(IBuildEngine2.BuildPrejectFilesInParallel)。这个例子又一次告诉我们这个世界上许多看似强大的东西,其实什么都没有干,只是因为他们手中掌握了有效的资源。-^o^-

除了基础的任务之外,任务还可以任意扩展,并且实现这种扩展非常方便。创建一个CLR 2.0以上的类库工程,编写实现了ITask接口的类,然后在构造工程中通过<UsingTask>元素注册任务就可以使用了。例如:

  1. <UsingTask TaskName="Microsoft.CompactFramework.Build.Tasks.PlatformVerificationTask" AssemblyName="Microsoft.CompactFramework.Build.Tasks, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
  2. ……
  3. <Target Name="PlatformVerificationTask">
  4. <PlatformVerificationTask
  5. PlatformFamilyName="$(PlatformFamilyName)"
  6. PlatformID="$(PlatformID)"
  7. SourceAssembly="@(IntermediateAssembly)"
  8. ReferencePath="@(ReferencePath)"
  9. TreatWarningsAsErrors="$(TreatWarningsAsErrors)"
  10. PlatformVersion="$(TargetFrameworkVersion)"/>
  11. </Target>

所以说MSBuild能够成为一个构造引擎,不是因为有个叫MSBuild的Exe文件,也不是因为脚本文件被称之为构造工程,而是因为与之配套的Microsoft.Build.Task.dll中主要实现了主要是和构造相关的任务。换句话说如果提供和测试相关的任务库的话,MSBuild也就是一个自动测试的平台。

总之,MSBuild本身更趋向于一个自动化执行平台,可以根据需求编写不同的脚本文件来满足不同的应用,当现有能力无法满足时,通过编写新的任务进行扩展。不仅限于构造,自动安装、自动测试等都可以依赖这个平台来实现。

03.Msbuild的更多相关文章

  1. #一周五# win10通用平台,无处不在的Xamarin,msbuild开源,MVP卢建晖的Asp.NET 5系列 (视频)

    又到周五,本周博主的大部分时间都花在深圳了.最近winhec的消息太多了,我只想补充一点,就是winhec时隔7年之后回归,大多数的媒体都还在沿用之前的“硬件工程大会(Hardware Enginee ...

  2. 用msbuild跑xunit单元测试

    用了Visual Studio 2015之后,发现没法跑xUnit单元测试,xUnit.net runner不支持VS2015,TestDriven.Net也不支持VS2015. 等它们支持VS201 ...

  3. [独孤九剑]持续集成实践(二)– MSBuild语法入门

    本系列文章包含: [独孤九剑]持续集成实践(一)- 引子 [独孤九剑]持续集成实践(二)– MSBuild语法入门 [独孤九剑]持续集成实践(三)- Jenkins安装与配置(Jenkins+MSBu ...

  4. Headless MSBuild Support for SSDT (*.sqlproj) Projects

    http://sqlproj.com/index.php/2012/03/headless-msbuild-support-for-ssdt-sqlproj-projects/ Update: bre ...

  5. 用MSBuild和Jenkins搭建持续集成环境(1)

     http://www.infoq.com/cn/articles/MSBuild-1 你或其他人刚刚写完了一段代码,提交到项目的版本仓库里面.但等一下,如果新提交的代码把构建搞坏了怎么办?万一出现编 ...

  6. 用MSBuild和Jenkins搭建持续集成环境(1)[收集]

    你或其他人刚刚写完了一段代码,提交到项目的版本仓库里面.但等一下,如果新提交的代码把构建搞坏了怎么办?万一出现编译错误,或者有的测试失败了,或者代码不符合质量标准所要求的底限,你该怎么办? 最不靠谱的 ...

  7. 使用MSBuild实现完整daily build流程

    一.MSBuild 在微软软件开发中,每日构建是最重要的过程之一,被称为微软产品开发的“心跳”.简单来看,每天构建系统将整个产品解决方案完整构建一遍,生成的目标文件和安装文件被放置在一个共享位置.接着 ...

  8. Android游戏开发实践(1)之NDK与JNI开发03

    Android游戏开发实践(1)之NDK与JNI开发03 前面已经分享了两篇有关Android平台NDK与JNI开发相关的内容.以下列举前面两篇的链接地址,感兴趣的可以再回顾下.那么,这篇继续这个小专 ...

  9. Jenkins配置MSBuild实现自动部署(MSBuild+SVN/Subversion+FTP+BAT)

    所要用到的主要插件: [MSBuild Plugin] 具体操作: 1.配置MSBuild的版本 [系统管理]->[Global Tool Configuration]->[MSBuild ...

随机推荐

  1. Delphi 数据类型列表 good

    Delphi 数据类型列表 分类 范围 字节 备注 简单类型 序数 整数 Integer -2147483648 .. 2147483647 4 有符号32位 Cardinal 0 .. 429496 ...

  2. poj1639 Picnic Planning 最小度数限制生成树

    题意:若干个人开车要去park聚会,可是park能停的车是有限的,为k.所以这些人要通过先开车到其它人家中,停车,然后拼车去聚会.另外,车的容量是无限的,他们家停车位也是无限的. 求开车总行程最短. ...

  3. 树莓派玩耍笔记4 -- 树莓派ssh党必备的配置

    1. 关闭桌面显示 对于ssh 党.当然不须要系统花费资源在显示上. 所以我们先在 "raspi-conifg" 下选择默认启动为Text 启动(这好像也是Raspbian 的默认 ...

  4. android JNI处理图片的例子

    android JNI处理图片的例子 原地址:http://blog.csdn.net/xjwangliang/article/details/7065670 <pre class=" ...

  5. [SVN]创建本地的SVN仓库

    本地创建SVN仓库,就算是自己平时写代码也养成使用SVN的习惯. 环境: OS:Mac OS X10.9.1 SVN Version:1.7.10 创建本地SVN仓库: $svnadmin creat ...

  6. SWT可视化设计

    SWT可视化设计,可以使用Google的WindowBuilder. 在Google Code中,搜索WindowBuilder就可以看到路径. 在Eclipse中   Help--->Inst ...

  7. servlet其工作原理和例子证明

    servlet简单介绍 当我们在地址栏里面输入www.baidu.com,终于呈如今我们面前的是百度搜索的页面.在这些訪问过程中,都会有一个webserver来处理这些请求以及訪问处理后的结果. 而s ...

  8. 初尝Java序列化/反序列化对象

    看个类: package com.wjy.bytes; import java.io.Serializable; public class ObjTest implements Serializabl ...

  9. hdu4283(区间dp)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=4283 题意:有一个队列,每个人有一个愤怒值D,如果他是第K个上场,不开心指数就为(K-1)*D.但是边 ...

  10. linux LNMP自动安装脚本

    #!/bin/bashsoft_dir="/home/soft"config_dir="/home/config"httpd="httpd-2.0.5 ...