Step 2 Build on Check-in

This is known as a CI-build : a Continuous Integration build. The idea of these kind of builds are to detect errors as soon as possible :
° code must compile (all references are in source control)
° basic functionality works (unit tests, this is covered in Step 3 Add unit tests)

Article at wikipedia about CI

Below are the settings for ccnet.config and the nant build script.
If you use these, whenever a modification is done, a build will be triggered. (check interval is 30 seconds)

CCNet.config

We want to keep this as clean as possible, so it's advised to use the Configuration Preprocessor as much as possible.
This also enforces all projects to have the same setup and lay-out, which is also a big benefit: you immediately know where the build files and build-artifacts are.
The following setup allows me to add CCNet projects with just 29 lines, white space included.

 1<cb:scope ProjectName="ProjectX">
 2  <cb:define ProjectType="_CI" />
 3  <project name="$(ProjectName)$(ProjectType)" queue="Q1" queuePriority="901">
 4      <workingDirectory>$(WorkingDir)$(ProjectName)$(ProjectType)</workingDirectory>
 5      <artifactDirectory>$(WorkingMainDir)$(ProjectName)$(ProjectType)$(ArtifactsDir)</artifactDirectory>
 6
 7      <labeller type="defaultlabeller" />
 8
 9      <intervalTrigger name="continuous" seconds="30" buildCondition="IfModificationExists" initialSeconds="30" />
10
11      <sourcecontrol type="vsts">
12       <workspace>$(ProjectName)</workspace>
13       <project>$/$(ProjectName)/Trunk</project>
14       <cb:vsts_ci/>          
15      </sourcecontrol> 
16
17      <tasks>
18      <nant>
19         <cb:nant_CI/>
20         <cb:nant_target_CI />
21      </nant>    
22      </tasks>
23
24      <publishers>
25      <cb:common_publishers />
26      </publishers>
27
28  </project>
29</cb:scope>

As you see, I use the TFS project name as a prefix for the CCNet project name, the type of project (CI) is appended.

The nant section determines what nant-targets get called, for this CI-setup that is clean and compile .
The complete ccnet.config :

 1<cruisecontrol xmlns:cb="urn:ccnet.config.builder">
 2<!-- preprocessor settings -->
 3<cb:define WorkingDir="D:\WorkingFolders\" />
 4<cb:define WorkingMainDir="D:\ArtifactFolders\" />
 5<cb:define ArtifactsDir="\Artifacts" />
 6
 7<!-- TFS settings for a CI build -->
 8<cb:define name="vsts_ci">
 9    <server>http://tfs-server:8080/tfs/default/</server>
