My Blahg

August 27, 2008

NAnt and Assembly Versioning

Filed under: c#, Continuous Integration/CruiseControl.NET, dotnet, NAnt — treyhutcheson @ 12:25 pm

A very popular NAnt topic is that of versioning one’s assemblies. Over the years, I’ve read many different posts on this topic, and each has its own method of performing versioning. Regardless, all share the common goal: generate an assembly version as part of the build process.

A couple of years ago I developed my owner versioning process. It’s mostly self-contained as a set of custom NAnt tasks, called from my build scripts. There’s an additional step of synchronizing the version with the repository, but that step is outside the scope of this post.

At a high level, this is how I manage the version information for a build:
1 – Read the current version number from an external resource/file
2 – Increment the version number
3 – Write the version information back to the external file
4 – Generate a c# source file that contains the version number

And here is an example script that demonstrates these steps:

<loadtasks assembly="CustomNAntTasks.Versioning.dll" />

<property name="version.offset" value="0.0.1.0" />

<!-- get the version -->
<getautoversion filename="version.txt" property="version" />

<!-- increment the version -->
<echo>Incrementing version ${version} by offset ${version.offset}</echo>
<property name="version" value="${version::offset-version(version,version.offset)}" />

<echo>Version is now ${version}</echo>

<!-- write the version back out -->
<setautoversion filename="version.txt" property="version" />

<!-- now generate a new file -->
<generateassemblyinfoversionfile filename="AssemblyInfoVersion.cs" property="version"
	set-file-version="true" set-asm-version="true" overwrite="true" />

Custom Task: getautoversion
After the script loads the custom task dll, it defines a property named “property.offset”. This is the value by which the current version will be offset. For example, if the current version is 1.0.0.0, and the offset is 0.0.1.0, then the combined version number is 1.0.1.0.

Next, the script calls the first custom task: getautoversion. This task simply reads the “version.txt” file and places the contents into a property named “version”. The value in the version.txt file must follow the Major.Minor.Build.Revision format used by dotnet. In fact, the string read from the file is actually passed to the constructor of a System.Version object at runtime; so if the version format is not correct, we’ll get a runtime exception.

Custom Functions: Incrementing the version
The next step is to somehow augment/offset/increment the current version number. This is implemented as a single function called “offset-version” (implemented in the VersionFunctions class). This function takes the current version number, as a string, in version form, and an offset to apply the version. The second argument is also a string, in version form. So a call to this function takes the form:

<property name="version" value="${version::offset-version(version,version.offset)}" />

If the first argument is “1.0.0.0”, and the offset argument is “0.0.1.0”, then the result, placed in the version property, will be “1.0.1.0”.

Custom Task: saving the version
Now that we have a new version number, its time to save the version back out to disk so that the version sequencing is maintained for the next build. This is accomplished via the “setautoversion” custom task. It simply takes the name of the version property, and the name of the file to write.

The attributes of this custom task are the same as the “getautoversion” task. That’s because both custom tasks derive from the same class: AutoVersionFileTaskBase. This is an abstract base class that simply declares two instance properties to map to the two attributes in the xml. The derived classes override the ExecuteTask method to read or write the version information to/from the file.

This behavior could have been implemented as a single task. But I preferred to break it out into two separate tasks to keep the reading behavior distinct from the writing behavior.

Custom Task: versioning the assembly
And finally here is where all of the versioning behavior comes together: versioning the assembly. Reading/writing the version information from a text file, and offsetting the version number is pointless if we don’t inject the version number into the assembly at build time.

Traditionally, the version number for an assembly lives in the AssemblyInfo.cs file, and is expressed via two attributes: AssemblyVersion and AssemblyFileVersion. The last custom task, “generateassemblyinfoversionfile”, generates a new .CS file that contains these two attributes. As a result, you must remove these two attribute declarations from your current AssemblyInfo.cs file.

This task defines five properties: filename, property, set-file-version, set-asm-version, and overwrite. The filename property defines the name of the file to be generated. The “property” property, like the getautoversion and setautoversion custom tasks, defines the name of the property where the version number is located. The set-file-version property, a boolean, emits the AssemblyFileVersion attribute if true. The set-asm-version property, also a boolean, emits the AssemblyVersion attribute if true. The overwrite property simply tells the task to overwrite the file if it already exists.

When this task is executed, as configured in the first code listing, the resulting file looks like this:

using System.Reflection;
[assembly: AssemblyFileVersion( "2.0.3.0" )]
[assembly: AssemblyVersion( "2.0.3.0" )]

Finally, this file must be included in your source code files when you build the application.

The full source code for all of these custom tasks can be found here: Download

August 25, 2008

NAnt – waiting for some condition

Filed under: c#, dotnet, NAnt — treyhutcheson @ 10:27 am

Question for all of the NAnt guys out there: have you ever needed to wait on some condition or interval in your build scripts? I have. Fortunately however, it doesn’t happen very often in my build scripts. On the other hand, for the two or so cases where I needed to wait, I found that there is no matching behavior in NAnt or NAntContrib.

A couple of years ago I had to come up with some build and test scripts for some automated UI testing. We were obviously using NAnt, and used NUnitForms. Some of the test conditions were complicated, requiring sequenced events, while some others required some mock threading magic. Most of the really complicated stuff took place in the unit tests themselves. However, we did run into a couple of places where we needed to control things from the test scripts and not the actual c# test cases. Here we ran into sequencing and timing issues, and this is where I discovered that I needed to be able to “pause” the nant script for some interval, or to wait for some condition to become true.

The result is a custom NAnt task simply named WaitTask. You can think of it as an NAnt version of a ManualResetEvent. The basic premise is this: pause execution until some condition becomes true, or the condition times out. Optionally fail.

The simplest way to demonstrate this is through xml:

<wait timeout="1000" interval="50" 
	failontimeout="true" for="${some.property=='true'}" />

Now lets look at the task’s properties.

timeout
This property defines the time, in milliseconds, in which the operation will timeout. If the condition does not evaluate to true during this time period, the task will end. In the example, the task has a timeout interval of one second.

interval
Defines the interval, in milliseconds, in which the condition will be evaluated. In the example, the condition will be evaluated every 50 milliseconds.

failontimeout
Indicates that the task will fail if the timeout occurs.

for
The boolean condition to evaluate at every interval. In the example, the task determines if the property named “some.property” has a string value of “true”.

And here’s the full code listing for the custom task:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

using NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Types;

namespace CustomNAntTasks
{
	/// <summary>
	/// This tasks waits until a condition is true 
	/// </summary>
	[TaskName( "wait" )]
	public class WaitTask : Task 
	{
		/// <summary>
		/// The timeout, in milliseconds
		/// </summary>
		private int _timeout;

		/// <summary>
		/// Indicates the task should fail on a timeout
		/// </summary>
		private bool _failOnTimeout;

		/// <summary>
		/// The expression to wait for 
		/// </summary>
		private string _forExpression;

		/// <summary>
		/// The interval, in milliseconds, to use for the timer
		/// </summary>
		private int _timerInterval = 50;

		/// <summary>
		/// Indicates we are in a timer callback
		/// </summary>
		private bool _inTimer;

		/// <summary>
		/// The timer instance 
		/// </summary>
		private ManualResetEvent _waitEvent;

		/// <summary>
		/// The result of the wait task
		/// </summary>
		private bool _result;

		public WaitTask()
		{
			_waitEvent = new ManualResetEvent( false );
		}	//END constructor 

		/// <summary>
		/// Gets/Sets the timeout in milliseconds 
		/// </summary>
		[TaskAttribute( "timeout" )]
		public int Timeout
		{
			get
			{
				return _timeout;
			}
			set
			{
				_timeout = value;
			}
		}	//END Timeout property

		/// <summary>
		/// Indicates the task should fail on timeout 
		/// </summary>
		[TaskAttribute( "failontimeout" )]
		public bool FailOnTimeout
		{
			get
			{
				return _failOnTimeout;
			}
			set
			{
				_failOnTimeout = value;
			}
		}	//END FailOnTimeout property

		/// <summary>
		/// Gets/Sets the For expression
		/// </summary>
		[TaskAttribute( "for" )]
		public string For
		{
			get
			{
				return _forExpression;
			}
			set
			{
				_forExpression = value;
			}
		}	//END For property

		/// <summary>
		/// Gets/Sets the interval, in milliseconds, to evaluate the condition
		/// </summary>
		[TaskAttribute( "interval" )]
		public int Interval
		{
			get
			{
				return _timerInterval;
			}
			set
			{
				_timerInterval = value;
			}
		}	//END TimerInterval property

		/// <summary>
		/// Executes the task 
		/// </summary>
		protected override void ExecuteTask()
		{
			bool timedout = false;
			
			//setup the timer 
			using( Timer t = new Timer( new TimerCallback( OnTimer ) , null , 0 , _timerInterval ) )
			{
				Log( Level.Info , "Waiting {0} milliseconds for condition to become true." , _timeout );

				_waitEvent.Reset();
				timedout = ! _waitEvent.WaitOne( _timeout , false );
				
			}	//dispose of the timer 
			
			if( timedout )
			{
				Level logLevel = _failOnTimeout ? Level.Error : Level.Info;
				Log( logLevel , "The wait operation timed out." );
			}
		}	//END ExecuteTask method 

		/// <summary>
		/// Indicates we are in a timer callback
		/// </summary>
		protected bool InTimer
		{
			get
			{
				lock( this )
					return _inTimer;
			}
			set
			{
				lock( this )
					_inTimer = value;
			}
		}	//END InTimer property

		/// <summary>
		/// The result of the wait task
		/// </summary>
		protected bool Result
		{
			get
			{
				lock( this )
					return _result;
			}
			set
			{
				lock( this )
					_result = value;
			}
		}	//END Result property

		/// <summary>
		/// Method called each timer interval
		/// </summary>
		/// <param name="state"></param>
		private void OnTimer( object state )
		{
			if( InTimer )
				return;

			try
			{
				InTimer = true;
				bool result = Evaluate();
				Log( Level.Info , "Evaluating condition: [{0}]" , _forExpression );

				if( result )
				{
					Log( Level.Info , "Evaluation result is True" );
					Result = true;
					_waitEvent.Set();
				}
				else
				{
					Log( Level.Info , "Evaluation unsuccessful. Trying again in {0} milliseconds" , _timerInterval );
				}
			}
			finally
			{
				InTimer = false;
			}
		}	//END OnTimer method 

		/// <summary>
		/// Determines if the wait condition is true 
		/// </summary>
		private bool Evaluate()
		{
			ExpressionEvaluator eval = new ExpressionEvaluator( this.Project , this.Properties , null , null );
			return ( bool ) eval.Evaluate( _forExpression );
		}	//END Evaluate method 

	}	//END WaitTask class

}	//END namespace

