Steve Trefethen
Contact me Send mail to the author(s)
About Me View my LinkedIn profile

Powered by discountASP.NET
referal ID: sdtref
Why recommend discountASP.NET?

Archives
Statistics
Total Posts: 524
This Year: 26
This Month: 0
This Week: 1
Comments: 1835
Disclaimer
The posts on this weblog are provided �AS IS� with no warranties, and confer no rights. The opinions expressed herein are my own personal opinions and do not represent my employer�s view in any way.
# Thursday, January 22, 2009

Trigger exception logging now supported in Cruisecontrol.NET

Tagged: Continuous Integration | Open Source

Tags:  | 
A little over a year ago I wrote a post expanding on a problem I was having in CCNET where exceptions thrown by a Source Control  Provider wouldn’t trigger the build publisher meaning there was no easy way to see something went wrong. Well, it took awhile but persistence paid off and thanks to Ruben Willems, who works on CCNET, a recent check-in has addressed this problem. Working from suggestions included in my patch he developed a more complete solution, after several iterations, that deals with a number of build status issues among other things. In case you’re interested the details can be found here and here.

Aside from the details you can now rest assured that if your Source Control Provider throws an exception the build publisher(s) will execute to let you know. Additionally, a new project property was added allowing for control over the number of exception that can occur before the project is simply stopped called maxAmountOfSourceControlExceptions which you’d use like this:

<project name="ProjectName" maxAmountOfSourceControlExceptions="3">
...
</project>
Again, big thanks to Ruben for his time and effort!
Comments [0] # permalink Posted @ 2:22PM
# Sunday, November 23, 2008

Modifying emails sent using CruiseControl.NET's email publisher

Tagged: Continuous Integration | Open Source

Tags:  | 

Last year I wrote a brief post on configuring email for CCNET whereas this time I’m looking at changing the content of mail sent by the email publisher. Btw, modifying the email is identical to controlling the look of the web dashboard so this really covers both topics. The first thing to note is that the format of the email is specified in ccnet.exe.config (or ccservice.exe.config) as follows:

<!-- Specifies the stylesheets that are used to transform the build results when using the EmailPublisher -->
<xslFiles>
    <file name="xsl\header.xsl"/>
    <file name="xsl\compile.xsl"/>
    <file name="xsl\unittests.xsl"/>
    <file name="xsl\fit.xsl"/>
    <file name="xsl\modifications.xsl"/>
    <file name="xsl\fxcop-summary.xsl"/>
</xslFiles>

The above collection of XSL files (included, by default, with CCNET) are used to construct the email body in the order they’re listed. To change the layout or add/remove content you can either edit the above .xsl files or add your own to augment the output as you see fit. The down side is you’ll need at least some understanding of XSL to make those changes.

In the case of my EDI work with CCNET I want to publish error details from custom CCNET tasks using the email publisher. By modifying the email content I can include details such as what caused the build to fail. When these custom tasks execute they contribute XML to the build log which looks similar to the following:

<editask warning_count="0" error_count="2">
  <files>
    <error name="c:\edi\partners\inbound\P100451612.850.TXT" message="Invalid buyer or buyer not configured to allow EDI DUNS: 00xxxxxx9" />
    <error name="c:\edi\partners\inbound\SYSCO850.txt" message="Invalid buyer or buyer not configured to allow EDI DUNS: 8xxxxxxx7" />
  </files>
</editask>

To have this content included in the email I modified msbuild2ccnet.xsl which is a stylesheet from Christian Rodemeyer to parse MSBuild output (available here), and tweaked it to handle the above XML. I then added <file name="xsl\edi.xsl"/> to the above <xslfiles> section. When the build fails this content is included in the email that’s published. Btw, making the same change to dashboard.config and copying the .xsl file to the webdashboard\xsl folder will update the CCNET web dashboard.

Comments [0] # permalink Posted @ 9:24PM
# Friday, June 13, 2008

Continuous Integration means developers doing command line builds

Tagged: Continuous Integration | Tips

Tags:  | 

Building from the command line != building from the IDE

