Tuesday, January 27, 2009

Customizing the code of CCNet : part 2 : Creating a publisher

Some stuff you need to know :
Tasks and publishers are the same in ccnet, the only difference is the handling of errors. If a class defined in the tasks section throws an error, the execution of the entire tasks section in the ccnet.config file is stopped. If an error occurs in a class defined in the publisher section, the current publisher will stop, but the next publisher of the publishers section will still be called.

When you open the solution c:\source\ccnet\project\ccnet.sln
you see that there are (for the moment) 10 projects :


CCTray : The CCTray application
CCTrayLib : Library for CCTray, here resides all its logic
Console : The CCtray Console server application
Core : Here resides the majority of the functionality
Objection : Code responsible for creating objects (heavy code)
Remote : Communication layer server
(communication between cctray/dashboard and a CCNet
Service : The CCNet service counterpart of the CCNet console application
UnitTests : All the unit tests of CCNet
Validator : A winform application that validates a ccnet.config file
WebDashboard : The Dashboard application

Now, the publishers are part of the Core, so if you expand the core project,
you will see a publishers folder. This holds all the publishers.



For example, we'll be creating a very simple publisher : FilePublisher.
This will create a txt file with the results of a build.
For configuration : it will take the path of the file.

Now, creating this publisher :
° Create a class FilePublisher in the publishers folder
° change .publishers into .Publishers in the namespace
° add using Exortech.NetReflector;
° make the class public

You have now the following :

using System.Collections;
using System.IO;
using System.Xml.Serialization;
using Exortech.NetReflector;

namespace ThoughtWorks.CruiseControl.Core.Publishers
{
public class FilePublisher : ITask
{
public void Run(IIntegrationResult result)
{
throw new System.NotImplementedException();
}

}
}

For letting the CCNet-system know that this is a publisher/task, the class must
implement the interface ITask. This interface foresees a Run method with an arguement of IIntegrationResult; The argument holds all the information of the current build.
So in fact, writing this publisher is nothing more than :
° opening the defined target file
° write the wanted properties of IIntegrationResult
° close the file
you see, not that hard.

Ok, back to the code. First add the code for the file argument. This is done by
adding a public property, lets say ResultFile. If you want this property to be definable in ccnet.config, you must decorate it with a Reflector attribute, coming from the Exortech.NetReflector namespace.
Also, the class must have such an attribute, to property is passed to the correct class.

using System.Collections;
using System.IO;
using System.Xml.Serialization;
using Exortech.NetReflector;

namespace ThoughtWorks.CruiseControl.Core.Publishers
{
[ReflectorType("filePublisher")]
public class FilePublisher : ITask
{
private string resultFile = "Result.txt";

[ReflectorProperty("resultFile")]
public string ResultFile
{
get { return resultFile; }
set { resultFile = value; }
}

public void Run(IIntegrationResult result)
{
throw new System.NotImplementedException();
}

}
}

All that is left, is the code that actually writes the wanted results to the file.
This results for example in :

using System.Collections;
using System.IO;
using System.Xml.Serialization;
using Exortech.NetReflector;

namespace ThoughtWorks.CruiseControl.Core.Publishers
{
[ReflectorType("filePublisher")]
public class FilePublisher : ITask
{
private string resultFile = "Result.txt";

[ReflectorProperty("resultFile")]
public string ResultFile
{
get { return resultFile; }
set { resultFile = value; }
}

public void Run(IIntegrationResult result)
{
PublishIt(ResultFile, result);
}


private void PublishIt(string targetFile, IIntegrationResult result)
{
StreamWriter Result = new StreamWriter(targetFile, false);
System.Text.StringBuilder Info = new System.Text.StringBuilder();

Info.AppendFormat("Project {0} has status {1}", result.ProjectName, result.Status);
Info.AppendLine();
Info.AppendFormat("Modifications :");
Info.AppendLine();
foreach (Modification mod in result.Modifications)
{
Info.AppendFormat(mod.ToString());
Info.AppendLine();
}
Result.WriteLine(Info.ToString());
}
}
}


In ccnet.config, you define it as follows in the publishers section :

<publishers>
<filePublisher resultFile="c:\logsresult.txt" />
</publishers>

2 comments:

  1. Came across your blog from Craig Sutherland's blog. It's great to see a post like this! I plan on trying my hand in the code shortly.

    Btw, your blog is *extremely* narrow (in IE and FF), making the code samples difficult to read; I can only see 53 columns of characters.

    ReplyDelete
  2. thanks for letting me know,
    about the narrowness of the blog,
    this has to do with the 'themes' of blogspot.
    I'll try to fix it ;-)

    ReplyDelete