Simon Green's Developer Blog
Developing .NET in the cold white north ...

Build versioning with NAnt, Draco.NET and SourceGear Vault

Friday, 21 October 2005 16:33 by Simon

One aim of continuous integration should be to automate as much of the build process as possible. If something is left to human intervention then inevitably it will at some point be completed incorrectly or inconsistently or forgotten completely.

Part of a good build process is to update the version number of the application so that each build (triggered by changes in the source) will have a unique number and the source control system should label the source so that the code that created that build can be identified. This is my solution to updating the version number when using NAnt, Draco.Net and SourceGear Vault.

The build process consists of the following:

Draco.Net monitors the project folder in the SourceGear Vault repository.
A developer checks in some changes which causes Vault to update the folder version number (based on the changeset).
Draco.Net detects the changes and gets the latest version of the project to a temporary build folder
The NAnt .build file (part of the project) is started by Draco.Net
Within NAnt a custom function gets the folder version from Vault and sets the revision part of the version number based on it
The project folder in Vault is labeled with the build revision number
The assembly is compiled using the version number by overriding the AssemblyInfo.cs file (using the standard <adminfo> task)
The NUnit tests are run and reports produced, MSI files created etc.
Everything is pretty straightforward using the standard functions of NAnt, Draco.Net and the SourceGear Vault NAnt Tasks other than step #5 which required a custom NAnt function to return the folder tree version number. The code for this is below which I added to the Vault NAnt Tasks project (I'm not sure yet of the process for feeding this into the project as an update):