I just spent the last several hours fixing a large number of assembly references in a big C# solution. That means lots of errors like the following that leave you grep’ing your way through .sln/.csproj files to figure out what’s gone wrong:

Solution file warning MSB4051: Project {A9E00AF7-A1E0-4E65-90EF-CB66E8580
9AE} is referencing a project with GUID {958E0376-0272-4149-A1CF-E03521D12A72}, but a project with this GUID was not found in the .SLN file.

And builds stats that look like this:

CruiseControl.NET build statistics

Not good. It doesn’t help when VS.NET itself can cause these problems yet continue on blissfully. What I ran into today was a different reason for the same error message, this time around it wasn’t a missing "EndProject". The issue was that the second GUI was referring to a project that not only wasn’t in the .sln file but no longer existed. Regardless, the outcome was the same, building from VS.NET worked but executing MSBuild from the command line failed. WHY?! IMO, VS.NET should have failed just like MSBuild because it’s not doing you any favors when it doesn’t fail consistently along with MSBuild rather that’s just masking a problem. In addition, the error could be improved to show better information about the GUIDs that do reference a project rather than the GUID itself. I couldn’t agree more with Jeff Atwood’s post title The F5 Key Is Not a Bulid Process where he says:

If your "build process" is the F5 key, you have a problem. ...

Get your build process out of the IDE and into a build script. That’s the first step on the road to build enlightenment.

Amen.

Communicating Continuous Integration Requirements

If you’re chasing the holy grail of CI and working to get automated builds, test execution, static code analysis, build stats etc. make sure the developers working on the project understand the build requirements of your CI setup. In this case, building the main project is about as straight forward as it gets:

%SystemRoot%\Microsoft.NET\Framework\v3.5\msbuild /t:clean mysolution.sln
%SystemRoot%\Microsoft.NET\Framework\v3.5\msbuild /t:build mysolution.sln

Like I said, simple. Now, on the build server itself things aren’t quite this simple but for the bulk of the build if you get by this step you’ve gone a long way as it builds 22 C# projects. The first line above, that cleans the build is crucial. If you skip it and build from the command line after building in VS.NET you’ll continue masking the problem. A successful clean process is just as important as a successful build process. On this particular project there was a major project renaming that introduced a slew of errors but the VS.NET IDE sat idly by and in fact allowed this problem to occur. Eventually a checkin that trigger this set of failures occurred all the while "it builds fine on my machine".

The second problem was solution file name changes, copy/renames to be more specific, which further broke the CI build. The third problem was that the old .sln and csproj files were not deleted from the repository which led to even more confusion but I digress, that’s life in the real world, we fix it and move on.

Comments [0] # permalink Posted @ 9:07AM
# Tuesday, March 04, 2008

CCNET based EDI Invoicing Project Goes into Production

Tagged: Continuous Integration | Development | Open Source

Tags:  |  | 

Over the past six months I’ve been working on a system to automate invoicing using EDI. The system is built on CruiseControl.NET and a collection of custom written source control providers and CCNET tasks. Back in December, I blogged about this project which integrates with a custom ERP system built from scratch by Falafel that processes half a billion dollars worth of transactions a year so it’s nice to finally see my small part come online.

There are several Open Source projects used in the implementation including:

Here’s a diagram of the system with CCNET at the heart:

EDI Invoicing using CruiseControl.NET

One of the many benefits of using CruiseControl.NET is the visibility the system can provide to the people in Accounts Receivable. Not only can they see what’s going on via the CCNET web dashboard but they can even install CCTray and monitor from their desktop as invoices are processed throughout the day. On the web dashboard clicking through to a specific "build" log for a given EDI 810 "build" provides invoice details including invoice number, customer name, date and dollar amount.

EDI CCNET Web Dashboard
As you can see I still need to customize the dashboard to fit into the web-based ERP system’s look and feel.

CCNET’s plug-in architecture made it perfect for this situation as it has numerous features that can be leveraged to create this, perhaps unusual, usage of a Continuous Integration server. I suppose CI could just as easily stand for Continuous Invoicing. :-)