ExecuteTask Method
When the task executes, it uses a System.Threading.Timer to call back into the current thread at the assigned interval. It then resets the private ManualResetEvent, then calls the WaitOne method on the event to suspend the current thread.

Timer Callback
Every time the timer callback is executed, the task calls the Evaluate method to evaluate the condition. If the condition is true, the event is set, and the task ends. If the condition is false, the timer callback leaves, waiting for the next callback, or timeout, whichever occurs first.

Condition Evaluation
This is the coolest part of the task because A) its extremely easy and B) it uses built-in NAnt objects. NAnt includes a class named NAnt.Core.ExpressionEvaluator. A new object of this class is instantiated, and the expression(reflected by the task’s for attribute) is evaluated. No fuss. The result of the evaluation is returned as the method result.

Conclusion
I don’t how helpful this task really is. Like I said at the beginning, I’ve only used it twice. However, this task did let me move some of my test case sequencing logic from outside of the test assembly and into the test script itself. Maybe some one can find another use for this task.

August 18, 2008

CruiseControl.NET / Delphi – Dashboard Templates

Filed under: Continuous Integration/CruiseControl.NET, Delphi, NAnt — treyhutcheson @ 10:25 am

In my last post I discussed integration CCNet/NAnt with Delphi/BDS 2006. That post however did not cover how to include the build results from Delphi in the project build report.

ERR File
When the bds.exe executes, it doesn’t output anything to STDOUT or STDERR. Instead, it outputs all of the compiler information into the file [ProjectName].err. Here’s an example of what you can expect to find in the file(ignore the various paths):

Delphi Command Line for "Project1.dpr"
$A8 $B- $C+ $D+ $E- $F- $G+ $H+ $I+ $J- $K- $L+ $M- $N+ $O- $P+ $Q- $R- $S- $T- $U- $V+ $W- $X+ $YD $Z1 -B -H+ -W
$M16384,1048576 $K4194304 -CG -Ee:\cc_sandbox\projects\Project1\ OutPutDir=out -N0DCUs
-Ue:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;
-Oe:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;
-Re:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;
PkgDllDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl
PkgDcpDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl
-AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; -C0 OBJOutputDir=DCUs
BPIOutputDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl -W-UNSAFE_TYPE -W-UNSAFE_CODE
-W-UNSAFE_CAST Project1.dpr
[Pascal Warning] BrowserForm.pas(444): W1036 Variable 'list' might not have been initialized

Knowing the basic format of this file, it’s not very hard to scrape it so the information can be included the build report.

NAnt script for .ERR file scraping
Here’s a bit of script to include in your NAnt build scripts. It’s embedded c# code that reads all of the lines of a text file, and dumps them into a single xml document. Each individual line is wrapped inside an xml element named “line”. The document element is named “delphi-console”.

Here’s the script to include in your NAnt builds:

<script language="C#" prefix="script">
<references>
  <include name="System.dll" />
  <include name="System.Xml.dll" />
</references>

<imports>
  <import namespace="System" />
  <import namespace="System.IO" />
  <import namespace="System.Xml" />
</imports>

<code>
  <!&#91;CDATA&#91;  
  &#91;Function( "read-console-output" )&#93;
  public static string ReadConsoleOutput( string path )
  {
	string&#91;&#93; lines = File.ReadAllLines( path );
	
	XmlDocument doc = new XmlDocument();
	XmlElement root = doc.CreateElement( "delphi-console" );
	doc.AppendChild( root );
	
	foreach( string line in lines ) 
	{
	  XmlElement lineNode = doc.CreateElement( "line" );
	  root.AppendChild( lineNode );
	  
	  lineNode.InnerText = line;
	} //END line loop 
	
	return doc.OuterXml;
  } //END ReadConsoleOutput method
  &#93;&#93;>
</code>
</script>

And here is the content of the Project1.err file converted to xml:

<delphi-console>
  <line>Delphi Command Line for "Project1.dpr"</line>
  <line>  $A8 $B- $C+ $D+ $E- $F- $G+ $H+ $I+ $J- $K- $L+ $M- $N+ $O- $P+ $Q- $R- $S- $T- $U- $V+ $W- $X+ $YD $Z1 -B -H+ -W </line>
  <line>  $M16384,1048576 $K4194304 -CG -Ee:\cc_sandbox\projects\Project1\ OutPutDir=out -N0DCUs </line>
  <line>  -Ue:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;</line>
  <line>  -Oe:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;</line>
  <line>  -Re:\program files\borland\bds\4.0\units;e:\program files\borland\bds\4.0\vcl;</line>
  <line>  PkgDllDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl </line>
  <line>  PkgDcpDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl </line>
  <line>  -AWinTypes=Windows;WinProcs=Windows;DbiTypes=BDE;DbiProcs=BDE;DbiErrs=BDE; -C0 OBJOutputDir=DCUs </line>
  <line>  BPIOutputDir=C:\Documents and Settings\CCBUILD\My Documents\Borland Studio Projects\Bpl -W-UNSAFE_TYPE -W-UNSAFE_CODE </line>
  <line>  -W-UNSAFE_CAST Project1.dpr </line>
  <line>[Pascal Warning] BrowserForm.pas(444): W1036 Variable 'list' might not have been initialized</line>
</delphi-console>

Now that you have the nant script to generate the build output xml file, you need to call it. If the bds.exe process fails, and you have failonerror attribute set to true, the err file scraping process won’t be executed. So for my Delphi build scripts, I wrap my “build” target with a new target that I inventively have named “trybuild”. This target calls the “build” target within a try/catch/finally block(available from the NAntContrib project). This way no matter what happens during the compile, the .err file will be scraped.

The following code listing demonstrates the use of .err file scraping:

<target name="trybuild">
    <trycatch>
      <try>
        <call target="build" />
      </try>
      <catch property="failure">
        <if test="${file::exists(file.result)}">
          <fail>Delphi build failure: ${failure}</fail>          
        </if>
        <if test="${not file::exists(file.result)}">
          <fail>General failure:${failure}</fail>
        </if>
      </catch>
      <finally>
        <if test="${file::exists(file.result)}">
          <echo file="${dir.logs}/delphi-console.xml">${script::read-console-output(file.result)}</echo>          
        </if>
      </finally>
    </trycatch>
 </target>

Notice the various *if conditions. If the bds.exe fails and the .err file exists, then there was some build related failure. If the .err file does not exist, then there was some other problem.

Also, notice that the way file final xml file is generated is via a call to the echo task, which explicitly calls the read-console-output script we defined earlier.

Once you have that all in place, you must now modify your ccnet.config file to include this output file in the build log via the merge publisher. Example:

<publishers>
	<merge>
		<files>		
			<file>logs/delphi-console.xml</file>
		</files>
	</merge>
</publishers>

Now that the .err file has been scraped, and the contents have been merged into ccnet’s build log, we can work on outputting the content to the build report.

This part is relatively simple. I started with a copy of the file compile.xsl, located in the /xsl folder of the dashboard directory. From there, I created an xsl transform that looked for each line in the build log that was prefixed with [Pascal Error] or [Pascal Hint]. The script does not currently look for [Pascal Warning] lines, and treats hints as warnings. I named the xsl script “delphi-console-out.xsl”, and here is its full listing:

<?xml version="1.0"?>
<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

  <xsl:output method="html"/>

  <xsl:template match="/">

    <xsl:variable name="lines" select="/cruisecontrol/build/delphi-console/line" />
    <xsl:if test="count($lines) > 0">

      <xsl:variable 
        name="lines.error" 
        select="$lines&#91;(contains(text(), '&#91;Pascal Error&#93;')) or (contains(text(), '&#91;Pascal Fatal Error&#93;'))&#93;" />
      <xsl:variable 
        name="lines.error.count" 
        select="count($lines.error)" />
      <xsl:variable 
        name="lines.hint" 
        select="$lines&#91;(contains(text(), '&#91;Pascal Hint&#93;'))&#93;" />
      <xsl:variable 
        name="lines.hint.count" 
        select="count($lines.hint)" />
      <xsl:variable 
        name="lines.total" 
        select="count($lines.error) + count($lines.hint)"/>

      <xsl:if test="$lines.error.count > 0">
        <table class="section-table" cellpadding="2" cellspacing="0" border="0" width="98%">
          <tr>
            <td class="sectionheader">
              Delphi Console Output - Errors: (<xsl:value-of select="$lines.error.count"/>)
            </td>
          </tr>
          <tr>
            <td>
              <xsl:apply-templates select="$lines.error"/>
            </td>
          </tr>
        </table>
      </xsl:if>
      <xsl:if test="$lines.hint.count > 0">
        <table class="section-table" cellpadding="2" cellspacing="0" border="0" width="98%">
          <tr>
            <td class="sectionheader">
              Delphi Console Output - Hints: (<xsl:value-of select="$lines.hint.count"/>)
            </td>
          </tr>
          <tr>
            <td>
              <xsl:apply-templates select="$lines.hint"/>
            </td>
          </tr>
        </table>
      </xsl:if>
      
    </xsl:if>
  </xsl:template>

  <xsl:template match="line">
    <pre class="section-error">
      <xsl:value-of select="text()"/>
    </pre>
  </xsl:template>
  
</xsl:stylesheet>

After that, you must modify the dashboard.config file to include the xsl. Fortunately, that’s easily done. Locate the dashboard/plugins/buildPlugins/buildReportBuildPlugin/xslFilenames element, and add a new <xslFile> element like so: <xslFile>xsl\delphi-console-out.xsl</xslFile>

Once all of this comes together, you can see at Delphi hints and errors reflected in ccnet’s build report. Here’s a screencap:

Sample output of build report

Sample output of build report

I hope this helps some one else.

August 14, 2008

CruiseControl.NET, and… Delphi?

Filed under: Continuous Integration/CruiseControl.NET, Delphi, NAnt — treyhutcheson @ 9:33 am

I recently began working on one of my company’s legacy applications, written in Delphi, using Borland Developer Studio 2006. I got sick of every developer doing one-off builds every time some one needed a new executable, so I made it my mission to integrate the project into our Automated Build Environment. It was a long and tortuous process, but completely worth it. During the past 6 months, the build environment has spit out exactly 662 successful builds of this project, averaging 3.6 successful builds per day(including weekends).

Command Line Interface & Licensing
I’m not a Delphi guru, so this entire effort was full of exploration. The starting point was working with Delphi/BDS via the command line. I did some research, and found that there were two command-line candidates: the actual compiler, dcc32.exe, and the executable for the development environment, bds.exe.

I spent maybe a day trying to get dcc32.exe to work. I consulted documentation, checked newsgroups, googled 25 different search terms, and I still couldn’t get it to work. I decided to simplify the problem, and create a new command line project with one source file. I turned on the option in the environment to show the command line. I used the exact same command line that the environment was telling me it was using, and I still couldn’t get it to compile. That was well over 6 months ago, so I can’t remember the exact compiler error messages at this point. Regardless, pursuing dcc32 wasted a day, so I then turned to bds.exe.