10    <username>cruise</username>
11    <password>**********</password>
12    <domain>tfs-server</domain>
13    <autoGetSource>true</autoGetSource>
14    <cleanCopy>true</cleanCopy>
15    <force>true</force>
16    <deleteWorkspace>true</deleteWorkspace>
17</cb:define>
18
19<!-- all projects will have at least these publishers  -->
20<cb:define name="common_publishers">
21    <xmllogger />
22    <statistics />
23    <modificationHistory  onlyLogWhenChangesFound="true" />
24    <artifactcleanup      cleanUpMethod="KeepLastXSubDirs"   cleanUpValue="2" />
25    <artifactcleanup      cleanUpMethod="KeepLastXBuilds"    cleanUpValue="25000" />
26    <!-- email the breakers on a broken build and when it's fixed -->
27    <email from="CruiseControl@TheBuilder.com" 
28          mailhost="TheMailer.Company.com" 
29          includeDetails="TRUE">
30        <groups/>
31        <users/>
32        <converters>
33            <ldapConverter domainName="Company"  />
34        </converters> 
35        <modifierNotificationTypes>
36           <NotificationType>Failed</NotificationType>
37           <NotificationType>Fixed</NotificationType>
38        </modifierNotificationTypes>
39    </email> 
40</cb:define>
41
42<!-- the Nant build arguments for a CI build  -->
43<cb:define name="nant_CI">
44  <executable>c:\Tools\nant\bin\nant.exe</executable>
45  <nologo>true</nologo>
46  <buildTimeoutSeconds>1800</buildTimeoutSeconds>
47  <buildArgs>-D:useExtraMsbuildLogger=true  -D:isCI=true  -listener:CCNetListener,CCNetListener -D:configuration=Debug</buildArgs>
48</cb:define> 
49
50<!-- the nant  targets for a CI build -->
51<cb:define name="nant_target_CI">
52   <targetList>
53        <target>clean</target>
54        <target>compile</target>
55   </targetList>
56</cb:define> 
57
58<!-- end preprocessor settings -->
59
60<!-- Projects -->
61 <cb:scope ProjectName="ProjectX">
62   <cb:define ProjectType="_CI" />
63    <project name="$(ProjectName)$(ProjectType)" queue="Q1" queuePriority="901">
64      <workingDirectory>$(WorkingDir)$(ProjectName)$(ProjectType)</workingDirectory>
65      <artifactDirectory>$(WorkingMainDir)$(ProjectName)$(ProjectType)$(ArtifactsDir)</artifactDirectory>
66
67      <labeller type="defaultlabeller" />
68
69      <intervalTrigger name="continuous" seconds="30" buildCondition="IfModificationExists" initialSeconds="30" />
70
71      <sourcecontrol type="vsts">
72          <workspace>$(ProjectName)</workspace>
73          <project>$/$(ProjectName)/Trunk</project>
74          <cb:vsts_ci/>          
75      </sourcecontrol> 
76
77      <tasks>
78         <nant>
79            <cb:nant_CI/>
80            <cb:nant_target_CI />
81         </nant>    
82      </tasks>
83
84      <publishers>
85         <cb:common_publishers />
86      </publishers>
87
88    </project>
89  </cb:scope>
90
91<!-- End Projects -->
92</cruisecontrol>

Nant Build Script

This script does the actual action, clean, compile, build, test, packaging and all the like. We'll start with the basics first :
clean and compile. I use Nant for the script logic, the actual compile is a call to MS-Build.

The script is also made for re-use, I have the following as a template and just change the solution name to match the correct solution. The build file is than added to source control, meaning all stuff needed to build is in source control, directly in the trunk main folder.
The build script also includes a function that changes the file version attribute from a file called VersionInfo.cs . Meaning that when your VS-Projects use this file as a linked file, all assemblies will have the same file version. We use the label from CCNet as the version number for the assemblies, so be sure that your labeller generates numbers only, and in a format that's compatible with the .Net version structure.