EDI 850 Up Next

My next task will be to re-work on an existing, read old, EDI 850 Purchase Order set of processes and integrate them into CCNET. Anyway, this has been a fun project to work on and I’ve learned a lot along the way and I’d like to give a tip 'O the hat to John Waters, Falafel’s CTO, for all his help. If you’re looking for an ERP guru, I think you’d be hard pressed to top John plus he’s got a great sense of humor. One afternoon while driving back through the farm land of Watsonville from our client’s site, one of the largest organic food companies in the world, he quipped:

Here we go driving through our business objects...

Something only another geek could really appreciate!

Lastly, I have to admit after 15 years of doing product development feels very satisfying to design and implement a system that solves a real world business problem. There’s just no substitute for hands on experience!

Related posts:

[UPDATED: March 4, 2008] Forgot SubSonic which I use for data mapping/xml creation!
Comments [5] # permalink Posted @ 12:50AM
# Wednesday, February 06, 2008

An FTP Source Control Provider for CruiseControl.NET

Tagged: Continuous Integration | Open Source

Tags:  | 
I've been working on a custom CCNET implementation to handle EDI invoicing which is nearly complete and one of the things I needed was FTP download support. In the project I'm working there is a utility that makes use of edtFTPnet, from Enterprise Distributed Technologies, an Open Source C# FTP library licensed under LGPL. It seems to work reasonably well though the authors are admittedly "love the 'internal'" keyword thus making extensibility difficult, if not impossible, short of forking the codebase. Anyway, I've been using the library and it seems to work just fine. Btw, I've submitted my suggested changes so hopefully they'll recognize the benefits and incorporate them.

Writing Plugins for CruiseControl.NET

Anyway, what I needed as a mechanism that would poll an FTP server looking for new files to be downloaded which sounded like a reasonable thing for CruiseControl.NET. Of course, once files are downloaded additional processing is done so it fits with CI quite nicely. Fortunately, CruiseControl.NET has a nice plugin architecture that makes this sort of thing easy.

Creating a Plugin Assembly

The first step is to create a new Class Library following the naming convention ccnet.*.plugin.dll and add the following assembly references:

  • edtFtpnet
  • Thoughtworks.CruiseControl.Core.dll
  • NetReflector.dll

And add the following to your using statements:

  • System.Collections
  • EnterpriseDT.Net.Ftp
  • Exortech.NetReflector
  • ThoughtWorks.CruiseControl.Core
  • ThoughtWorks.CruiseControl.Core.Util

Your assembly should appear as follows (click the image for a larger version):

CCNET plugin assembly

Adding ISourceControl

The next step is to add the ISourceControl interface and provide the necessary attribute which will declare the name used to identify your Source Control Provider.

[ReflectorType("ftpscc")]
public class FTP: ISourceControl

The ReflectorType attribute comes from the NetReflector assembly and is located in the Exortech.NetReflector namespace. The value provided to the attribute is the name which will be used in ccnet.config as follows:

<sourcecontrol name="ftpscc"></sourcecontrol>

Adding FTP Specific Properties

Next, add properties for the FTP connection and once again we'll use attributes from Exortech.NetReflector making it easy to declare settings from ccnet.config:

[ReflectorProperty("deleteRemoteFiles", Required = false)]
public bool DeleteRemoteFiles = true;

[ReflectorProperty(
"fileMask")]
public string FileMask;

[ReflectorProperty(
"localPath")]
public string LocalPath;

[ReflectorProperty(
"password")]
public string Password;

[ReflectorProperty(
"remoteHost")]
public string RemoteHost;

[ReflectorProperty(
"timeOut", Required = false)]
public int TimeOut = 60;

[ReflectorProperty(
"userName")]
public string UserName;

[ReflectorProperty(
"remoteDir", Required = false)]
public string RemoteDir = "/";

These properties indicate settings for the FTP connection as well as the location where the files should be placed after being downloaded. Additionally, there is a flag which indicates if the downloaded file should be deleted from the server.