The documentation for BDS 2006 states that the bds.exe executable does support a command line interface, so I was in luck. The funny thing is that I couldn’t find any info on the CLI through the integrated help, but when I ran “bds /?” from the console, the BDS environment started up, and opened the integrated help to the correct topic. I don’t know how much it will help, but the url(in the integrated help) for the docs is: ms-help://borland.bds4/bds4ref/html/idecommandline.htm, and the navigation path is Borland Help > Developer Studio 2006 (Common) > Refreence > Command Line Switches > IDE Commnad Line Switches and Options.

With the aid of the documentation, it was pretty simple to run bds from the console. However, there were a few hiccups. First, you must remember that bds.exe is a gui application first that appears to have had a CLI tacked on at the end. This is unfortunate. The executable does not write to STDOUT or STDERR, nor does it support process exit codes. The other problem is that your environment must be setup correctly! More on that in a moment.

After I had decided the practical matter of how I would handle the builds from the console, I needed to check on licensing. BDS 2006 is quite expensive, and the installation/licensing mechanism is quite complex. I needed to check with Borland(or Inprise, or CodeGear, or whatever the company name was that quarter) on the licensing terms. I called, went through phone limbo for a while, until I was finally connected to a sales person. I explained that the company already had x licenses of the product and none were free, and that I needed another so I could perform automated builds in a dedicated build environment. I was transferred multiple times until I finally reached some licensing specialist. I explained the scenario again, and was put on hold again. When the “specialist” returned, I was told that no one had ever inquired about using licenses for this purpose before. I was then told that as long as it was installed in a dedicated environment, and that this new installation would not be used for actual interactive development, that I did not need a separate license. That was good news.

This is a good point to ask a question. I’ve already established that I was the first person in the world to attempt to develop a cruisecontrol.net dashboard plugin, and now it appears that I was the first person to ever contact the company about automated builds. The question: how do I end up doing all these freaky fringe things? Sorry for the tangent.

Anyway, having gotten the OK on the license front, it was time to implement a solution.

Environment Setup is Critical
As I previously mentioned, environment setup is critical. The bds executable is a gui application, and does not behave as one would expect from a CLI. When bds.exe is executed from the console, it actually launches the development environment interactively, loads a specified project, and attempts to compile it. You can actually watch it compile. When its completes, it closes. However, there are cases where the environment does not close. For example, if your project references a component or package that cannot be found, the environment will complain via a modal message box. Also, if the act of compiling changes some file in the project, the environment will ask you if you wish to save the changes before it closes. Specifically, this happens if you have the “Include version information in project” option selected.

