在.NET環境中實現每日構建(Daily Build)--NAnt篇

作者: 摩詰  來源: 博客園  發布時間: 2008-08-17 10:48  閱讀: 2197 次  推薦: 1   原文鏈接   [收藏]  

.NET環境中實現每日構建--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

VSSVisual Source Safe,微軟源碼管理工具

Draco.NET: 用于自動檢測VSS中源代碼變動情況,調用Nant完成自動編譯

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

下載所需的工具后,按照如下步驟進行安裝:

在服務器上安裝VSS源碼管理工具

安裝下載的Draco Server Draco Web,修改安裝后的Draco Web目錄下的web.config文件,設置正確的Draco Server安裝路徑

NAntNAntContribNUnit2Report壓縮包解壓,將三個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 "說明該任務執行前需要namecheckclean任務先執行

description描述了該任務的一些說明性信息

 

l          定義變量

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

如上所示,定義變量使用<property>標簽,name屬性定義了變量的名稱,value屬性定義變量的值,其中name屬性可以使用字母、數字、點號、下劃線等符號,而value屬性可以使用字符串或是已經定義的變量,Nant內建的函數等,

要使用已經定義的變量,可以用${<變量名>},要使用內建函數,可以使用${<函數名稱>}

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

使用了已定義變量core.basedirsolution.name來定義變量solution.basedir;

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

使用了NAnt內建函數directory::get-current-directory()來定義curdir變量

 

 

 

3.2.      定義環境變量

定義環境變量的腳本代碼寫在CommonConfig文件里

主要有以下幾類信息的定義:

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>

說明:deletemkdir標簽內的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源碼管理器上下載源代碼,userpassword屬性表示登錄VSS服務器的信息;Localpath屬性是指下載的源代碼存放的路徑;recursive="true"表示遞歸獲取代碼;replace="true"表示如果本地有重復文件,則進行覆蓋;dbpath定義VSSsrcsafe.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中提取出來的webproj1webproj2變量,查找該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項目,有2WEB項目,有一個WEB項目的三種情況處理

下面僅列出有兩個WEB項目的情況

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

                        <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"表明生成的文件擴展名為xmloutputdir指出了文件將被保存到哪個目錄

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"(此處略去詳細代碼)。

        

        下載示例代碼
1
0
 
標簽:Daily Build NAnt
 
 

文章列表

全站熱搜
創作者介紹
創作者 大師兄 的頭像
大師兄

IT工程師數位筆記本

大師兄 發表在 痞客邦 留言(0) 人氣()