zoukankan      html  css  js  c++  java
  • 在.NET环境中实现每日构建(Daily Build)NAnt篇

    前言 

    关于每日构建这个话题,也已经有很多很好的文章讨论了。本文的写作过程中也参考了这些文章。本文之所以继续这个题目,是因为在查阅了网上的资源后,发现没有一个比较通用的过程。所以本文就主要讨论了利用NAnt构建一个通用日编译的方案。利用这个方案,日编译的维护者可以不需要对每个要编译的方案都要做很多维护。只要定义一个属性文件就可以了。


     

    关键词: Daily Build, NAnt 


    1.       简介

    1.1.      每日构建的优点:

    每日构建(Daily Build)也可称为持续集成(Continuous Integration),强调完全自动化的、可重复的创建过程,其中包括每天运行多次的自动化测试。每日构建的作用日益显得重要。它让开发者可以每天进行系统集成,从而减少了开发过程中的集成问题。

    持续集成可以减少集成阶段"捉虫"消耗的时间,从而最终提高生产力。它使得绝大多数bug在引入的同一天就可以被发现。而且,由于一天之中发生变动的部分并不多,所以可以很快找到出错的位置。

    1.2.      每日构建完成的任务

    实现自动化每日构建需要做以下几部分的工作:

    l          使创建过程完全自动化,让任何人都可以只输入一条命令就完成系统的创建。

    l          使测试完全自动化,让任何人都可以只输入一条命令就运行一套完整的系统测试。

    l          确保所有人都可以得到最新、最好的可执行文件。

    2.       每日构建所使用的工具

    在.NET环境下建立每日构建可以使用一系列开源工具:

    Nant: 完成代码的自动编译,自动运行测试工具。http://nant.sourceforge.net/builds/

    NantContrib:自动从源码库中获取源代码。http://nantcontrib.sourceforge.net/nightly/builds/

    NUnit2Report:将NUnit测试工具产生的XML报告转换为HTML报告形式。http://NUnit2Report.sourceforge.net

    VSS:Visual Source Safe,微软源码管理工具

    Draco.NET: 用于自动检测VSS中源代码变动情况,调用Nant完成自动编译

          http://sourceforge.net/projects/draconet/

    下载所需的工具后,按照如下步骤进行安装:

    在服务器上安装VSS源码管理工具

    安装下载的Draco Server 和 Draco Web,修改安装后的Draco Web目录下的web.config文件,设置正确的Draco Server安装路径

    将NAnt、NAntContrib、NUnit2Report压缩包解压,将三个Bin目录中的内容复制到一个公用目录,比如D:\DailyBuildTools,然后将该路径加入系统的Path路径列表中,具体为“控制面板-〉系统属性-〉环境变量-〉Path”

    3.       NAnt自动脚本

    NAnt脚本实现了每日构建的主体功能,它具体分为下面几部分

    l          定义每日构建所需的一些环境变量,比如从VSS上下载的源码的保存目录,发布目录等

    l          清除旧的代码并从VSS源码库中下载最新源代码

    l          编译源代码并运行测试代码集

    l          将编译后的目标代码拷贝到发布目录进行发布

    为了尽可能少的改动NAnt的脚本文件,简化日常维护的工作量,我们把一些对所有项目都基本相同的过程抽取出来,如环境变量定义,清除旧代码获取新代码,编译源代码,对目标代码进行发布的过程都可以写成通用的脚本,而一个具体项目的每日构建脚本则调用通用过程完成

    本文采取的目录体系如下所示:

    D:\DailyBuild\

    <project1>\Source:存放<project1>源代码的目录

    <project1>\Build:存放<project1>编译后的目标代码的目录

    <project1>\Publish:存放<project1>的WEB发布文件的目录

    <project1>\log:存放<project1>的日志文件

     

    3.1.      Nant的基础知识

    l          Nant脚本代码文件的基本结构

    <?xml version="1.0" encoding="gb2312"?>

    <project name="Projects" default="prebuild">

           <target name="prebuild" depends="namecheck,clean " description="…">

                       ……

           </target>

           <target name="namecheck" >

                       ……

           </target>

    </project>

    说明:encoding="gb2312"使得脚本文件可以支持中文

    <project>标签定义了项目属性,一个脚本文件只能有一个项目定义

    default="prebuild"说明该项目缺省从prebuild任务开始执行

    <target>标签定义了一项任务,任务是Nant脚本具体执行动作的最小单元

    depends="namecheck,clean "说明该任务执行前需要namecheck和clean任务先执行

    description描述了该任务的一些说明性信息

    l          定义变量

    <property name="<变量名>" value="$<变量值>"/>

    如上所示,定义变量使用<property>标签,name属性定义了变量的名称,value属性定义变量的值,其中name属性可以使用字母、数字、点号、下划线等符号,而value属性可以使用字符串或是已经定义的变量,Nant内建的函数等,

    要使用已经定义的变量,可以用${<变量名>},要使用内建函数,可以使用${<函数名称>}

    如: <property name="solution.basedir" value="${core.basedir}\${solution.name}"/>

    使用了已定义变量core.basedir和solution.name来定义变量solution.basedir;

    <property name="curdir" value="${directory::get-current-directory()}"/>

    使用了NAnt内建函数directory::get-current-directory()来定义curdir变量

    3.2.      定义环境变量

    定义环境变量的脚本代码写在Common。Config文件里

    主要有以下几类信息的定义:

    l          每日构建所在的根目录

            <property name="curdir" value="${directory::get-current-directory()}"/>

            <property name="core.basedir" value="${curdir}"/>

    说明:${directory::get-current-directory()}内建函数获取当前文件所在路径信息

    l          被编译的解决方案的目录结构,和前面提到的目录体系一致

            <property name="solution.basedir" value="${core.basedir}\${solution.name}"/>

            <property name="solution.source" value="${solution.basedir}\source"/>

            <property name="solution.build" value="${solution.basedir}\build"/>

            <property name="solution.log" value="${solution.basedir}\log"/>

    说明:以上代码是定义了要编译的解决方案的目录结构信息,其中${solution.name}是由外部传入的解决方案的名称,后面的代码将根据该名称在日编译的根目录下生成和solution.name指定的名称同名的目录,并在该目录下生成source,buld,log等子目录

    l          VSS源代码管理系统的基本信息

    <!--vss数据库登录信息-->   

    <property name="vss.username" value="autobuild"/>

    <property name="vss.password" value="autobuild"/>

    <!--vss数据库所在的位置-->

    <property name="vss.dbpath" value="\\10.136.238.231\vss\srcsafe.ini"/>

    <!--vss中工程的根目录-->

    <property name="vss.basepath" value="$/"/>

    说明:定义了和VSS源码管理系统相关的一些信息,其中VSS数据库所在位置可以是网络路径,也可以是本地路径

    l                  <编译时的一些参数

    <!--编译版本号-->

    <property name="build.number" value="1.0"/>

    <!--决定编译是Debug版本还是Release版本-->

    <property name="build.configuration" value="Release"/>

     

    3.3.      建立目录结构,获取源代码

    脚本代码写在CheckSource.build.xml文件里

    l          包含在Common.config文件里定义的公共变量

    <include buildfile="common.config"/>

    l          检查是否存在solution.name变量

    <target name="namecheck" description="检查solution.name变量是否设置">

           <!--检查解决方案名称是否已经定义-->        

                       <ifnot test="${property::exists('solution.name')}">

                                   <fail message="未定义解决方案名称solution.name"/>

                       </ifnot>

                       <!--去掉可能的空格字符-->

                       <property name="solution.name" value="${string::trim(solution.name)}"/>

                       <!--检查solution.name变量是否为空字符-->

                       <if test="${string::get-length(solution.name)==0}">

                                   <fail message="未定义解决方案的名称solution.name"/>

                       </if>

    </target>

    说明:${property::exists('<变量名>')}是NAnt内建函数,用于测试某变量是否存在

    ${string::get-length(<字符串变量>)==0}测试字符串的长度是否为0

    <ifnot test=<逻辑表达式> … </ifnot>:如果test表达式值为假,执行<ifnot>标签内的代码

    <if test=<逻辑表达式> … </if>:如果test表达式值为假,执行<if>标签内的代码

    l          建立解决方案的目录结构

    <target name="clean" depends="namecheck" description="移除旧目录,建立新目录">

                       <!--删除旧的解决方案代码所在目录-->

                       <delete dir="${solution.basedir}" failonerror="false"/>

                       <!--重新建立目录-->

                       <mkdir dir="${solution.basedir}\" failonerror="false"/>

                       <mkdir dir="${solution.source}" failonerror="false"/>

                       <mkdir dir="${solution.build}" failonerror="false"/>

                       <mkdir dir="${solution.log}" failonerror="false"/>

    </target>

    说明:delete和mkdir标签内的failonerror属性表示即使操作文件夹的过程中出现了错误,也忽略错误向下执行

    l          获取源代码:

    从VSS上获取解决方案<solution.name>的源代码

    <target name="getsourcecode">

                  <!--检查从VSS上下载解决方案的路径是否设定-->               

                  <!-- 如果不定义vss.projectpath,则缺省为solution.name  -->

                  <ifnot test="${property::exists('vss.projectpath')}">

                              <property name="vss.projectpath" value="${solution.name}"/>

                  </ifnot>

                  <vssget

                  user="${vss.username}"

                  password="${vss.password}"

                  localpath="${solution.source}"

                  recursive="true"

                  replace="true"

                  dbpath="${vss.dbpath}"

                  path="${vss.basepath}${vss.projectpath}"

             />

    </target>

    说明:<vssget>标签是NAntContrib的语法,用来从VSS源码管理器上下载源代码,user和password属性表示登录VSS服务器的信息;Localpath属性是指下载的源代码存放的路径;recursive="true"表示递归获取代码;replace="true"表示如果本地有重复文件,则进行覆盖;dbpath定义VSS的srcsafe.ini文件的路径信息,包括srcsafe.ini文件名;path定义了要获取的源代码在VSS数据库中的路径,一般都是以$/为根目录。

    3.4.      编译源代码

    l          编译命令

    编译解决方案的命令为

    <solution  solutionfile="…" configuration="…" outputdir="…">

                            <webmap>

                                        <map url="… " path="…"/>

                                        <map url="… " path="…"/>

              </webmap>

    </solution>

    其中solutionfile属性表明了要编译的解决方案文件的路径信息,即以"sln"为扩展名的文件,

    configuration属性表明要编译的是发行版还是调试版,取值为"Release"或"Debug"

    outputdir表明了编译后的动态链接库或可执行文件存放的目录

    solution中的嵌套标签<webmap>用于当解决方案含有WEB项目的情况,有几个WEB项目,就有几项<map>标签,map标签中的url属性为WEB项目的*.csproj文件的WEB路径,path则为该*.csproj文件所在磁盘上的物理路径,例如,解决方案中有WEB项目exam,则map标签为 <map url="http://localhost/exam/exam.csproj" path="c:\exam\exam.csproj"

    l          根据解决方案名称获取解决方案文件的路径信息

    <target name="build" description="编译解决方案">

                            <!--       查找解决方案文件名         -->

                            <foreach item="File" property="filename">

                                        <in>

                                                    <items>

                                                                <include name="**\${solution.name}.sln"/>

                                                    </items>

                                        </in>

                                        <do>

                                                    <!--根据文件名设置解决方案的名称-->

                                                    <property name="solution.file" value="${filename}"/>

                                        </do>

                            </foreach>

    说明:<foreach>标签是NAnt中处理循环的命令,item="File"说明foreach进行循环处理的对象是文件,<include>中的name变量表示要查找的文件信息,"**\"表示查找路径包括子目录。Foreach的属性property="<变量名>"表示查找到的文件路径信息保存在该变量中,可以在<do>标签中引用.foreach每查找到一项符合条件的Item,都会执行<do>标签中的代码,以上代码执行的结果就是查找到指定名称的解决方案文件,供后面编译代码使用

    l          获取解决方案中WEB项目的路径信息

    如果解决方案中含有WEB项目,则其编译命令和不含WEB项目的解决方案编译有所区别,所以要区别对待。如果解决方案含有多个WEB项目,则可以让用户将多个WEB项目的名称放在一个变量中,如solution.webprojects,以逗号或分号或空格做分隔符。然后将项目名称分别提取出来,根据Web项目的个数决定solution命令的形式,代码如下

    <!--将solution.webprojects中用",",";"或" "分隔的Web工程名提取出来,

    分别设为webproject1,webproject2              -->

    <if test="${property::exists('solution.webprojects')}">

                <foreach item="String" in="${solution.webprojects}" delim=";, " property="project">

                <if test="${property::exists('webproject1')}">

                         <property name="webproject2" value="${project}"/>

                     </if>

                     <ifnot test="${property::exists('webproject1')}">

                         <property name="webproject1" value="${project}"/>

                     </ifnot>

                 </foreach>

    </if>

    以上代码中foreach标签的属性item="String" in="${solution.webprojects}" delim=";, " property="project"表明循环对象是字符串,对in所代表的字符串

    如果设定solution.webprojects="webprj1;webprj2”,则以上代码执行的结果是定义了两个变量webproject1 ="webproj1"和webproject2 ="webproj2"

    l          查找WEB工程名

    根据前面从solution.webprojects中提取出来的webproj1和webproj2变量,查找该WEB工程的文件名

    <!--       查找WEB工程文件名 -->

                <if test="${property::exists('webproject1')}">

                            <echo message="test ${webproject1}" />

                            <foreach item="File" property="filename">

                            <in>

                                        <items>

                                                    <include name="**\${webproject1}.csproj"/>

                                        </items>

                            </in>

                            <do>

                            <!--根据Web项目的名称获取Web项目文件路径,可以处理两个Web项目的情况-->

                                        <echo message="WebProject file=${filename}"/>

                <property name="webproject1.file" value="${filename}"/>

                            </do>

                </foreach>

    </if>

    同理可以处理存在第二个WEB工程项目的情况,设置webproject2.file变量

    l          编译解决方案

    最后是编译解决方案,分别根据无WEB项目,有2个WEB项目,有一个WEB项目的三种情况处理

    下面仅列出有两个WEB项目的情况

                            <!-- 存在2个Web工程 -->

                            <if test="${property::exists('webproject2')}">

                                        <solution

                                                    solutionfile="${solution.file}"

                                                    configuration="${build.configuration}"

                                                    outputdir="${solution.build}"

                                        >

                                                    <webmap>

                                                                <map

                                                                            url="http://localhost/${webproject1}/${webproject1}.csproj"

                                                                            path="${webproject1.file}"

                                                                />

                                                                <map

                                                                            url="http://localhost/${webproject2}/${webproject2}.csproj"

                                                                            path="${webproject2.file}"

                                                                />

                                                    </webmap>

                                        </solution>

                            </if>

    3.5.      运行测试代码

    l          测试命令

    NAnt中关于测试的命令是<NUnit2>标签

    <nunit2>

       <formatter type="Xml" usefile="true"

    extension=".xml" outputdir="…"

    />

                                        <test assemblyname="…" haltonfailure="false" />

    </nunit2>

    说明:<formatter>标签中,type="Xml"表明了根据测试结果生成XML结构化信息,usefile="true"表明使用文件保存测试结果,extension=".xml"表明生成的文件扩展名为xml,outputdir指出了文件将被保存到哪个目录

    Test标签中的assemblyname表明了被测试的dll程序集的路径信息,haltonfailure="false"表明即使测试没有通过仍然继续执行脚本文件

    这样在测试命令完成后,会在outputdir指出的目录下生成一个XML形式的报告文件,为了增加测试结果的可读性,可以使用另一个工具NUnit2Report,将测试结果转换为直观的HTML文件。具体命令如下

    <nunit2report out="<文件名>" todir="<输出目录" >

            <fileset>

                                        <includes name="<文件匹配符>" />

            </fileset>

    </nunit2report>

    说明:includes标签用来搜索符合条件的XML文件,转换出来的HTML文件保存为out指出的文件名,todir指出了HTML文件将保存的目录信息

    <if test="${property::exists('solution.testprojects')}">

    <foreach item="String" in="${solution.testprojects}" delim=";, " property="project">

            <property name="testfile" value="${solution.build}\${project}.dll"/>

            <nunit2>

    <formatter type="Xml" usefile="true"

     extension=".xml" outputdir="${solution.build}" />

                                    <test assemblyname="${testfile}" haltonfailure="false" />

    </nunit2>

    <nunit2report out="${project}.html" todir="${solution.log}" >

                        <fileset>

                                    <includes name="${solution.build}\*.xml" />

                        </fileset>

            </nunit2report>

    </foreach>

    </if>

    3.6.      进行WEB发布

    WEB发布主要针对有WEB工程项目的解决方案,其实现原理为利用NAnt的拷贝命令,将WEB工程下除了源代码,资源代码,VSS信息文件外的其他文件和编译后的程序集拷贝到发布目录,最后设置WEB虚拟路径以供WEB访问的过程。

    设置WEB虚拟路径的命令为

    <mkiisdir dirpath="<物理路径>" vdirname="<虚拟路径>"/>

    说明:设WEB项目发布在C:\Intepub\wwwroot\Exam,访问该WEB项目用地址http://127.0.0.1/Example/default.aspx,则<物理路径>为"C:\Intepub\wwwroot\Exam",虚拟路径为"Example"(此处略去详细代码)。

            

            下载示例代码

    引用自 :http://dragon.cnblogs.com/archive/2005/07/29/203189.html

  • 相关阅读:
    自定义View的ToolBar布局报错Error:(2) No resource identifier found for attribute 'context' in package 'c
    在学git之主分支 branch
    获取发布版SHA1
    关于开启线程与UI的操作
    播放音频和视频(VideoView控件)
    通知栏Notification的应用
    Android 真机调式 Installation failed with message 远程主机强迫关闭了一个现有的连接。. It is possible that this issue is resolved by uninstalling an existing version of the apk if it is present, and then re-installing. WA
    运行程序申请危险权限
    mysql乐观锁总结和实践
    Nginx配置文件nginx.conf中文详解
  • 原文地址:https://www.cnblogs.com/zhangchenliang/p/2345138.html
Copyright © 2011-2022 走看看