This is a Big Deal. If you’re running CruiseControl.NET as a service, and the BDS environment prompts one of these modal dialogs, your process will hang. You can get around this problem by setting a timeout in the ccnet project configuration, but that will cause the build to fail. Here are a few tips for getting around this problem:

  • Setup your build environment, including installed packages and the like, first.
  • Do not include version info in your project
  • Don’t mess with project resources
  • The point is, getting the environment setup correctly is the most important part of the process. This integration is pretty fragile, and doesn’t take much to break. You’re going to spend quite a bit of time logged in to your build environment, interactively testing your build script, before you finalize it.

    Versioning and Project Resources
    Our build script handles the versioning of the project. It uses a set of custom NAnt tasks to generate and increment the version number for the build. I’ll cover the minutiae of auto-versioning in a different post. Regardless, after the version number is generated, the build script injects the version number into the .bdsproj file before the bds.exe is launched. This method allows the project output to include the version number, but avoids the problem of hanging the bds environment because a file changed during the compile phase.

    The version number is separated into its components: Major, Minor, Build, and Release, via calls to custom embedded c# script functions. That information is then injected into the .bdsproj file via the NAnt xmlpoke task. It is important to note here that Delphi reverses the Build and Release members of a version, at least when compared to a dotnet version structure. So you’ll see in the script that these values are swapped when injected. Here’s the code listing for the version injection:

    <!-- extract the version members -->
    <property name="version.major" value="${script::get-version-major(version)}" />
    <property name="version.minor" value="${script::get-version-minor(version)}" />
    <property name="version.build" value="${script::get-version-build(version)}" />
    <property name="version.revision" value="${script::get-version-revision(version)}" />
    
    <xmlpoke 
    	file="${file.proj}"	
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo&#91;@Name='IncludeVerInfo'&#93;"
    	value="True" />
    <xmlpoke 
    	file="${file.proj}"	
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo&#91;@Name='AutoIncBuild'&#93;"
    	value="False" />		
    <xmlpoke 
    	file="${file.proj}"	
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo&#91;@Name='MajorVer'&#93;"
    	value="${version.major}" />
    <xmlpoke 
    	file="${file.proj}"	
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo&#91;@Name='MinorVer'&#93;"
    	value="${version.minor}" />
    <xmlpoke 
    	file="${file.proj}"	
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo&#91;@Name='Release'&#93;"
    	value="${version.build}" />
    <xmlpoke 
    	file="${file.proj}"	
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo&#91;@Name='Build'&#93;"
    	value="${version.revision}" />
    <xmlpoke 
    	file="${file.proj}"	
    	xpath="BorlandProject/Delphi.Personality/VersionInfoKeys/VersionInfoKeys&#91;@Name='FileVersion'&#93;"
    	value="${version}" />
    <xmlpoke 
    	file="${file.proj}"	
    	xpath="BorlandProject/Delphi.Personality/VersionInfoKeys/VersionInfoKeys&#91;@Name='ProductVersion'&#93;"
    	value="${version}" />
    

    As you can see, this code injects the version components individually into the .bdsproj file, and also sets the output’s File version and Product version values. Next, we’re going to deal with resources.

    At one point a few months ago, our build process was brought to its knees. It turns out that some one changed the application’s main icon and checked in the modified files. For some reason, this caused some checksum to be out of sync with some other file(I believe it was the project’s .drc file), which caused BDS to believe that some change had been made during the compile phase, which resulted in BDS prompting the user to save changes. Because this was occurring during a non-interactive service, the process hung until it finally timed out. That’s why I mentioned that you do not want to mess with a project’s resources.

    To get around the problem of the icon, I figured out how to generate the application’s icon from the build script. I removed the application’s icon, saved the project, and committed the changes. I then added a separate .ico file to the repository, named [Project].ico. Next, I created a resource script named [Project].rc. Finally, before I launch the bds.exe executable, I run brcc32.exe to compile the resource script, which outputs the resource into [Project].res.

    The resource script is a simple one line text file. Here’s an example: MAINICON Icon Project1.ico
    This little work around has a few benefits. First, bds was no longer prompting us to save changes, and second, to change the app’s icon, one need only commit a new [Project].ico to the repository.

    Building the Project
    Now that the version information has been set, and the main application resource has been generated, it’s time to finally launch bds.exe and perform the build. This process is actually fairly easy, so I’ll let the following snippet of NAnt script do all the talking:

    <target name="build" depends="clean">
    	<property name="file.bds.args" value="-ns -nw -b ${file.proj}" />
    
    	<!-- generate the new version number -->
    	<call target="gen-version" />
    
    	<!-- delete the current .res file, and generate the new one -->
    	<delete file="project.res" />
    	<exec program="${env.borland_home}\brcc32.exe" commandline="Project.rc" />
    
    	<exec program="${file.bds}"	commandline="${file.bds.args}" basedir="E:\Program Files\Borland\BDS\4.0\Bin" 
    	failonerror="true" timeout="600000" />		
    	
    	<!-- revert the transient project files -->
    	<call target="revert-project-temp-files" />		
    </target>
    

    File Reversion
    The compile phase emits some files and changes a few others. Some of these files are transient, and I don’t want the changed files to be committed back to the repository. Hence the custom target “revert-project-temp-files”.

    We’re using cvs as our repository, so the script logs in to cvs, deletes a series of files, and performs a CVS UPDATE on each file that was deleted. This ensures that the transient files are up to date before the next build begins. The list of transient files includes the .bdsproj file(we changed it when we generated the version information), the project.cfg file, the project.drc file, the project.res file, and any .tlb/_TLB.pas files.

    Conclusion
    That should be enough to get you started, though the process is not yet complete. The next post will discuss handling the .err file, and including the compile-related errors and warnings in the CruiseControl.NET build report.

    August 13, 2008

    CruiseControl.NET and multiple build configurations: Part 7 – Committing changes

    Filed under: c#, Continuous Integration/CruiseControl.NET, dotnet, NAnt — treyhutcheson @ 12:05 pm

    [More Posts On This Topic]

    This is post 7 in a series devoted to extending CruiseControl.NET to support multiple build configurations for a single project. In this post, I will revisit the dashboard plugin and discuss how the property changes are committed. This will be a lengthy post.

    Where We’ve Been
    Before I get into the meat of the post, I’d like to recap what I’ve covered so far. The idea is to create some user interface element in the dashboard from which the user can modify nant script properties and have them used by the nant script at build time. I’ve chosen to implement this as a dashboard plugin with a corresponding server side plugin. The dashboard plugin provides the user interface for editing the nant property values, and the server-side plugin provides the functionality of passing these property values to the nant script at build time.

    Back to the Dashboard Plugin
    The last post covered the server side plugin. It’s complete, and doesn’t need to be revisited. The remaining work all takes place within the dashboard plugin. The fifth post in this series introduced the implementation of the Dashboard plugin, but left it incomplete. I’ll correct that shortly.

    Reference: Dashboard Plugin Source
    Before I get into the details of the remaining implementation of the plugin, I’ll post the source. You can review it now, or come back later:

    /// <summary>
    /// This class implements the custom dashboard plugin that will allow the user 
    /// to modify the project's nant script properties via the browser. 
    /// 
    /// This plugin is a project plugin, and appears as a link via the project page. 
    /// </summary>
    [ReflectorType( "propertiesPlugin" )]
    public class PropertiesPlugin : ICruiseAction , IPlugin
    {
    	/// <summary>
    	/// The xpath to the params task
    	/// </summary>
    	private const string XPATH_PARAMS = "project/tasks/parameterizedNant";
    
    	/// <summary>
    	/// The name of the plugin
    	/// </summary>
    	private const string PLUGIN_DESCRIPTION = "NAnt Project Properties";
    
    	/// <summary>
    	/// The name of the action
    	/// </summary>
    	private const string ACTION_NAME = "ViewProjectProperties";
    
    	/// <summary>
    	/// Generates the html view used by the dashboard; injected into the constructor 
    	/// by the runtime
    	/// </summary>
    	private readonly IVelocityViewGenerator _viewGenerator;
    
    	/// <summary>
    	/// Reference back to the actual ccnet server. 
    	/// </summary>
    	private readonly ICruiseManagerWrapper _manager;
    
    	/// <summary>
    	/// Initializes the plugin. The arguments are set by the dashboard/netreflector runtime
    	/// </summary>
    	public PropertiesPlugin( IVelocityViewGenerator viewGenerator , ICruiseManagerWrapper manager )
    	{
    		_viewGenerator = viewGenerator;
    		_manager = manager;
    	}	//END constructor 
    
    	#region ICruiseAction Members
    
    	/// <summary>
    	/// Executes the request
    	/// </summary>
    	public IResponse Execute( ICruiseRequest cruiseRequest )
    	{
    		// get the request and the current project
    		IRequest req = cruiseRequest.Request;
    
    		// read all of the properties as they are currently defined in the project's config
    		List<PropertyItem> existingProperties = ReadItems( cruiseRequest.ProjectSpecifier );
    
    		// determine if we are displaying the properties, or committing the properties
    		if( IsRefresh( req ) )
    			return Commit( existingProperties , cruiseRequest );
    		else
    			return ExecuteNew( existingProperties , cruiseRequest );
    	}	//END Execute method 
    
    	#endregion
    
    	/// <summary>
    	/// Determines if the current page view is a normal view, or if the form has
    	/// been submitted as a postback(and subsequently the modified property values will
    	/// need to be committed back to the ccnet server).
    	/// </summary>
    	private bool IsRefresh( IRequest request )
    	{
    		string key = request.FindParameterStartingWith( "Refresh" );
    		return key != string.Empty;
    	}	//END IsRefresh method 
    
    	/// <summary>
    	/// Reads all of the properties from the project's configuration
    	/// </summary>
    	private List<PropertyItem> ReadItems( IProjectSpecifier projectSpec )
    	{
    		// read the project configuration, in xml form
    		string config = _manager.GetProject( projectSpec );
    
    		// load the config into an xml document 
    		XmlDocument doc = new XmlDocument();
    		doc.LoadXml( config );
    
    		// find the parameterized task node. If no node is defined, then return 
    		// an empty list of property items 
    		XmlElement node = ( XmlElement ) doc.SelectSingleNode( XPATH_PARAMS );
    		if( node == null )
    			return new List<PropertyItem>();
    
    		// deserialize the custom server plugin from the xml node
    		NetReflectorTypeTable typeTable = NetReflectorTypeTable.CreateDefault();
    		typeTable.Add( typeof( PropertyItem ) );
    		typeTable.Add( typeof( ParameterizedNAntTask ) );
    		ParameterizedNAntTask task = ( ParameterizedNAntTask ) NetReflector.Read( node , typeTable );
    
    		return new List<PropertyItem>( task.Properties );
    	}	//END ReadItems method 
    
    	/// <summary>
    	/// Updates the configuration 
    	/// </summary>
    	private void UpdateConfiguration( IProjectSpecifier projectSpec , List<PropertyItem> properties )
    	{
    		string config = _manager.GetProject( projectSpec );
    
    		// load the config into an xml document 
    		XmlDocument doc = new XmlDocument();
    		doc.LoadXml( config );
    
    		// find the parameterized task node 
    		XmlElement node = ( XmlElement ) doc.SelectSingleNode( XPATH_PARAMS );
    		if( node == null )
    			return;
    
    		// load the configuration
    		NetReflectorTypeTable typeTable = NetReflectorTypeTable.CreateDefault();
    		typeTable.Add( typeof( PropertyItem ) );
    		typeTable.Add( typeof( ParameterizedNAntTask ) );
    
    		// extract the current parameterized 
    		ParameterizedNAntTask task = ( ParameterizedNAntTask ) NetReflector.Read( node , typeTable );
    
    		// update the tasks's properties and save the configuration
    		task.Properties = properties.ToArray();
    
    		// write out the xml to a string builder and load the xml into another node
    		StringBuilder sb = new StringBuilder();
    		using( XmlTextWriter writer = new XmlTextWriter( new StringWriter( sb ) ) )
    		{
    			NetReflector.Write( writer , task );
    		}
    
    		XmlDocument newDoc = new XmlDocument();
    		newDoc.LoadXml( sb.ToString() );
    
    		XmlElement clonedNode = ( XmlElement ) doc.ImportNode( newDoc.DocumentElement , true );
    		XmlElement parent = ( XmlElement ) node.ParentNode;
    		parent.ReplaceChild( clonedNode , node );
    
    		// send the new xml/config back to the server
    		sb = new StringBuilder();
    		using( XmlTextWriter writer = new XmlTextWriter( new StringWriter( sb ) ) )
    		{
    			writer.Formatting = Formatting.Indented;
    			doc.Save( writer );
    		}
    
    		string newXml = sb.ToString();
    		_manager.UpdateProject( projectSpec , newXml );
    	}	//END UpdateConfiguration method 
    
    	/// <summary>
    	/// Gets the property items that have been posted back 
    	/// </summary>
    	private void GetUpdatedItems( IRequest req , List<PropertyItem> list )
    	{
    		bool keyFound = true;
    		int counter = 0;
    
    		// each property is defined dynamically in the html view via a text input element.
    		// these elements follow the names "row0", "row1", etc, based on ordinal position
    		// relative to the original list of items. 
    
    		// loop until there are no more keys
    		while( keyFound )
    		{
    			string key = string.Format( "row{0}" , counter );
    			if( req.FindParameterStartingWith( key ) == string.Empty )
    				break;
    
    			string value = req.Params[ key ];
    
    			list[ counter ].Value = value;
    			counter++;
    		}	//END loop
    	}	//END GetUpdatedItems method 
    
    	/// <summary>
    	/// Commits the modified/updated property values back to the ccnet server 
    	/// </summary>
    	/// <param name="properties"></param>
    	/// <param name="cruiseRequest"></param>
    	/// <returns></returns>
    	private IResponse Commit( List<PropertyItem> properties , ICruiseRequest cruiseRequest )
    	{
    		IProjectSpecifier projectSpec = cruiseRequest.ProjectSpecifier;
    		IServerSpecifier serverSpec = cruiseRequest.ServerSpecifier;
    		IRequest req = cruiseRequest.Request;
    		Hashtable map = new Hashtable();
    
    		// get the list of updated property values and pass them back
    		// to the ccnet server 
    		GetUpdatedItems( req , properties );
    		UpdateConfiguration( projectSpec , properties );
    
    		map.Add( "projectName" , cruiseRequest.ProjectName );
    		map.Add( "props" , properties );
    
    		return _viewGenerator.GenerateView( "ViewProjectProperties.vm" , map );
    	}	//END Commit method 
    
    	/// <summary>
    	/// Generates a new view from scratch with the property items that are presently defined.
    	/// </summary>
    	private IResponse ExecuteNew( List<PropertyItem> properties , ICruiseRequest cruiseRequest )
    	{
    		Hashtable map = new Hashtable();
    		map.Add( "props" , properties );
    		map.Add( "projectName" , cruiseRequest.ProjectName );
    
    		return _viewGenerator.GenerateView( "ViewProjectProperties.vm" , map );
    	}	//END ExecuteNew method 
    
    	#region IPlugin Members
    
    	/// <summary>
    	/// Returns the description for the link
    	/// </summary>
    	public string LinkDescription
    	{
    		get
    		{
    			return PLUGIN_DESCRIPTION;
    		}
    	}	//END LinkDescription property
    
    	public INamedAction[] NamedActions
    	{
    		get
    		{
    			return new INamedAction[] { new ImmutableNamedAction( ACTION_NAME , this ) };
    		}
    	}	//END NamedActions property
    
    	#endregion
    
    }	//END PropertiesPlugin class 
    

    Synopsis
    The server side plugin wraps the built-in nant task. It adds support for explicit nant property definitions within the ccnet project configuration file. The dashboard plugin will use the server-side plugin to acquire these properties and to write the properties back to the configuration file.

    Getting the NAnt Properties
    Before the properties can be edited by the user, they must be enumerated from the ccnet project configuration file. This poses quite a challenge.

    The problem is that the dashboard is executing in a different context from the ccnet server itself. The ccnet server is either running from the command line or as a service, and the dashboard is hosted by asp.net. There is a remoting api, but it’s pretty limited. The api doesn’t expose much functionality – it’s mostly limited to read-only access or kicking off builds. However, the api does provide limited access to a project configuration.

    The ReadItems method uses this functionality to acquire the project configuration, and uses its own copy of the server-side plugin to deserialize the nant properties. It’s a bit of a hack, but it works.

    Here’s the code listing for the ReadItems method:

    /// <summary>
    /// Reads all of the properties from the project's configuration
    /// </summary>
    private List<PropertyItem> ReadItems( IProjectSpecifier projectSpec )
    {
    	// read the project configuration, in xml form
    	string config = _manager.GetProject( projectSpec );
    
    	// load the config into an xml document 
    	XmlDocument doc = new XmlDocument();
    	doc.LoadXml( config );
    
    	// find the parameterized task node. If no node is defined, then return 
    	// an empty list of property items 
    	XmlElement node = ( XmlElement ) doc.SelectSingleNode( XPATH_PARAMS );
    	if( node == null )
    		return new List<PropertyItem>();
    
    	// deserialize the custom server plugin from the xml node
    	NetReflectorTypeTable typeTable = NetReflectorTypeTable.CreateDefault();
    	typeTable.Add( typeof( PropertyItem ) );
    	typeTable.Add( typeof( ParameterizedNAntTask ) );
    	ParameterizedNAntTask task = ( ParameterizedNAntTask ) NetReflector.Read( node , typeTable );
    
    	return new List<PropertyItem>( task.Properties );
    }	//END ReadItems method 
    

    The first line of the method acquires the project configuration. It calls the GetProject method on the ICruiseManagerWrapper interface(defined as the _manager member, set via constructor injection). The GetProject method accepts an IProjectSpecifier interface, which defines the project to retrieve. Fortunately, the IProjectSpecifier object reference is available from the ICruiseRequest the dashboard plugin is processing.

    The return value of the GetProject method is a simple string. This string is the xml form of the project configuration.

    This xml is loaded into an XmlDocument object. From there, a single node is selected from the XmlDocument via a SelectSingleNode call. The xpath is “project/tasks/parameterizedNant”. This gives us the actual xml node that represents the parameterizedNant server-side plugin. It’s important to understand that we are not actually interacting with the parameterizedNant object running within the ccnet process via remoting. Instead we’re going to be creating our own copy.

    The next bit of code uses the NetReflector tricks used throughout the ccnet sourcecode. Ultimately all it does is deserialize the retrieved node into a new parameterizedNant object instance.

    Finally the ReadItems method returns the object’s Properties value, which is List.

    Which values were updated by the user?
    So now we’ve retrieved the available nant property values from the project configuration. We’ve dumped them to the dashboard so the user can edit them(this work was covered in Post 5). When the user clicks the Submit button, we get a post back, and we need to determine which values actually changed.

    Here’s the code listing for the GetUpdatedItems method:

    /// <summary>
    /// Gets the property items that have been posted back 
    /// </summary>
    private void GetUpdatedItems( IRequest req , List<PropertyItem> list )
    {
    	bool keyFound = true;
    	int counter = 0;
    
    	// each property is defined dynamically in the html view via a text input element.
    	// these elements follow the names "row0", "row1", etc, based on ordinal position
    	// relative to the original list of items. 
    
    	// loop until there are no more keys
    	while( keyFound )
    	{
    		string key = string.Format( "row{0}" , counter );
    		if( req.FindParameterStartingWith( key ) == string.Empty )
    			break;
    
    		string value = req.Params[ key ];
    
    		list[ counter ].Value = value;
    		counter++;
    	}	//END loop
    }	//END GetUpdatedItems method 
    

    This code looks for a series of postback values named “row1”, “row2”, etc. We know the naming of these values because that’s how they are named in the template. The value names are sequenced, so if we can’t find the next key in the sequence, we know there are no more values to process.

    The sequence of these keys are based on the ordinal position of the list of PropertyItem objects. So the first object will be represented by “row0”, the second “row1”, etc. If the key exists, we replace the corresponding PropertyItem object’s value with the key’s value.

    Committing the changes
    When the Submit button is clicked, the plugin commits all of the property values back to the configuration file, then re-generates the view with the updated property values. This behavior is facilitated by two methods: Commit, and UpdateConfiguration. The Commit method is pretty simple, but all of the real committing work takes place in UpdateConfiguration. Here’s the code listing:

    /// <summary>
    /// Updates the configuration 
    /// </summary>
    private void UpdateConfiguration( IProjectSpecifier projectSpec , List<PropertyItem> properties )
    {
    	string config = _manager.GetProject( projectSpec );
    
    	// load the config into an xml document 
    	XmlDocument doc = new XmlDocument();
    	doc.LoadXml( config );
    
    	// find the parameterized task node 
    	XmlElement node = ( XmlElement ) doc.SelectSingleNode( XPATH_PARAMS );
    	if( node == null )
    		return;
    
    	// load the configuration
    	NetReflectorTypeTable typeTable = NetReflectorTypeTable.CreateDefault();
    	typeTable.Add( typeof( PropertyItem ) );
    	typeTable.Add( typeof( ParameterizedNAntTask ) );
    
    	// extract the current parameterized 
    	ParameterizedNAntTask task = ( ParameterizedNAntTask ) NetReflector.Read( node , typeTable );
    
    	// update the tasks's properties and save the configuration
    	task.Properties = properties.ToArray();
    
    	// write out the xml to a string builder and load the xml into another node
    	StringBuilder sb = new StringBuilder();
    	using( XmlTextWriter writer = new XmlTextWriter( new StringWriter( sb ) ) )
    	{
    		NetReflector.Write( writer , task );
    	}
    
    	XmlDocument newDoc = new XmlDocument();
    	newDoc.LoadXml( sb.ToString() );
    
    	XmlElement clonedNode = ( XmlElement ) doc.ImportNode( newDoc.DocumentElement , true );
    	XmlElement parent = ( XmlElement ) node.ParentNode;
    	parent.ReplaceChild( clonedNode , node );
    
    	// send the new xml/config back to the server
    	sb = new StringBuilder();
    	using( XmlTextWriter writer = new XmlTextWriter( new StringWriter( sb ) ) )
    	{
    		writer.Formatting = Formatting.Indented;
    		doc.Save( writer );
    	}
    
    	string newXml = sb.ToString();
    	_manager.UpdateProject( projectSpec , newXml );
    }	//END UpdateConfiguration method 
    

    This method literally does the opposite of the ReadItems method. It takes an IProjectSpecifier object reference and a list of PropertyItem objects, and updates the project’s configuration with the property values.

    Like ReadItems, UpdateConfiguration gets the latest project configuration from the _manager member. It loads the xml into an XmlDocument object and deserializes the current parameterizedNant object from the xml.

    From there, it sets the parameterizedNant object’s properties to be the new list of properties collected from the dashboard plugin. It then uses NetReflector to serialize the parameterizedNant object back to xml, injects that xml into the project configuration, and saves the configuration back to the ccnet server via a call to the ICruiseManagerWrapper’s UpdateProject method.

    Viola! The properties that were just collected from the user will find themselves persisted to the project configuration file. We’re done!

    Problem 1: Concurrency
    The dashboard is a website, thus it is entirely possible that multiple users could commit modified properties back to the ccnet server at the same time. I’ve not tested this possibility, as my ccnet environment doesn’t get much traffic from users other than myself. I don’t know if ccnet will serialize these requests or what; all I know is that the dashboard plugin does not protect against this issue. Just keep that in mind.

    Problem 2: Destructive Write Operation
    The process of reading and writing the xml for the project configuration is destructive. Any whitespace and/or xml comments embedded within your project configuration file will be overwritten. I’m willing to accept this, but some people may not be.

    Problem 3: Changes are Permanent
    When a user changes a property value, that value is permanent. At least until it is explicitly modified by hand, or until some one changes the value again via the plugin. In other words, this plugin does not allow the nant script to use these properties for one-off builds.

    For example, the entire reason I developed this plugin was so I could have a single project configuration produce either Debug or Release builds. As it stands now, if I switched the configuration from Debug to Release, all builds going forward will be Release builds. Of course, I could always change it back to a Debug build, but it will not happen automatically.

    Consequently, this approach is not suitable for something like defining explicit version numbers for a build.

    Next?
    Until this point, I’ve posted all of the code in fragments. I would like to post all of the code in self-contained downloadable form, but I don’t know if that’s possible for reasons that I will not discuss at present. I know that this series of post is not the most organized, but I hope the information has been presented coherently enough to be of use to some one out there.

    August 6, 2008

    CruiseControl.NET and multiple build configurations: Part 6 – The server-side plugin

    Filed under: c#, Continuous Integration/CruiseControl.NET, dotnet, NAnt — treyhutcheson @ 10:11 am

    [More Posts On This Topic]

    This is post 6 in a series devoted to extending CruiseControl.NET to support multiple build configurations for a single project. In this post, I will discuss a new component: the server-side plugin.

    Up until now, we’ve limited our discussion to dashboard plugins. So far we’ve implemented a simple page that will allow a user to modify NAnt property values for a project. However, we are missing two very important pieces: the acquisition of the properties, and the committing of those properties once they have changed.

    Unfortunately, we’re limited in what we can do with a dashboard plugin. The dashboard can be considered nothing more than a front-end for the actual ccnet service. It lets the user kick off builds, and inspect the status of current or past builds. It has some rather sophisticated reporting services, but provides little in the way of actually manipulating the ccnet service itself.

    That entire paragraph is the long-winded way of saying that the dashboard api does not provide us with a means to really *do* anything with a given project. That’s why we need a server side plugin.

    NAnt script properties
    Now that we’ve decided that we would like our plugin to modify the properties for a nant script, we need to decide how to actually accomplish this work.

    Those that are familiar with the existing ccnet/nant integration will recall that nant task has a property called BuildArgs. This property allows the configuration author to augment how nant executes a script by passing nant command line arguments. We will use this property and a bit of inheritance.

    Custom Task
    I’ve created a new class called ParameterizedNAntTask which implements ccnet’s ITask interface. This task will be substitute your existing nant task references in your ccnet configuration. This task will internally delegate to the existing NAnt task, so we benefit from the existing nant integration behavior. Additionally, the task will expose an array of PropertyItem objects(this class was introduced in the last post). When this task is executed, the properties will be passed to the inner nant task via the BuildArgs property, so that the properties are injected into the nant script at runtime.

    The full code listing for this class follows:

    /// <summary>
    /// This object is the heart of the server side plugin. This object implements the core ITask interface, and 
    /// wraps/delegates CCnet's built-in nant task. 
    /// 
    /// This task allows the delegated nant task to be paramterized. 
    /// </summary>
    [ReflectorType( "parameterizedNant" )]
    public class ParameterizedNAntTask : ITask
    {
    
    	/// <summary>
    	/// The inner nant task 
    	/// </summary>
    	private NAntTask _innerTask;
    
    	/// <summary>
    	/// The properties for the build
    	/// </summary>
    	private PropertyItem[] _properties;
    
    	/// <summary>
    	/// Gets/Sets the inner nant task 
    	/// </summary>
    	[ReflectorProperty( "nant" , Required = true )]
    	public NAntTask InnerTask
    	{
    		get
    		{
    			return _innerTask;
    		}
    		set
    		{
    			_innerTask = value;
    		}
    	}	//END InnerTask property
    
    	/// <summary>
    	/// Gets/Sets the array of property objects that will be passed to the inner nant task
    	/// </summary>
    	[ReflectorArray( "properties" )]
    	public PropertyItem[] Properties
    	{
    		get
    		{
    			return _properties;
    		}
    		set
    		{
    			_properties = value;
    		}
    	}	//END Properties property
    
    	#region ITask Members
    
    	/// <summary>
    	/// Delegates to the inner nant task
    	/// </summary>
    	public void Run( IIntegrationResult result )
    	{
    		// set the nant tasks's build args; the existing object's build args will be replaced
    		string argText = GetBuildArgs();
    		_innerTask.BuildArgs = argText;
    		
    		_innerTask.Run( result );
    	}	//END Run method 
    
    	#endregion
    
    	/// <summary>
    	/// Converts the defined property items into name/value pairs that will be 
    	/// passed to nant via the command line(see the -D: nant switch)
    	/// </summary>
    	/// <returns></returns>
    	private string GetBuildArgs()
    	{
    		StringBuilder sb = new StringBuilder();
    
    		foreach( PropertyItem item in Properties )
    		{
    			string arg = string.Format( " -D:{0}={1}" , item.Name , item.Value );
    			sb.Append( arg );
    		}	//END property loop 
    
    		return sb.ToString();
    	}	//END GetBuildArgs method 
    
    }	//END ParameterizedNAntTask class 
    

    The first thing of note is the ReflectorType attribute; I’ve decorated the class with this attribute, naming it “parameterizedNant”. Remember, this value is how the task will be referred from the configuration file.

    The task has only two members: a private inner NAntTask object, and an array of PropertyItem objects. Each of these members are accessed via public properties.

    The NAntTask object reference is wrapped via the InnerTask public property(read/write). This property is decorated with the ReflectorProperty attribute, named “nant”, and is required.

    The PropertyItem array is accessed via the Properties public property. This property is decorated via the ReflectorArray attribute, which is likewise named “properties”.

    ** This custom task does not instantiate either of these members. We are relying on ccnet to inject these object references via the property setters.

    That leaves two methods: ITask.Run, and GetBuildArgs.

    GetBuildArgs
    The GetBuildArgs method returns a simple string that will contain all of the property declarations. This value will be provided to the inner nant task’s BuildArgs property so the property values will be injected into the script at runtime.

    It’s a fairly simple method: it simply loops through each PropertyItem in the Properties array, appending the property declaration in the form of ” -D:[name]=[value]”. Notice that each property value is not quote-escaped; so the property values within the configuration itself should be quote-escaped as necessary.

    ITask.Run
    The ITask interface defines a single method: void Run( IIntegrationResult ). Fortunately for us, we don’t need to figure out how to work with the IIntegrationResult interface because we’re delegating to an existing ITask implementation that already knows how to do so. Our implementation of this method simply calls the GetBuildArgs method to get an argument list of properties, and sets the inner nant task’s BuildArgs property to this value. Then it delegates to the inner task’s Run method. Pretty simple.

    So what does this look like in a configuration file?
    You’ve seen the code for the server side plugin. I hope it makes sense. But to put it into context, here is a bit of sample xml that would be used in the configuration file:

    <cruisecontrol>
      <project>
    
        <tasks>
          <parameterizedNant>
            <nant>
              <buildArgs />
              <buildFile>main.build</buildFile>
              <buildTimeoutSeconds>1320</buildTimeoutSeconds>
              <baseDirectory />
              <executable>nant.exe</executable>
              <logger>NAnt.Core.XmlLogger</logger>
              <nologo>True</nologo>
              <targetList />
            </nant>
            <properties>
              <property>
                <name>build.release</name>
                <value>false</value>
              </property>
              <property>
                <name>prop2</name>
                <value />
              </property>
              <property>
                <name>prop3</name>
                <value />
              </property>
            </properties>
          </parameterizedNant>
        </tasks>
        
      </project>
    </cruisecontrol>
    

    Notice that the parameterizedNant declaration is inserted before the normal nant declaration. In this case, you would define your nant setup normally. After the nant declaration is the element, which contains a series of elements.

    So from this sample xml, and from the code listing, one can infer that when ccnet kicks off this project, it will execute nant and pass it the “build.release”, “prop2”, and “prop3” property values via the command-line.

    What’s next?
    We have a dashboard plugin. We now have a server-side plugin. What’s next? Now we need to find a way for the dashboard plugin to augment the configuration for a project and set the embedded property values for the parameterizedNant task. That is the object of the next post.

    August 5, 2008

    CruiseControl.NET and multiple build configurations: Part 5 – My custom template

    Filed under: c#, Continuous Integration/CruiseControl.NET, dotnet, NAnt — treyhutcheson @ 3:38 pm

    [More Posts On This Topic]

    It’s been a long few months since my last post in this series. For that I apologize. Today I resume the series with the discussion of the custom template I’ve used for my properties plugin.

    Let’s not waste time and jump right into the template. It’s not very large:

    <h2>NAnt properties for project $projectName</h2>
    
    <table>
    	<tr>
    		<td width="20" />
    		<td width="350" >The properties defined on this page are passed to the NAnt executable via the command line as 
    		NAnt properties.</td>
    	</tr>
    </table>
    <br />
    #if ($props.Count == 0)
    	No properties
    #else
    	<form method="post">
    	<table class="section-table" cellspacing="0" border="0">
    		<tr class="sectionheader">
    			<td width="150">Property Name</td>
    			<td width="200">Value</td>
    		</tr>
    		#set( $rowCounter = 0 )
    		#foreach( $prop in $props )
    			
    			#if ( $rowCounter % 2 == 0 )
    				#set( $rowClass = 'section-oddrow' )
    			#else
    				#set( $rowClass = 'section-evenrow' )
    			#end
    			
    			<tr class="$rowClass">
    				<td>$prop.name</td>
    				<td><input type="text" name="row$rowCounter" value="$prop.value"></td>
    			</tr>
    			
    			#set( $rowCounter = $rowCounter + 1 )
    		#end
    		<tr>
    			<td colspan="2" align="right"><br /><input type="submit" name="Refresh" value="Commit"/></td>
    		</tr>
    		
    	</table>
    
    #end
    

    Before we examine the template, here is the output of the template:

    Output of the template

    Output of the template

    As you can see, it’s a simple and dirty page that’s not very pretty. However, it is functional. Now that you’ve seen the complete template and it’s output, lets examine it piece-by-piece.

    Header
    The header section of the template looks like this:

    <h2>NAnt properties for project $projectName</h2>
    
    <table>
    	<tr>
    		<td width="20" />
    		<td width="350" >The properties defined on this page are passed to the NAnt executable via the command line as 
    		NAnt properties.</td>
    	</tr>
    </table>
    <br />
    

    The template starts out with a simple H2 header element, and displays the name of the project via a reference to the $projectName variable. After that, it outputs a short description of what this page does. Pretty simple html so far.

    No properties defined?
    The next section of the template looks like this:

    #if ($props.Count == 0)
    	No properties
    #else
    

    This section determines if there are any property objects available for the template. The $props variable will be published to the template via the plugin, so we can refer to it here. If there are no properties, then we just output the text “No properties”. Short and sweet.

    What’s interesting to note here though is Velocity’s support of dot-notation evaluation. Notice the text “$props.Count”? This text causes Velocity to evaluate the $props object for a property called Count. So as long as the $props object has a public property named Count and can be compared to the literal ‘0’, we’re in good shape. We need to remember that when we advertise the object to the template when the template is generated.

    Forms and post-back
    The next bit of code declares a form, containing a table and a couple of table columns. Notice the form declaration has the method defined, but is not named nor does it define an action. I found that this declaration allows the form to post back to itself; actually, I borrowed this behavior from other templates distributed with the ccnet dashboard.

    Displaying the defined properties
    The rest of the template simply loops through all of the properties defined, and outputs the name and value of each.

    Notice the use of the $rowCounter variable? It’s defined before the looping begins. For each iteration of the loop, $rowCounter is evaluated by modulus 2 to determine if we are on an odd or even row, and sets the style accordingly. This lets each row be shaded on or off.

    Additionally, the $rowCounter variable serves another purpose: it allows us to refer to each input box for each property’s value. When the property name and value are emitted, the value is placed within an input text element. The name of the text element takes the form of “row$rowCounter”, which will expand at runtime to be “row0”, “row1”, etc. We’ll use that later in the plugin when processing the postback.

    Here’s the the code listing for the rest of the template:

    <form method="post">
    	<table class="section-table" cellspacing="0" border="0">
    		<tr class="sectionheader">
    			<td width="150">Property Name</td>
    			<td width="200">Value</td>
    		</tr>
    		#set( $rowCounter = 0 )
    		#foreach( $prop in $props )
    			
    			#if ( $rowCounter % 2 == 0 )
    				#set( $rowClass = 'section-oddrow' )
    			#else
    				#set( $rowClass = 'section-evenrow' )
    			#end
    			
    			<tr class="$rowClass">
    				<td>$prop.name</td>
    				<td><input type="text" name="row$rowCounter" value="$prop.value"></td>
    			</tr>
    			
    			#set( $rowCounter = $rowCounter + 1 )
    		#end
    		<tr>
    			<td colspan="2" align="right"><br /><input type="submit" name="Refresh" value="Commit"/></td>
    		</tr>
    		
    	</table>
    
    #end
    

    Great – we have a template. Now what?

    Generating the template
    We’ve already covered how templates are generated at runtime by the dashboard. Now we can discuss how to generate this specific template from the properties plugin.

    The first thing we need to do is to determine if the template is posting-back, or if the template is being generated from scratch. Why? Because if it’s a post back, we need to commit/save the modified properties, otherwise we need to publish the properties to the template afresh. So how do we do that? More specifically, how does the dashboard tell us that the request is postback?

    Fortunately for us, the dashboard stuffs a parameter into the request called “Refresh”. So all we have to do is check the request for this parameter. It doesn’t matter matter what the parameter’s value is. If the parameter exists, it’s a postback. Now we need to know how to interrogate the request for parameters. Fortunately the IRequest interface is not that complicated, and it exposes a method named FindParameterStartingWith. From what I can determine, this method does not actually return the parameter value, merely the parameter key. So, using this behavior, we can define a new method for our plugin:

    /// <summary>
    /// Determines if the current page view is a normal view, or if the form has
    /// been submitted as a postback(and subsequently the modified property values will
    /// need to be committed back to the ccnet server).
    /// </summary>
    private bool IsRefresh( IRequest request )
    {
      string key = request.FindParameterStartingWith( "Refresh" );
      return key != string.Empty;
    }	//END IsRefresh method
    

    Now that we know if the page is a postback or not, what do we do next? Since we’re publishing properties, we need to know what properties to publish to the template. Additionally, if the request is indeed a postback, we need to commit the saved properties.

    Here’s my full implementation of the ICruiseAction.Execute method:

    /// <summary>
    /// Executes the request
    /// </summary>
    public IResponse Execute( ICruiseRequest cruiseRequest )
    {
    	// get the request and the current project
    	IRequest req = cruiseRequest.Request;
    
    	// read all of the properties as they are currently defined in the project's config
    	List<PropertyItem> existingProperties = ReadItems( cruiseRequest.ProjectSpecifier );
    
    	// determine if we are displaying the properties, or committing the properties
    	if( IsRefresh( req ) )
    		return Commit( existingProperties , cruiseRequest );
    	else
    		return ExecuteNew( existingProperties , cruiseRequest );
    }	//END Execute method 
    

    This is a pretty short method implementation. The first thing it does it acquire the actual IRequest reference. Next it reads the properties that are defined for the current project(notice the reference to the cruiseRequest.ProjectSpecifier property?). Afterwards, it calls the IsRefresh method(which we already detailed) and either calls Commit or ExecuteNew. Let’s not worry about the Commit method just yet – it will demand another post entirely. For now let’s concentrate on the calls to ReadItems and ExecuteNew.

    ReadItems method
    From the Execute implementation, you can infer that the ReadItems method returns a List of type PropertyItem. The PropertyItem class is very simple, and I won’t waste time discussing it in detail. The full listing follows:

    /// <summary>
    /// This type reflects custom properties defined in the config file, as child elements to the 
    /// parameterizedNant task. PropertyItem objects are simple name/value pairs.
    /// </summary>
    [ReflectorType( "property" )]
    public class PropertyItem
    {
    	/// <summary>
    	/// The name of the property
    	/// </summary>
    	private string _name;
    
    	/// <summary>
    	/// The value of the property
    	/// </summary>
    	private string _value;
    
    	/// <summary>
    	/// Default constructor
    	/// </summary>
    	public PropertyItem()
    		: this( string.Empty , string.Empty )
    	{
    	}	//END constructor 
    
    	/// <summary>
    	/// Parameterized constructor to initialize the object with its name/value pair
    	/// </summary>
    	/// <param name="name">the name of the property</param>
    	/// <param name="value">the value of the property</param>
    	public PropertyItem( string name , string value )
    	{
    		_name = name;
    		_value = value;
    	}	//END constructor 
    
    	/// <summary>
    	/// Gets/Sets the name of the property
    	/// </summary>
    	[ReflectorProperty( "name" )]
    	public string Name
    	{
    		get
    		{
    			return _name;
    		}
    		set
    		{
    			_name = value;
    		}
    	}	//END Name property
    
    	/// <summary>
    	/// Gets/Sets the value of the property
    	/// </summary>
    	[ReflectorProperty( "value" )]
    	public string Value
    	{
    		get
    		{
    			return _value;
    		}
    		set
    		{
    			_value = value;
    		}
    	}	//END Value property
    
    	public override string ToString()
    	{
    		return string.Format( "{0}={1}" , _name , _value );
    	}	//END ToString method 
    
    }	//END PropertyItem class 
    

    The ReadItems needs to read the nant properties defined for a given project configuration. Out of the box, ccnet does not give us this behavior. That means we will be developing a server-side plugin that is a sister to the dashboard plugin. Like the implementation of the Commit method, that topic is *way* too deep for this post. So for right now, just implement the ReadItems method so it returns a mock list of PropertyItem objects.

    ExecuteNew method
    This post is getting pretty long, so fortunately the ExecuteNew method is pretty simple. The purpose of the ExecuteNew method is to take a list of PropertyItem objects, and publish them to the template so they can be available at evaluation time. This method is only executed for new page requests, and not for postbacks. Here’s the full implementation of the ExecuteNew method:

    /// <summary>
    /// Generates a new view from scratch with the property items that are presently defined.
    /// </summary>
    private IResponse ExecuteNew( List<PropertyItem> properties , ICruiseRequest cruiseRequest )
    {
    	Hashtable map = new Hashtable();
    	map.Add( "props" , properties );
    	map.Add( "projectName" , cruiseRequest.ProjectName );
    
    	return _viewGenerator.GenerateView( "ViewProjectProperties.vm" , map );
    }	//END ExecuteNew method 
    

    When you looked at the template, did you think it would be difficult to add a list of objects? Fortunately for us, it’s exceedingly simple. Remember that the hashmap published to the template is a simple string/object affair. So to publish the properties, we simply add them to the hashtable with the key of “props”. After that, it’s nothing that hasn’t already been covered.

    Here would be a good time to point out how stupid it is to use string literals. Just remember, this is nothing more than a blog post🙂.

    ** Styles
    The template I list earlier referenced a few different styles. These styles are defined within the file “cruisecontrol.css” and is in the root of the dashboard’s folder. I’m sure you are free to define your own styles in your template or even in an external css file, but I just played around with the template until I found something that wasn’t too obnoxious.

    The next post will cover the topic of the server-side plugin. At this point you’re probably wondering why we even need one?

    April 28, 2008

    CruiseControl.NET and multiple build configurations: Part 4 – Velocity Templates

    Filed under: c#, Continuous Integration/CruiseControl.NET, dotnet, NAnt — treyhutcheson @ 3:16 am

    [More Posts On This Topic]

    This is post 4 in a series devoted to extending CruiseControl.NET to support multiple build configurations for a single project. In this post, I will cover the use of the Velocity engine and outputting responses from the plugin.

    ICruiseAction.Execute method
    The second post in this series addressed the interfaces that your plugin is required to implement. The main method to implement is the ICruiseAction.Execute method, which accepts an ICruiseRequest object and returns an IResponse object. What exactly is an IResponse, and how can your plugin create it?

    As mentioned in other posts, if you ever have questions about how something works, I suggest that you consult the ccnet source code. The main reference that I’ve been using is the ProjectReportProjectPlugin class. Examination of this class shows that it uses an object called IVelocityViewGenerator to generate responses. It acquires the IVelocityViewGenerator reference from its constructor(injected by the runtime).

    IVelocityViewGenerator – How is it used?
    The following code has been borrowed from the ProjectReportProjectPlugin’s Execute method implementation:

    Hashtable velocityContext = new Hashtable();
    
    velocityContext["projectName"] = projectSpecifier.ProjectName;
    velocityContext["externalLinks"] = farmService.GetExternalLinks(projectSpecifier);
    velocityContext["noLogsAvailable"] = (buildSpecifiers.Length == 0);
    
    return viewGenerator.GenerateView(@"ProjectReport.vm", velocityContext);
    

    Take a moment and let the code sync in. The object is only used once, in the method’s return statement. The interface is rather simple, and has but a single method. The interface follows:

    public interface IVelocityViewGenerator
    {
      HtmlFragmentResponse GenerateView(string templateName, Hashtable velocityContext);
    }
    

    If you examine the HtmlFragmentResponse object, it implements the IResponse interface, which is the return type for the ICruiseAction.Execute method.

    The GenerateView method accepts a template name, and a velocityContext; a basic hashtable. The return statement from the code listing shows a string literal, “ProjectReport.vm”, and a hashtable.

    The hashtable that is passed to the GenerateView method has been populated with a number of items, such as the project name. The hash table is keyed on string values, and the values are objects.

    Now that you know what the GenerateView method accepts, what do you think the arguments actually are? What is “ProjectReport.vm”, and what is that hashtable?

    Velocity & Templates
    The literal “ProjectReport.vm” is obviously a filename, so I looked through the ccnet distro for the file. The file is located in the dashboard’s templates directory, along with a bunch of other .vm files. It’s a simple text file, that appears to be some sort of html template. The full source for the file follows:

    <h2>Project Report for $projectName</h2>
    
    #if ($externalLinks.Length > 0)
    <table class="ExternalLinks">
    	<tr>
    		<th>External Links</th>
    	</tr>
    	#foreach ($externalLink in $externalLinks)
    	<tr bgcolor="Ivory">
    		<td align="Left">
    			<a href="$externalLink.Url">$externalLink.Name</a>
    		</td>
    	</tr>
    	#end
    </table>
    #end
    
    <p>
    #if ($noLogsAvailable)
    There are currently no build logs available for this project - make sure your Build Server configuration is correct and that this Project's build is not throwing any exceptions
    #else
    	#if ($pluginInfo)
    	<h3>Most Recent Build Results</h3>
    	$pluginInfo
    	
    	Click <a href="$mostRecentBuildUrl">here</a> for the full build report.
    	#else
    	Click <a href="$mostRecentBuildUrl">here</a> for the most recent build report.
    	#end
    #end
    </p>
    

    Like I said, it appears to be html. However, scattered throughout the source are various directives. Statements or commands appear to begin with the pound sign, while property/variable references appear to begin with a dollar sign. In fact, the very first line of the template refers to a property/variable named $projectName. Does that ring a bell? If you refer to the first code listing, the velocityContext hashmap has a key named “projectName”.

    Putting two and two together, its apparent that the IVelocityViewGenerator.GenerateView method takes the name of a template, and the second argument, the velocityContext hashmap, contains references that can be expanded when the template is processed.

    This templating system piqued my interest, and I discovered that Velocity is the name of a templating project hosted by Apache. The official home page is available here. The reference is here, and the user’s guide is here. These resources will prove to be very valuable as you develop your own velocity templates.

    And one more thing. This is a dotnet project, so the original java version of Velocity isn’t being used. One of the project references for the dashboard is an assembly named NVelocity. A quick google search of that term yield the home page for the project, which is a dotnet port of Velocity. I haven’t fully researched the project, so I don’t know how complete it is, nor do I know the differences. This definitely deserves further investigation.

    Conclusion
    You should now know how the ICruiseAction.Execute method behaves, and how to generate responses via the velocity runtime. I’ve also provided the docs and reference for Velocity, which will aid you in composing your own custom templates.

    The next post will cover the template that I have developed for my build configuration plugin.

    April 23, 2008

    CruiseControl.NET and multiple build configurations: Part 3 – Dependency Injection

    Filed under: c#, Continuous Integration/CruiseControl.NET, dotnet, NAnt — treyhutcheson @ 3:09 pm

    [More Posts On This Topic]

    This post is part 3 in a series devoted to extending CruiseControl.NET to support multiple build configurations for a single project. In this post, I will cover a single topic: ccnet’s method of dependency injection.

    Constructor Dependency Injection
    The ccnet docs for developing dashboard plugins contains a section titled “Defining dependencies”, which states:

    The CruiseControl.NET Web Dashboard uses a Constructor Dependency Injection (CDI) pattern to enable classes to define what types they are dependent on. The Dashboard API has a number of types you can depend upon which can do things like return you the currently viewed build log. You can also specify dependencies to your own types. It is recommended that you use interfaces to define responsibilities.
    The Dashboard's CDI implementation does not currently allow for runtime configuration. It will use sensible defaults where available. We plan on adding more configuration later.

    That’s all great, but what does it mean? From my first pass of the docs, I understood that somehow my constructor would be analyzed and the correct object references would be provided to my plugin. A fine theory, but how does it work in practice? Specifically, *which* objects would I receive?

    Again I consulted the ccnet’s source code. I referred to the handy ProjectReportProjectPlugin class, and found the following constructor:

    public ProjectReportProjectPlugin(IFarmService farmService, IVelocityViewGenerator viewGenerator, ILinkFactory linkFactory)
    {
    }
    

    This class is receiving a reference to the IFarmService interface, IVelociyViewGenerator interface, and the ILinkFactory interface. I copied these same arguments into the constructor for my own plugin, and attached to the asp.net process. I refreshed the dashboard page, caught the break point in the debugger, and sure enough, my constructor received a valid object reference for each argument. It must be magic.

    Constructor Argument Type Availability
    I’ve now established that at the minimum I can receive valid object references for three interfaces. But what else is available? At this point, I didn’t know what any of these interfaces actually *did*, so I was worried about obtaining dashboard services that might or might not be available through these interfaces.

    Still attached to the debugger, I used the Call Stack window to go backwards and find out what was actually instantiating my plugin. This technique is pivotal; you will use this technique many times during the development of your plugin, depending on the services your plugin requires.

    Following the call stack backwards shows a bunch of methods being executed within the NetReflector assembly, and the first method one can find that is debuggable is ThoughtWorks.CruiseControl.WebDashboard.DashboardConfigurationLoader.Load method. The last line of the method looks like this:

    return NetReflector.Read(node, typeTable);
    

    Evidently, the dashboard is relying upon the NetReflector runtime to deserialize all of the plugins from the dashboard config file. The read method accepts an xml node, and a NetReflectorTypeTable. This guy proves very interesting.

    The NetReflectorTypeTable has a member named “instantiator”, of type ObjectionNetReflectorInstantiator. That object has a member named “objectStore”, of type ObjectionStore. All of these objects and members are viewable from the runtime type inspector.

    The ObjectionStore apparently contains all of the reflection information and available object instances. The ObjectionStore has a hashtable named “implementationTypes”, and it is this hashtable that I used to determine which objects were available to be injected into my constructor.

    My implementationTypes map has 17 types available. I have no idea if these types are universal; I imagine the contents of the implementationTypes table will vary depending on the deployment. Inspection of my copy of this table yields the following types:

    ThoughtWorks.CruiseControl.WebDashboard.IO.ICruiseRequest
    ThoughtWorks.CruiseControl.Core.Reporting.Dashboard.Navigation.IPhysicalApplicationPathProvider
    ThoughtWorks.CruiseControl.Remote.ICruiseManagerFactory
    ThoughtWorks.CruiseControl.WebDashboard.Dashboard.ILinkFactory
    ThoughtWorks.CruiseControl.Core.Reporting.Dashboard.Navigation.ICruiseUrlBuilder
    ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IAllBuildsViewBuilder
    ThoughtWorks.CruiseControl.WebDashboard.MVC.View.IVelocityViewTransformer
    ThoughtWorks.CruiseControl.WebDashboard.ServerConnection.ICruiseManagerWrapper
    ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IProjectGrid
    ThoughtWorks.CruiseControl.WebDashboard.ServerConnection.IFarmService
    ThoughtWorks.CruiseControl.WebDashboard.Dashboard.ILinkListFactory
    ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IProjectGridAction
    ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IActionInstantiator
    ThoughtWorks.CruiseControl.WebDashboard.Configuration.IDashboardConfiguration
    ThoughtWorks.CruiseControl.Core.Util.IMultiTransformer
    ThoughtWorks.CruiseControl.WebDashboard.MVC.View.IVelocityViewGenerator
    ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IBuildNameFormatter

    I really don’t know what most of these objects do, but it’s not too hard to figure out when you have the source code.

    Now you know how to determine what types are available for your constructor at runtime. From here, you can consult the source code to determine what services these interfaces provide, and parameterize your constructor accordingly.

    The next post will cover the use of the Velocity subsystem.

    April 22, 2008

    CruiseControl.NET and multiple build configurations: Part 2 – Starting the dashboard plugin

    Filed under: c#, Continuous Integration/CruiseControl.NET, dotnet, NAnt — treyhutcheson @ 10:11 am

    [More Posts On This Topic]

    This post continues a thread about how to support multiple build configurations from CruiseControl.NET and NAnt.

    My goal was to expose some method through the web dashboard by which the user could select a build configuration and kick off a build. To accomplish that goal, some modification or extension of the dashboard is required. I decided against modifying the actual ccnet source, and instead chose to develop a dashboard plugin.

    I started with the official ccnet docs regarding the subject, available here. I suggest readers acquaint themselves with the material.

    Plugins Won’t Load
    Before I get to the meat of the post, I must warn you that as it stands *today*, custom dashboard plugins will not load. The documentation states that plugin assemblies should be named “ccnet.*.plugin.dll”, and be placed in the dashboard’s bin folder. Presently, the code for loading plugins for the dashboard looks for assemblies via the path returned from Assembly.GetExecutingAssembly().Location(the Assembly.GetExecutingAssembly() call refers to the actual dashboard assembly). This line of code is logical, but it returns the path of the assembly as it is hosted by asp.net, which is some deep-dark directory buried within Windows because of asp.net’s shadow copy.

    The dashboard’s source code must be changed. The Assembly.CodeBase property should be used instead, which returns a string uri to the assembly. This uri then needs to be parsed to obtain the actual physical location of the assembly so that satellite assembly lookup can work. I started a thread in the ccnet-devl groups. A ticket was opened, and as far as I know, the issue has been corrected. I suspect the correct code will be present for version 1.4, whenever that is released. Please note, however, that that is my assumption only, and has not been promised in any way by the ccnet team. Details on the defect, and how to fix it, are available here. Also please note that until the correction for the defect is available, you must pull down a copy of the source code, make the fix yourself, and deploy the updated dashboard binary.

    Goal
    I’ve already established my goal. Now let me describe how I will achieve that goal. Building upon my previous post about using NAnt properties to build debug or release configurations via the csc NAnt task, I have decided that I would like to allow the user to modify the initial values of NAnt properties for each configured ccnet project. The modified values for these properties must ultimately be recognized by ccnet so that these values can be passed to the root NAnt build script for the project when it is launched. For these reasons I have named my dashboard plugin “PropertiesPlugin”.

    Source Code & Debugging
    It’s possible to develop a dashboard plugin without benefit of the ccnet source code, but I strongly encourage you to download the source as it will make things much easier. For my development, I used version 1.2.1 of the source.

    To debug your plugin, you will need to make sure you have the ccnet server running(either as a service or from the console), and you will need to attach to the aspnet_wp.exe process. Make sure that the build output of the project is the dashboard’s bin directory, otherwise the debugger won’t hit your breakpoints because the assembly will be out of date. Another issue that I sometimes face is that objects provided to your plugin from the dashboard will not evaluate and cannot be browsed. In such cases, I use the IIS control panel applet to stop/start the ccnet www app, which usually corrects the problem.

    Starting the plugin, interfaces, and NetReflector
    To start the plugin, create a public class that implements the ThoughtWorks.CruiseControl.WebDashboard.MVC.Cruise.ICruiseAction interface, and the ThoughtWorks.CruiseControl.WebDashboard.Dashboard.IPlugin interface. Additionally, the class must be decorated with the ReflectoryType attribute(exposed via the Exortech.NetReflector interface).

    At a minimum, your plugin assembly will require references to NetReflector.dll, ThoughtWorks.CruiseControl.Core.dll, ThoughtWorks.CruiseControl.Remote.dll, and ThoughtWorks.CruiseControl.WebDashboard.dll (each is present in the dashboard’s bin folder).

    Example:

    [ReflectorType( "propertiesPlugin" )]
    public class PropertiesPlugin : ICruiseAction , IPlugin
    {
      private const string PLUGIN_DESCRIPTION = "NAnt Project Properties";
    
      private const string ACTION_NAME = "ViewProjectProperties";
    
      #region IPlugin Members
      public IResponse Execute( ICruiseRequest cruiseRequest )
      {
        return null;
      } //END Execute method
      #endregion
    
      #region IPlugin Members
    
      /// <summary>
      /// Returns the description for the link
      /// </summary>
      public string LinkDescription
      {
        get
        {
          return PLUGIN_DESCRIPTION;
        }
      } //END LinkDescription property
    
      public INamedAction[] NamedActions
      {
        get
        {
          return new INamedAction[] { new ImmutableNamedAction( ACTION_NAME , this ) };
        }
      } //END NamedActions property
    }
    

    The above code is the bare minimum for creating a plugin. As it stands, it will not execute correctly, because the ICruiseAction.Execute implementation returns null. Ultimately we will need to return a valid IResponse by using the Velocity view generator, but I’ll touch on that subject later. Regardless, at this point the plugin should compile, and you should be able to debug it.

    ReflectorType Attribute
    As noted earlier, the class is decorated with the ReflectorType attribute. This attribute is significant. When the dashboard loads the configuration(dashboard.config), it will attempt to load named plugins from all assemblies(internal and external) via NetReflector. This plugin is named “propertiesPlugin”, and NetReflector will look for any public types decorated with this value.

    ICruiseAction interface
    This class implements the ICruiseAction interface. This interface is used to perform any number of actions from the dashboard user interface. Almost every link you click within the dashboard is represented by an accompanying action implementation.

    This interface defines but a single method: IResponse Execute( ICruiseRequest req ). The basic idea is that the action accepts the current request and generates a response. It’s pretty easy really. The request identifies all kinds of things, including the current ccnet server and project. The response is expected to be consumable html that will ultimately be sent to the browser. More on that later.

    IPlugin interface
    The IPlugin interface is what allows the dashboard to expose your plugin via its user interface. The IPlugin interface is rather simple:

    interface IPlugin
    {
      string LinkDescription
      {
        get;
      }
    
      INamedAction[] NamedActions
      {
        get;
      }
    }  
    

    The LinkDescription property is the text for the link as it will be displayed to the user. The INamedAction[] property returns an array of named actions that will be executed when the link is clicked by the user.

    I have not yet experimented with what happens when more than one named action is returned in the array, but I assume the actions are executed in succession. I do wonder however what happens to the IResponse from each action’s Execute method. Are they just appended to the stream? Unknown.

    In the previous example, the plugin’s LinkDescription implementation simply returns a constant value. However, examine the NamedActions implementation:

    public INamedAction[] NamedActions
    {
      get
      {
        return new INamedAction[] { new ImmutableNamedAction( ACTION_NAME , this ) };
      }
    }	
    

    This code defines an INamedAction array inline, with only a single element. That element is of type ImmutableNamedAction, with an action name and a reference to the actual action implementation(this – the plugin).

    At this point I would like to explain that I didn’t inherently know to use the ImmutableNamedAction class. In fact, I didn’t know how to implement this property. To figure that part out, I consulted the dashboard source code, and found the ProjectReportProjectPlugin class. I saw how it implemented this property, and borrowed its behavior.

    Regardless, it is important to note the two entities being used by the dashboard here, and the interaction of these entities.

    The dashboard will load the configuration, see that a plugin is defined, and load the plugin. This plugin must implement the IPlugin interface. This interface allows the dashboard to expose the plugin to the user interface. It also instructs the dashboard as to what actions are available from the plugin.

    The actions for the plugin are altogether different from the plugin. An action is what happens when the link for the plugin is clicked. Notice the distinction?

    In this plugin, the action and the plugin are the same object, only because this plugin is very simple.

    Plugin Configuration
    The documentation states that dashboard plugins can take one of four forms:

  • Farm Plugin
  • Server Plugin
  • Project Plugin
  • Build Plugin
  • More information about what type of plugin does what is available in the documentation. For my purposes, I chose to implement a project plugin because I wanted to be able to modify the build properties for each project independently.

    After you have implemented the basic plugin shell and deployed the assembly, you must configure the dashboard to recognize the plugin. This topic is also covered in the documentation. But for the purposes of this plugin, the configuration was very simple. I simply added a node named “propertiesPlugin” as a child element to the “projectPlugins” node(in the file dashboard.config). Remember, this node name – projectPlugins – must match the value of the ReflectorType attribute.

    An example dashboard config follows(boilerplate stuff exempted):

    <dashboard>
      <plugins>
        <farmPlugins>
          ...
        </farmPlugins>
        <serverPlugins>
          ...
        </serverPlugins>
        <projectPlugins>
    
          <!-- this declaration instructs the dashboard to include the project plugin -->
          <propertiesPlugin />
          
        </projectPlugins>
      </plugins>
    </dashboard>
    

    That’s it for now. The above code(along with the dashboard source code fix) should get any one started with a very basic project plugin. I will detail the Velocity stuff and ccnet’s use of constructor dependency injection in the next post.

    Older Posts »

    Create a free website or blog at WordPress.com.