Feel free to adjust your build script to your needs, this is just a basic one, but it's sufficient for most setups.

  1<project default="help">
  2    <property name="solution"                           unless="${property::exists('solution')}"                            value="ProjectX.sln" />    
  3    <property name="configuration"                      unless="${property::exists('configuration')}"                       value="Debug"        />    
  4    <property name="CCNetListenerFile"                  unless="${property::exists('CCNetListenerFile')}"                   value="listen.xml"    />    
  5    <property name="msbuildverbose"                     unless="${property::exists('msbuildverbose')}"                      value="normal"       />    
  6    <property name="CCNetLabel"                         unless="${property::exists('CCNetLabel')}"                          value="0.0.0.0"      />     
  7
  8    <property overwrite="false" name="msbuildlogger"    value="C:\Program Files\CruiseControl.NET\server\MSBuildListener.dll"                     />
  9    <property overwrite="false" name="versionInfofile"  value="VersionInfo.cs" />
 10
 11    <!-- custom scripts --> 
 12     <script language="C#" prefix="RuWi">
 13          <references>
 14              <include name="System.Xml.dll" />
 15              <include name="System.dll" />
 16          </references>
 17          <imports>
 18              <import namespace="System.Text" />
 19          </imports>
 20          &lt;code&gt;
 21            &lt;![CDATA[
 22                [Function("UpdateVersionFile")]
 23                public static bool UpdateVersionFile(string inputFile, string newVersion, bool debugMode)
 24                {
 25                    bool ok = true;
 26                    try
 27                    {
 28                        System.IO.StreamReader versionFile = new System.IO.StreamReader(inputFile, System.Text.Encoding.ASCII);
 29                        string line = "";
 30                        System.Text.StringBuilder result = new StringBuilder();
 31                        string searchPatternVersion = @"(\d+\.\d+\.\d+\.\d+)";
 32                        string searchPatternAssemblyProduct = string.Format(@"AssemblyProduct\({0}(.*?)\{0}", "\"");
 33                        string replacePatternAssemblyProduct = string.Format(@"AssemblyProduct({0}(Debug)${1}1{2}{0}", "\"", "{", "}");
 34
 35                        while (!versionFile.EndOfStream)
 36                        {
 37                            line = versionFile.ReadLine();
 38
 39                            if (System.Text.RegularExpressions.Regex.IsMatch(line, searchPatternVersion) && (line.Contains("AssemblyFileVersion")))
 40                            {
 41                                line = System.Text.RegularExpressions.Regex.Replace(line, searchPatternVersion, newVersion);
 42                            }
 43
 44                            if (debugMode && System.Text.RegularExpressions.Regex.IsMatch(line, searchPatternAssemblyProduct))
 45                            {
 46                                line = System.Text.RegularExpressions.Regex.Replace(line, searchPatternAssemblyProduct, replacePatternAssemblyProduct);
 47                            }
 48
 49                            result.AppendLine(line);
 50                        }
 51
 52                        versionFile.Close();
 53
 54                        System.IO.StreamWriter updatedVersionfile = new System.IO.StreamWriter(inputFile);
 55                        updatedVersionfile.Write(result.ToString());
 56                        updatedVersionfile.Close();
 57                    }
 58                    catch (Exception ex)
 59                    {
 60                        ok = false;
 61                        Console.WriteLine(ex.ToString());
 62                    }
 63                    return ok;
 64                }
 65                ]]&gt;
 66          &lt;/code&gt;
 67     &lt;/script&gt;
 68    &lt;!-- end custom scripts --&gt; 
 69
 70    &lt;target name="help" &gt;   
 71        &lt;echo message="Removed for keeping the file shorter." /&gt;
 72    &lt;/target&gt;
 73
 74    &lt;target name="clean" description="deletes all created files"&gt;
 75        &lt;delete &gt;
 76            &lt;fileset&gt;
 77                &lt;patternset &gt;
 78                    &lt;include name="**/bin/**"  /&gt;
 79                    &lt;include name="**/obj/**"  /&gt;
 80                &lt;/patternset&gt;
 81            &lt;/fileset&gt;
 82        &lt;/delete&gt;
 83    &lt;/target&gt;
 84
 85    &lt;target name="adjustversion" description="Adjusts the version in the version.info file"&gt;
 86        &lt;if test="${not file::exists(versionInfofile)}"&gt;
 87            &lt;fail message="file: ${versionInfofile}  which must contains the version info was NOT found" /&gt;
 88        &lt;/if&gt;
 89
 90        &lt;echo message="Setting version to ${CCNetLabel}" /&gt;        
 91
 92        &lt;property name="debugMode" value = "False" /&gt;
 93        &lt;property name="debugMode" value = "True"  if="${configuration=='Debug'}"  /&gt;
 94        &lt;if test="${not RuWi::UpdateVersionFile(versionInfofile,CCNetLabel,debugMode)}"&gt;
 95            &lt;fail message="updating file: ${versionInfofile}  which must contains the version info failed" /&gt;
 96        &lt;/if&gt;
 97    &lt;/target&gt;
 98
 99    &lt;target name="compile" description="compiles the solution in the wanted configuration" depends="adjustversion"&gt;                         
100        &lt;msbuild  project="${solution}" &gt;
101            &lt;arg value="/p:Configuration=${configuration}" /&gt;
102            &lt;arg value="/p:CCNetListenerFile=${CCNetListenerFile}" /&gt;
103            &lt;arg value="/v:${msbuildverbose}" /&gt;
104            &lt;arg value="/l:${msbuildlogger}" /&gt; 
105        &lt;/msbuild&gt;
106    &lt;/target&gt;
107
108&lt;/project&gt;    
109

Previous -- Next