<?xml version="1.0"?>
<project name="NUnitAspInjector" default="build" basedir=".">
 <!-- set by Draco.Net for automated builds -->
 <property name="draco" value="false"/>
 <property name="build.id" value="0"/>
 <!-- project properties -->
 <property name="project.name" value="NUnitAspInjector"/>
 <!-- base project version for this trunk / branch -->
 <property name="build.version.major" value="1"/>
 <property name="build.version.minor" value="0"/>
 <property name="build.version.build" value="5000"/>
 <property name="build.version.revision" value="0"/>
 <!-- if automated build then set revision number based on version in vault -->
 <if test="${draco}">
  <!-- vault parameters (set in Draco.Net project properties) -->
  <!--  <property name="vault.url"  value="http://localhost"/>  <property name="vault.repository" value="default"/>  <property name="vault.path"  value="$/NUnitAspInjector/trunk"/>  <property name="vault.username"  value="build"/>  <property name="vault.password"  value="password"/>   -->
  <!-- set revision number based on tree version from vault -->
  <property name="build.version.revision" value="${vault::getversion(vault.url, vault.repository, vault.path, vault.username, vault.password)}"/>
  <!-- label the build -->
  <!-- in case a build is forced, this avoids a duplicate label error -->
  <vaultdeletelabel    url="${vault.url}"    username="${vault.username}"    password="${vault.password}"    repository="${vault.repository}"    path="${vault.path}"    labelstring="build ${build.version.revision}"/>
  <!-- apply the label to the source folder -->
  <vaultlabel    url="${vault.url}"    username="${vault.username}"    password="${vault.password}"    repository="${vault.repository}"    path="${vault.path}"    labelstring="build ${build.version.revision}"/>
 </if>
 <!-- set the full build version based on major.minor.build.revision -->
 <property name="build.version" value="${build.version.major}.${build.version.minor}.${build.version.build}.${build.version.revision}"/>
 <!-- output directory -->
 <property name="build.outdir" value="C:\working\buildoutput\${project.name}\${build.version}"/>
 <!-- target .NET framework and corresponding compiler directives -->
 <property name="nant.settings.currentframework" value="net-1.1"/>
 <property name="csc.define" value="NET11"/>
 <target name="build">
  <echo message="building: ${project.name} ${build.version}"/>
  <echo message="build id: ${build.id}" if="${draco}"/>
  <!-- build the solution in debugh mode so that the test are created -->
  <!-- this is a shortcut until a proper test project is created -->
  <solution solutionfile="src\NUnitAspInjector.sln" configuration="debug">
   <webmap>
    <map url="http://localhost/NUnitAspInjector.Sample/" path="src\NUnitAspInjector.Sample\"/>
   </webmap>
  </solution>
  <!-- make a folder for the assembly -->
  <mkdir dir="bin"/>
  <!-- if automated build then create assembly version info -->
  <if test="${draco}">
   <attrib file="src\NUnitAspInjector\AssemblyInfo.cs" readonly="false"/>
   <asminfo output="src\NUnitAspInjector\AssemblyInfo.cs" language="CSharp">
    <imports>
     <import name="System" />
     <import name="System.Reflection" />
     <import name="System.EnterpriseServices" />
     <import name="System.Runtime.InteropServices" />
    </imports>
    <attributes>
     <attribute type="ApplicationNameAttribute" value="${project.name}" />
     <attribute type="AssemblyProductAttribute" value="${project.name}" />
     <attribute type="AssemblyTitleAttribute" value="InteSoft ${project.name} ${build.version.major}.${build.version.minor} for .NET 1.1" />
     <attribute type="AssemblyDescriptionAttribute" value="${project.name} for ASP.NET" />
     <attribute type="AssemblyCompanyAttribute" value="InteSoft IT Ltd." />
     <attribute type="AssemblyCopyrightAttribute" value="Copyright &#x00A9; 2005 InteSoft IT Ltd." />
     <!-- the version stays at x.x.x.0 for binding -->
     <attribute type="AssemblyVersionAttribute" value="${build.version.major}.${build.version.minor}.${build.version.build}.0" />
     <!-- the information versions show the revision number -->
     <attribute type="AssemblyFileVersionAttribute" value="${build.version}" />
     <attribute type="AssemblyInformationalVersionAttribute" value="${build.version}" />
     <attribute type="AssemblyDelaySignAttribute" value="false" />
     <attribute type="AssemblyKeyFileAttribute" value="" />
     <attribute type="AssemblyKeyNameAttribute" value="" />
     <attribute type="CLSCompliantAttribute" value="true" />
     <attribute type="ComVisibleAttribute" value="false" />
    </attributes>
    <references>
     <include name="System.EnterpriseServices.dll" />
    </references>
   </asminfo>
  </if>
  <!-- now build the proper release version of our component -->
  <csc target="library" output="bin\NUnitAspInjector.dll" debug="false" define="${csc.define};" optimize="true" rebuild="true">
   <sources basedir="src\NUnitAspInjector">
    <include name="AssemblyInfo.cs"/>
    <include name="Gdi32.cs"/>
    <include name="Handler.cs"/>
    <include name="Html2Image.cs"/>
    <include name="InjectorModule.cs"/>
    <include name="InjectorWebFormTestCase.cs"/>
    <include name="RenderAssertion.cs"/>
    <include name="TestHostAttribute.cs"/>
    <include name="TestInjectorAttribute.cs"/>
    <include name="TestRenderAttribute.cs"/>
    <include name="TestServer.cs"/>
    <include name="cassini\ByteParser.cs"/>
    <include name="cassini\ByteString.cs"/>
    <include name="cassini\Connection.cs"/>
    <include name="cassini\Host.cs"/>
    <include name="cassini\Messages.cs"/>
    <include name="cassini\Request.cs"/>
    <include name="cassini\Server.cs"/>
   </sources>
   <resources prefix="NUnit.Extensions.Asp" basedir="src\NUnitAspInjector">
    <include name="Html2Image.resx"/>
   </resources>
   <references>
    <include asis="true" name="System.Web.dll"/>
    <include asis="true" name="System.Web.Services.dll"/>
    <include asis="true" name="System.Runtime.Remoting.dll"/>
    <include asis="true" name="System.XML.dll"/>
    <include asis="true" name="System.Windows.Forms.dll"/>
    <include asis="true" name="System.Drawing.dll"/>
    <include asis="true" name="lib\AForge.Imaging.dll"/>
    <include asis="true" name="lib\AForge.Math.dll"/>
    <include asis="true" name="lib\nmock.dll"/>
    <include asis="true" name="lib\nunit.framework.dll"/>
    <include asis="true" name="lib\NUnitAsp.dll"/>
    <include asis="true" name="lib\Interop.SHDocVw.dll"/>
    <include asis="true" name="lib\AxInterop.SHDocVw.dll"/>
    <include asis="true" name="lib\Microsoft.mshtml.dll"/>
   </references>
  </csc>
  <!-- copy this release version to the test app -->
  <copy file="bin\NUnitAspInjector.dll" todir="src\NUnitAspInjector.Sample\bin" overwrite="true"/>
  <!-- run the NUnit tests -->
  <nunit2 failonerror="true">
   <formatter type="Xml" usefile="true" extension=".xml" outputdir="${build.outdir}"/>
   <test assemblyname="src\NUnitAspInjector.Sample\bin\NUnitAspInjector.Sample.dll" appconfig="src\NUnitAspInjector.Sample\web.config">
    <categories>
     <exclude name="IIS"/>
    </categories>
   </test>
  </nunit2>
  <!-- create NUnit report -->
  <nunit2report out="NUnitReport.htm" todir="${build.outdir}" opendesc="true">
   <fileset>
    <include name="${build.outdir}\NUnitAspInjector.Sample.dll-results.xml"/>
   </fileset>
  </nunit2report>
  <!-- copy this release version to the output folder -->
  <copy file="bin\NUnitAspInjector.dll" todir="${build.outdir}" overwrite="true"/>
 </target>