Again, simply adding the ReflectorProperty attribute will cause these members to be populated with the data from your <sourcecontrol name="ftpscc"> tag in ccnet.config.

Implementing ISourceControl

Next, implement the ISourceControl interface on your class. Of course, the easiest way (in VS.NET) to stub out the necessary methods is via the context menu with the caret sitting on ISourceControl:

VS.NET Implement Interface

This will give you five new methods though I'll focus on the following two since that's all you really need to implement here:

public Modification[] GetModifications(IIntegrationResult from, IIntegrationResult to)

public void GetSource(IIntegrationResult result)

In GetModifications we need to return an array which contains information about the files to be downloaded and in GetSource actually fetch the files. One caveat here is we use DateTime.Now as the modified time so that CCNET will consider the file as a "new" change. So, GetModifications will look like this:

1 public Modification[] GetModifications(IIntegrationResult from, IIntegrationResult to)
2 {
3 ArrayList mods = new ArrayList();
4 Connect();
5 try 6 {
7 Log.Debug("Fetching list of files that match: " + FileMask);
8 FTPFile[] mModifiedFiles = ftp.DirDetails(FileMask);
9 Modification mod;
10 int i = 0;
11 foreach (FTPFile f in mModifiedFiles)
12 {
13 mod = new Modification();
14 mod.Type = "Added";
15 mod.FileName = f.Name;
16 mod.ChangeNumber = i;
17 mod.ModifiedTime = DateTime.Today;
18 mods.Add(mod);
19 i++;
20 }
21 return (Modification[])mods.ToArray(typeof(Modification));
22 }
23 finally 24 {
25 // Close the connection if there are no files to process 26 if (ftp.IsConnected && mods.Count == 0)
27 ftp.Quit();
28 }
29 }
30

GetSource handles downloading files from the server and then deleting them:

1 public void GetSource(IIntegrationResult result)
2 {
3 try 4 {
5 foreach (Modification m in result.Modifications)
6 {
7 Log.Debug("Fetching file: " + m.FileName);
8 ftp.Get(LocalPath, m.FileName);
9 if (DeleteRemoteFiles)
10 {
11 Log.Debug("Deleting remote file: " + m.FileName);
12 ftp.Delete(m.FileName);
13 }
14 result.AddTaskResult(string.Format(
15 "<fileDownloaded>{0}</fileDownloaded>\n", m.FileName));
16 }
17 }
18 finally 19 {
20 ftp.Quit();
21 }
22 }

Connecting to the FTP Server

The call to Connect in GetModifications above looks like this:

1 public void Connect()
2 {
3 Log.Debug(String.Format("Connecting to remotehost: {0}...", RemoteHost));
4 ftp.Connect();
5 Log.Debug(String.Format("Connected, logging in with user: {0}", UserName));
6 ftp.Login(UserName, Password);
7 Log.Debug(String.Format("Switching to remote directory: {0}", RemoteDir));
8 ftp.ChDir(RemoteDir);
9 }

The only thing I haven't really covered here is the construction of the FTPClient member and the assignment of the its RemoteHost and Timeout properties which I think is pretty straightforward.

Conclusion

Writing plugins, whether for Source Control Providers or tasks (ITask), the process is pretty straightforward though there is little documentation on the subject. Be prepared to spend some time studying the other ISourceControl and ITask implementations included with CCNET and quite a bit of time debugging CCNET itself to understand how it works. One issue I've run into and that I've blogged about before which is when exceptions occur from within a Source Control Provider. In the current released version of CCNET (1.3) exceptions are not handled by the publishers for the project therefore you'll never get notified about connection problems etc. unless you use the log4net settings I mentioned.

The above implementation is lacking in a number of areas, like error checking, and is a very simplified example of code I'm using in production. I've also tweaked CCNET itself so that any exceptions raised within my FTP provider are handled by the build publisher so that I can get email when things fail. In fact, I've been working to get these, or similar, changes incorporated into CCNET.

[UPDATE: Feb 6, 2008] Fix image links
Comments [0] # permalink Posted @ 12:20AM