NAnt构建实例
NAnt构建实例
前言
NAnt,一款大名鼎鼎的.NET开源构建工具,功能强大,易于定制。
悲催的是开源的工具往往文档匮乏,广大程序猿们有时发现了看起来很酷的工具,可迟迟无法上手,时间就这么被残酷地浪费掉了。
在园子里搜索了一下,讲“持续集成”或者“每日构建”的不少,合我心意的不多,要么只能入门,要么起点太高。
正好这两天不忙,学习了一下NAnt的使用方法,下面就由我来通过一个实例,演示利用NAnt搭建一个自动化构建环境。
通过本文的构建,最终实现的效果为:
首先从SVN下载最新代码;利用NAnt编译代码;利用NUnit进行单元测试;生成单元测试结果报表以及代码覆盖率报表。
希望通过这篇文章,让打算使用NAnt进行自动化构建的同袍尽快上手。
本文中利用到的工具
NAnt(v0.92)
NUnit(v2.6.2)
OpenCover(v4.0.804)
ReportGenerator(v1.8.1.0)
TortoiseSVN(v1.7.10)
一、利用TortoiseSVN检出项目源码
1、获得工具
TortoiseSVN的项目地址如下:
http://sourceforge.net/projects/tortoisesvn/
2、安装工具
运行安装包,一路下一步即可。
3、检出源码
连接到你自己的代码服务器,检出源码。鉴于TortoiseSVN的易用性相当不错,我就不再罗嗦介绍具体的源码检出方法了,毕竟这并不是本文的重点。
本例中,假设代码服务器上面我们要构建的工程的地址为:http://192.168.1.1/myproject
假设源码检出到本地路径:D:\source\myproject
二、利用NAnt编译C#工程
1、获得工具
NAnt的项目地址如下:
NAnt: http://sourceforge.net/projects/nant/
NAntContrib: http://sourceforge.net/projects/nantcontrib/
NAnt不用多说。NAntContrib是NAnt的扩展,在本例中,需要利用它来生成单元测试报表和SVN控制。
2、安装工具
将NAnt的bin文件夹包含的文件拷贝出来,本例中放置在D:\Tools\NAnt
之后,将NAntContrib的bin文件夹包含的文件也拷贝到D:\Tools\NAnt
在任意位置,建立一个文件 nant.bat,文件内容如下:
1 @echo off 2 "D:\Tools\NAnt\NAnt.exe" %*
然后,将nant.bat文件剪切到C:\WINDOWS目录下
运行cmd.exe,在命令行窗口中敲入命令“nant -help”,如果看到NAnt的帮助信息,则说明安装成功。
3、编译源码
首先,在刚刚检出的源码根目录(D:\source\myproject)下建立一个名字为myproject.build的xml文件。
文件内容如下:
1 <? xml version="1.0" encoding="utf-8" ?> 2 < project name ="myproject" default ="build" basedir ="." > 3 < property name ="nant.settings.currentframework" value ="net-3.5" /> 4 <!-- 源码路径 --> 5 < property name ="dir.source" value ="D:\source\myproject" /> 6 < property name ="dir.source.myexe" value ="${dir.source}\myexe" /> 7 < property name ="dir.source.mylib" value ="${dir.source}\mylib" /> 8 < property name ="file.ico.myexe" value ="${dir.source.exe}\myexe.ico" /> 9 <!-- 编译结果 --> 10 < property name ="dir.release" value ="D:\Release" /> 11 < property name ="dir.bin" value ="${dir.release}\bin" /> 12 < property name ="file.exe.myexe" value ="${dir.bin}\myexe.exe" /> 13 < property name ="file.lib.mylib" value ="${dir.bin}\mylib.dll" /> 14 < target name ="build" 15 depends ="compile" > 16 </ target > 17 < target name ="compile" 18 depends ="mylib,myexe" > 19 </ target > 20 < target name ="mylib" > 21 < csc target ="library" 22 output ="${file.lib.mylib}" 23 debug ="Full" 24 optimize ="true" 25 define ="TRACE" 26 platform ="AnyCPU" 27 warninglevel ="4" 28 rebuild ="true" 29 filealign ="512" > 30 < sources > 31 < include name ="${dir.source.mylib}\**\*.cs" /> 32 </ sources > 33 </ csc > 34 </ target > 35 < target name ="myexe" 36 depends ="mylib" > 37 < csc target ="winexe" 38 output ="${file.exe.myexe}" 39 debug ="Full" 40 optimize ="true" 41 define ="TRACE" 42 platform ="AnyCPU" 43 warninglevel ="4" 44 rebuild ="true" 45 filealign ="512" 46 win32icon ="${file.ico.myexe}" > 47 < sources > 48 < include name ="${dir.source.myexe}\**\*.cs" /> 49 </ sources > 50 < resources > 51 < include name ="${dir.source.myexe}\**\*.resx" /> 52 </ resources > 53 < references > 54 < include name ="${file.lib.mylib}" /> 55 </ references > 56 </ csc > 57 </ target > 58 </ project >
这就是NAnt的构建配置文件了,下面对其中的内容说明一下:
文件主要由两种元素构成:property和target
property用来设置全局变量,以name属性作为唯一标识,使用的时候用${变量名}来引用。
除了自定义的property,NAnt自己也内建了一些全局变量,例如本例中出现的“nant.settings.currentframework”,用来指定当前工程使用的.NET Framework版本。
target是要执行的动作,同样适用name属性作为唯一标识,depends属性用来表示依存关系,例如
1 < target name ="myexe" 2 depends ="mylib" >
上面的配置表示“myexe”这个target执行之前,要先保证mylib被执行了。
target内部可以包含很多Task标签,表示这个target要执行的任务,具体有哪些标签的可以参照NAnt的帮助文档。
最常用的就是csc标签,用来编译C#源码。
大部分csc的属性很好理解,这里强调几个需要特别注意的:
target:这个可不是外层的target标签哦,而是表示要生成什么类型的结果,本例中出现了library(类库)、winexe(窗口程序),还可以设置为exe(控制台程序)。
debug:设置成None的话,就只生成output指定的文件;如果设置成Full,则还会生成pdb文件,这个文件在我们下面进行代码覆盖率计算时需要,因此我们设置成Full。
csc的子标签常用的有三种,本例中都出现了,分别是sources(源码)、resources(资源文件)、references(引用),本文提供的实例应该很好理解,不做说明啦。
特别说明一下路径中使用到的通配符**和*,他们都表示任意文字,区别是**只能用于代表目录,并且可以代表任意级层次的目录,*可以代表目录与文件,但只能代表单级层次的内容,例如:
test1/test2/test3.cs和test1/test2/test3/test4.cs都可以被test1/**/*.cs匹配,而test1/*/*.cs只能匹配到test1/test2/test3.cs
OK,build文件做好了,现在再做一个build.bat文件,内容为:
1 cls 2 nant -buildfile :myproject .build -logfile :build .log
事实上,这两个参数都可选,只打一个命令“nant”也是可以的。
-buildfile参数用来指定build文件,如果不指定的话,会自动搜索当前目录下扩展名为.build的文件,如果存在多个.build文件,则只执行第一个。
-logfile参数用来输出构建过程中的日志,直观的说,就是我们在命令行窗口中看到的文字,都会被输出到指定的日志文件中。
三、利用NAnt自动更新代码
在我们文章的开始,我们是使用TortoiseSVN客户端来检出代码的,但我们想自动化,所以这个动作,也可以交给NAnt来完成。
1、修改.build文件
在.build文件中追加一个target,如下
1 < target name ="update" > 2 < svn command ="update" 3 destination ="${dir.source}" 4 uri ="http://192.168.1.1/myproject" 5 verbose ="true" 6 quiet ="false" 7 /> 8 </ target >
然后,再把update动作追加到动作序列里:
1 < target name ="build" 2 depends ="update,compile" >
齐活儿~
四、利用NAnt进行单元测试并生成报表
1、获取工具
NUnit: http://sourceforge.net/projects/nunit/
OpenCover: https://opencover.codeplex.com/
ReportGenerator: https://reportgenerator.codeplex.com/
2、安装工具
NUnit直接执行安装文件,一路下一步。
将OpenCover的解压缩出来,本例中放置在D:\Tools\OpenCover
将ReportGenerator的bin文件夹包含的文件拷贝出来,本例中放置在D:\Tools\ReportGenerator
3、修改.build文件
在.build文件中追加target,如下
1 < target name ="mylib.test" 2 depends ="mylib" > 3 < csc target ="library" 4 output ="${file.lib.mylib.test}" 5 debug ="None" 6 optimize ="true" 7 define ="TRACE" 8 platform ="AnyCPU" 9 warninglevel ="4" 10 rebuild ="true" 11 filealign ="512" > 12 < sources > 13 < include name ="${dir.source.mylib.test}\**\*.cs" /> 14 </ sources > 15 < references > 16 < include name ="${file.lib.mylib}" /> 17 < include name ="${file.lib.nunit.framework}" /> 18 </ references > 19 </ csc > 20 < copy todir ="${dir.bin}" flatten ="true" > 21 < fileset > 22 < include name ="${file.lib.nunit.framework}" /> 23 </ fileset > 24 </ copy > 25 </ target > 26 < target name ="myexe.test" 27 depends ="myexe" > 28 < csc target ="library" 29 output ="${file.lib.myexe.test}" 30 debug ="None" 31 optimize ="true" 32 define ="TRACE" 33 platform ="AnyCPU" 34 warninglevel ="4" 35 rebuild ="true" 36 filealign ="512" > 37 < sources > 38 < include name ="${dir.source.myexe.test}\**\*.cs" /> 39 </ sources > 40 < references > 41 < include name ="${file.exe.myexe}" /> 42 < include name ="${file.lib.mylib}" /> 43 < include name ="${file.lib.nunit.framework}" /> 44 </ references > 45 </ csc > 46 < copy todir ="${dir.bin}" flatten ="true" > 47 < fileset > 48 < include name ="${file.lib.nunit.framework}" /> 49 </ fileset > 50 </ copy > 51 </ target > 52 < target name ="test" 53 depends ="mylib.test,myexe.test" > 54 < exec program ="OpenCover.Console.exe" basedir ="${dir.exe.opencover}" > 55 < arg value ="-register:user" /> 56 < arg value ="-target:${file.exe.nunit}" /> 57 < arg value ="-targetargs:${file.lib.myexe.test} ${file.lib.mylib.test} /result:${file.xml.test.result} /framework:net-3.5 /noshadow" /> 58 < arg value ="-output:${file.xml.test.coverage}" /> 59 </ exec > 60 < nunit2report format ="NoFrames" todir ="${dir.report}\NUnit" verbose ="true" > 61 < fileset > 62 < include name ="${file.xml.test.result}" /> 63 </ fileset > 64 </ nunit2report > 65 < mkdir dir ="${dir.report}" /> 66 < exec program ="ReportGenerator.exe" basedir ="${dir.exe.repotegenerator}" > 67 < arg value ="-reports:${file.xml.test.coverage}" /> 68 < arg value ="-targetdir:${dir.report}\OpenCover" /> 69 </ exec > 70 </ target >
根据之前介绍的内容,这些配置比较好理解了,下面还是挑需要注意的地方讲解一下。
csc的debug属性设置成了None,这是因为测试工程生成的dll不需要进行覆盖率计算,因此不必生成pdb文件。
出现了copy标签,顾名思义,用来拷贝文件。
需要注意flatten属性,这个属性设置成true的意思是,拷贝的文件,不考虑原文件的目录结构,而是直接把原文件拷贝到目标文件夹下。如果设置成false,会把要拷贝的原文件的目录结构一起带过来的呦~
exec标签,用来执行一个外部程序。本例中用来调用OpenCover和ReportGenerator。
需要注意的地方:
1)NUnit是通过OpenCover来调用的,使用的是OpenCover的-target和-targetargs参数。
其中,-targetargs用来提供NUnit的执行参数,这里有点绕,希望注意。
2)NUnit可以同时对多个dll执行测试,多个dll之间用空格隔开。
3)nunit2report标签用来根据单元测试结果xml文件生成单元测试报表。
format属性用来设定报表的形式,NoFrames表示将单元测试结果使用一个html文件来展示;而Frames会把各个单元测试项结果分别生成一个html。本例中是采用了生成单一文件的形式。
4)OpenCover生成的代码覆盖率计算结果文件是一个xml,需要交给ReportGenerator来生成报表
其他属性嘛,一目了然啊,不罗嗦啦。
五、完成NAnt构建配置
经过上述的配置,基本的自动化流程已经设置好啦。再根据需要进行一些细节处的调整。最终的.build文件如下:
View Code
1 <? xml version="1.0" encoding="utf-8" ?> 2 < project name ="myproject" default ="build" basedir ="." > 3 < property name ="nant.settings.currentframework" value ="net-3.5" /> 4 <!-- 需要利用到的工具 --> 5 < property name ="dir.exe.opencover" value ="D:\Tools\OpenCover" /> 6 < property name ="dir.exe.repotegenerator" value ="D:\Tools\ReportGenerator" /> 7 < property name ="file.lib.nunit.framework" value ="C:\Program Files\NUnit 2.6.2\bin\framework\nunit.framework.dll" /> 8 < property name ="file.exe.nunit" value ="C:\Program Files\NUnit 2.6.2\bin\nunit-console-x86.exe" /> 9 <!-- 源码路径 --> 10 < property name ="dir.source" value ="D:\source\myproject" /> 11 < property name ="dir.source.myexe" value ="${dir.source}\myexe" /> 12 < property name ="dir.source.myexe.test" value ="${dir.source}\myexe.test" /> 13 < property name ="dir.source.mylib" value ="${dir.source}\mylib" /> 14 < property name ="dir.source.mylib.test" value ="${dir.source}\mylib.test" /> 15 < property name ="file.ico.myexe" value ="${dir.source.exe}\myexe.ico" /> 16 <!-- 编译结果 --> 17 < property name ="dir.release" value ="D:\Release" /> 18 < property name ="dir.bin" value ="${dir.release}\bin" /> 19 < property name ="file.exe.myexe" value ="${dir.bin}\myexe.exe" /> 20 < property name ="file.lib.myexe.test" value ="${dir.bin}\myexe.test.dll" /> 21 < property name ="file.lib.mylib" value ="${dir.bin}\mylib.dll" /> 22 < property name ="file.lib.mylib.test" value ="${dir.bin}\mylib.test.dll" /> 23 < property name ="file.pdb.myexe" value ="${dir.bin}\myexe.pdb" /> 24 < property name ="file.pdb.mylib" value ="${dir.bin}\mylib.pdb" /> 25 <!-- 单元测试 --> 26 < property name ="dir.report" value ="${dir.release}\report" /> 27 < property name ="dir.result" value ="${dir.release}\result" /> 28 < property name ="file.xml.test.result" value ="${dir.result}\myproject-results.xml" /> 29 < property name ="file.xml.test.coverage" value ="${dir.result}\myproject-coverage.xml" /> 30 < target name ="build" 31 depends ="update,compile,test,clean" > 32 </ target > 33 < target name ="update" > 34 < svn command ="update" 35 destination ="${dir.source}" 36 uri ="http://192.168.1.1/myproject" 37 verbose ="true" 38 quiet ="false" 39 /> 40 </ target > 41 < target name ="compile" 42 depends ="mylib,mylib.test,myexe,myexe.test" > 43 </ target > 44 < target name ="mylib" > 45 < csc target ="library" 46 output ="${file.lib.mylib}" 47 debug ="Full" 48 optimize ="true" 49 define ="TRACE" 50 platform ="AnyCPU" 51 warninglevel ="4" 52 rebuild ="true" 53 filealign ="512" > 54 < sources > 55 < include name ="${dir.source.mylib}\**\*.cs" /> 56 </ sources > 57 </ csc > 58 </ target > 59 < target name ="mylib.test" 60 depends ="mylib" > 61 < csc target ="library" 62 output ="${file.lib.mylib.test}" 63 debug ="None" 64 optimize ="true" 65 define ="TRACE" 66 platform ="AnyCPU" 67 warninglevel ="4" 68 rebuild ="true" 69 filealign ="512" > 70 < sources > 71 < include name ="${dir.source.mylib.test}\**\*.cs" /> 72 </ sources > 73 < references > 74 < include name ="${file.lib.mylib}" /> 75 < include name ="${file.lib.nunit.framework}" /> 76 </ references > 77 </ csc > 78 < copy todir ="${dir.bin}" flatten ="true" > 79 < fileset > 80 < include name ="${file.lib.nunit.framework}" /> 81 </ fileset > 82 </ copy > 83 </ target > 84 < target name ="myexe" 85 depends ="mylib" > 86 < csc target ="winexe" 87 output ="${file.exe.myexe}" 88 debug ="Full" 89 optimize ="true" 90 define ="TRACE" 91 platform ="AnyCPU" 92 warninglevel ="4" 93 rebuild ="true" 94 filealign ="512" 95 win32icon ="${file.ico.myexe}" > 96 < sources > 97 < include name ="${dir.source.myexe}\**\*.cs" /> 98 </ sources > 99 < resources > 100 < include name ="${dir.source.myexe}\**\*.resx" /> 101 </ resources > 102 < references > 103 < include name ="${file.lib.mylib}" /> 104 </ references > 105 </ csc > 106 </ target > 107 < target name ="myexe.test" 108 depends ="myexe" > 109 < csc target ="library" 110 output ="${file.lib.myexe.test}" 111 debug ="None" 112 optimize ="true" 113 define ="TRACE" 114 platform ="AnyCPU" 115 warninglevel ="4" 116 rebuild ="true" 117 filealign ="512" > 118 < sources > 119 < include name ="${dir.source.myexe.test}\**\*.cs" /> 120 </ sources > 121 < references > 122 < include name ="${file.exe.myexe}" /> 123 < include name ="${file.lib.mylib}" /> 124 < include name ="${file.lib.nunit.framework}" /> 125 </ references > 126 </ csc > 127 < copy todir ="${dir.bin}" flatten ="true" > 128 < fileset > 129 < include name ="${file.lib.nunit.framework}" /> 130 </ fileset > 131 </ copy > 132 </ target > 133 < target name ="test" 134 depends ="mylib.test,myexe.test" > 135 < exec program ="OpenCover.Console.exe" basedir ="${dir.exe.opencover}" > 136 < arg value ="-register:user" /> 137 < arg value ="-target:${file.exe.nunit}" /> 138 < arg value ="-targetargs:${file.lib.myexe.test} ${file.lib.mylib.test} /result:${file.xml.test.result} /framework:net-3.5 /noshadow" /> 139 < arg value ="-output:${file.xml.test.coverage}" /> 140 </ exec > 141 < nunit2report format ="NoFrames" todir ="${dir.report}\NUnit" verbose ="true" > 142 < fileset > 143 < include name ="${file.xml.test.result}" /> 144 </ fileset > 145 </ nunit2report > 146 < mkdir dir ="${dir.report}" /> 147 < exec program ="ReportGenerator.exe" basedir ="${dir.exe.repotegenerator}" > 148 < arg value ="-reports:${file.xml.test.coverage}" /> 149 < arg value ="-targetdir:${dir.report}\OpenCover" /> 150 </ exec > 151 </ target > 152 < target name ="clean" > 153 < delete dir ="${dir.result}" /> 154 < delete > 155 < fileset > 156 < include name ="${file.lib.myexe.test}" /> 157 < include name ="${file.lib.mylib.test}" /> 158 < include name ="${file.pdb.myexe}" /> 159 < include name ="${file.pdb.mylib}" /> 160 < include name ="${dir.release}\bin\nunit.framework.dll" /> 161 </ fileset > 162 </ delete > 163 </ target > 164 </ project >
后记
本来觉得没什么内容,还特意选择了比较简单的场景用来演示,结果写了一下午啊。好吧,我承认我的效率比较低,哈哈。
遗憾之处是还没有集成StyleCop或者FxCop,等我学会了集成它们,再更新这篇文章。
总之,希望此文对需要的朋友有帮助。
文章如有疏漏之处,望读者不吝赐教,板砖粪蛋尽管招呼。
作者: Leo_wl
出处: http://www.cnblogs.com/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息