</project>

The key line is this:

<!-- set the revision number based on a build startdate -->
<property name="build.startdate" value="01 Oct 2005"/>
<script language="C#">
 <imports>
  <import namespace="System.Globalization"/>
  <import namespace="System.Threading"/>
 </imports>
 <code>
 <![CDATA[
  public static void ScriptMain(Project project)
  {
   DateTime start = Convert.ToDateTime(project.Properties["build.startdate"]);   
   Calendar calendar = Thread.CurrentThread.CurrentCulture.Calendar;   
   int months = ((calendar.GetYear(DateTime.Today) - calendar.GetYear(start)) * 12) + calendar.GetMonth(DateTime.Today) - calendar.GetMonth(start);   
   int day = DateTime.Now.Day;   
   int revision = (months * 100) + day;   
   project.Properties["build.version.revision"] = revision.ToString();  
  }  
 ]]>
 </code>
</script>

However, I like the fact that the build number only changes when the source changes (due to checkins) rather than incrementing simply due to time. Also, with Vault, the version number of the folder ends up matching the build label applied to it which just seems neat. I like neat :)

That's it. Hope someone finds this of some use!

 

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:   , , ,
Categories:   .NET | Build
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

Windows 2003 SP1 .NET Encoding Issue

Tuesday, 11 October 2005 16:31 by Simon

We recently came across an issue with one of our components (ASPRedirector.NET) where a customer who had installed Windows 2003 SP1 started getting errors on their website. The errors only happened if they used a certain encoding in the globalisation settings:

public class Filter : Stream
 {
  private Encoder encoder;
  private Decoder decoder;
  private Stream baseStream;

  public Filter(HttpContext context)
  {
   Encoding encoding = context.Response.ContentEncoding;
   this.encoder = encoding.GetEncoder();
   this.decoder = encoding.GetDecoder();
   this.baseStream = context.Response.Filter;
  }

  public override bool CanRead
  {
   get { return false; }
  }

  public override bool CanWrite
  {
   get { return true; }
  }

  public override bool CanSeek
  {
   get { return false; }
  }

  public override void Close()
  {
   baseStream.Close();
  }

  public override void Flush()
  {
   baseStream.Flush();
  }

  public override long Length
  {
   get { throw new NotSupportedException(); }
  }

  public override long Seek(long offset, SeekOrigin origin)
  {
   throw new NotSupportedException();
  }

  public override void SetLength(long value)
  {
   throw new NotSupportedException();
  }

  public override long Position
  {
   get { throw new NotSupportedException(); }
   set { throw new NotSupportedException(); }
  }

  public override int Read(byte[] buffer, int offset, int count)
  {
   throw new NotSupportedException();
  }

  public override void Write(byte[] buffer, int offset, int count)
  {
   // get number of characters that the byte array will be decoded into
   int charCount = this.decoder.GetCharCount(buffer, offset, count);
   // create character array to hold them
   char[] chars = new char[charCount];
   // decode byte array into characters
   int charCountDecoded = decoder.GetChars(buffer, offset, count, chars, 0);

   // we now have characters and can process them
   // converting to a string should be the readable content
   string content = new string(chars);

   // get number of bytes that the character array will be encoded into
   int byteCount = encoder.GetByteCount(chars, 0, charCountDecoded, true);
   // create byte array to hold them
   byte[] bytes = new byte[byteCount];
   // encode character array into bytes
   int byteCountEncoded = encoder.GetBytes(chars, 0, charCountDecoded, bytes, 0, true);

   // write bytes to output stream - this should be exactly what came in
   baseStream.Write(bytes, 0, byteCountEncoded);
  }
 }

This fails if the responseEncoding is set to "iso-8859-15" after Windows 2003 SP1 has been applied but works before SP1 and if a different encoding is used (the default "utf-8" for instance).

So what's the problem? I found a lone post in a newsgroup that explained it:

SP1 of Windows Server introduced a new "feature" in the GlobalizationConfig class called EnableBestFitResponseEncoding.

Effect is that when you set anything other than UTF8 in web.config, e.g. <globalization requestEncoding="iso-8859-1" responseEncoding="iso-8859-1" />, many functions will break (including HttpUtility.URLDecode) with a MethodNotImplementedExecption in the new internal class CodePageNoBestFitEncoding.

Analyzing with the reflector reveals, that new code was added to the HttpResponse.ContentEncoding property:

                  if (!this._encoding.Equals(Encoding.UTF8))
                  {
                        string text1 = this._encoding.GetType().FullName;
                        if ((config1 == null) || !config1.EnableBestFitResponseEncoding)
                        {
                              if (text1 == "System.Text.MLangCodePageEncoding")
                              {
                                    this._encoding = Encoding.GetEncoding("mlang");
                              }
                              else if ((text1 == "System.Text.CodePageEncoding") || (text1 == "System.Text.Latin1Encoding"))
                              {
                                    int num1 = this._encoding.CodePage;
                                    this._encoding = new CodePageNoBestFitEncoding(num1);
                              }
                        }

Assuming that EnableBestFitResponseEncoding is false by default, this property will return a new instance of CodePageNoBestFitEncoding when you set a responseEncoding="iso-8859-1" in web.config.

Many of the normal Encoding methods in CodePageNoBestFitEncoding will throw the MethodNotImplemented exception which explains the above mentioned bahavior.

Some additional analysis reveals that there is a new attribute for <globalization > called "enableBestFitResponseEncoding". After setting this attribute to "true", everthing works again as normal with a responseEncoding other than UTF8.

A search in Microsoft's site for "enableBestFitResponseEncoding" returns 0 hits. Thank you Microsoft!

 

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:   , ,
Categories:   .NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed

NUnitAspInjector Project

Sunday, 9 October 2005 16:29 by Simon

This is a helper library to make unit testing with NUnit, NUnitAsp and NMock easier. It builds on some of the ideas here and also adds some new features such as the ability to create unit tests that match the rendered output of a page or control against a base-line image. TDD for HTML and CSS !

I still have to do a complete write-up on how to use it but for those that like to download and explore, here it is:

http://www.intesoft.net/NUnitAspInjector/

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:   , ,
Categories:   .NET
Actions:   E-mail | del.icio.us | Permalink | Comments (0) | Comment RSSRSS comment feed