Jun 30, 2007

Tips for Faster Pages

As a followup on my previous post about making your browser faster, the vast majority of visitors to your web site will be using browsers loaded with the default settings. How can they get a better experience when visiting your site?

Thankfully Steve Souders & Tenni Thourer gave a presentation on this subject a while back and now Rich Skrenta has knocked up a list of the key points with URLs to the relevant resources and backup materials. It's a really good reference list and I'm sure there's something in there for everyone to either learn for the first time or freshen up on.

Speed up your Browser

One of the speed limitations with browsers is the number of simultaneous connections they make to the web server. On modem connections this limitation makes sense, but on broadband connections why do you need the limit so low.

The good news is there are ways to increase this limit and let your browser request more items at once.

For IE you can download IE Toys from Bayden Systems.
For Firefox you can download Fasterfox from the Mozilla site.

Your browser doesn't actually run any faster but because the contents of the page arrive sooner the data loads quicker and you get a better overall experience. Nice :-)

P.S. IE Toys has a number of other features you may find useful such as right click options to do dictionary look ups of words, or kick off Google searches from the selected text, etc. It's worth a look.

Jun 28, 2007

Sandcastle now on MSDN

The Sandcastle project team is obviously making good progress. They recently released their latest CTP and now they've been given an official home on the Microsoft MSDN site.

Keep up the good work Anand & crew.

World Wide Wii-velopers Wii-joice!

Now this is a tempting distraction... The Wii is being opened up to the mass market of independent developers who want to write mini-games, tinker or just have fun.

It's called WiiWare and there's a press statement from Nintendo about it.

I'll be really curious to see how this pans out. Oh, and apparently worldwide sales of the Wii have hit 8.5 million - only 1.5 million behind the XBox 360 - it's a huge installed base considering the platforms only been on sale six months.

4 Invaluable Scrum Tools

I was recently asked what tools you need for Scrum. It's a common enough question and in the software industry we have a tendency to look for the latest and greatest tools to help us do our jobs better.

In the Agile movement it's particularly rife, after all one of the tenets of agile is the removal of impediments from your team and automation is one of the main tools in achieving this.

Scrum, though, is a little different. Scrum is not software engineering, it's a project management methodology. Automation and tools are definitely needed to assist the engineering practices of your team; to help them concentrate more on the problem they're trying to solve and less on the repetitive elements of building code.

When it comes to process however there is a nasty side effect in using software tools to assist & automate the management effort. Tools bring rigidity to the process and impede the "inspect & adapt" thinking required in an agile project. Why, because all software has rules, and regardless of how flexible they may be, people's thinking will subconsciously align to the rules present in the software and assume that they can't be changed.

A further issue with software tools for management is that software isn't visible. It's not easily accessed by everyone, and it requires directed thinking to open up the software and check on the status of the project. What's needed is a set of tools that are light touch and extremely visible to all team members. People are visual beings, and highly visible status information is something that people will not ignore.

As a result project management, and Scrum in particular is best served not by software but by a simple set of tools.

So here are the 4 invaluable tools you need for Scrum:

1. The Wall - it's where you'll stick your sticky notes and easel pad sheets. Make sure the wall is a big visible wall. No wall space? Then use a window - just make sure the curtains/blinds are open!

2. Sticky Notes - For recording the tasks in the sprint/iteration. Also for recording the product backlog items. Use multiple colours for visually differentiating task types or backlog items (e.g. functional vs non-functional, business layer vs UI, etc).

3. Paper for Charts - Keep your burndown charts visible and easy to see. The bigger the chart is (in size) the easier it is to see and the harder it is to ignore.

4. A Marker Pen - For writing on your sticky notes and drawing your charts! A marker is much more visible than ball point pen from a distance. Feel free to use different colours.

That's it. It might seem incredibly lo-tech yet it's very easy to use and it's all you actually need.

If you need to record the progress of your sprints and backlogs for posterity or to email updates to remote stakeholders then you might need a few more tools in the kit:

  • A digital camera - just take photos of your wall and save/email the images.
  • Excel - Record your sprint backlog in an excel workbook and update it daily. I have one you can download if you want to use it as a starting point.

Finally, if you are new to Scrum you may feel an overwhelming urge to look for a specialized scrum management tool to help guide and manage the process while you comfortable with it. I would strongly advise against doing so. Run the process manually and don't use any software tools until you have bedded down Scrum within your team (at least 18 months). And even then, I would suggest that the less management software you have, the more flexible and adaptable you and your team will be when the winds of change come blowing through.

Jun 22, 2007

Publish your CC.NET Builds with Twitter

OK, so you probably don't want to do this for internal projects (CCTray works great for that), but if you've got an open source project and you're using CruiseControl.NET then it might be an idea to let your community know about it.

You can't have them all downloading CCTray and pinging away at your server all day, but you can let them listen to a Twitter feed of your build notifications. If that's something interesting for you, then have a look at the Twitter Publisher for CC.NET.

At long last something useful to do with Twitter.

More Sandcastle News

Sandcastle will be getting a refresh of the June CTP shortly and will change the name of VSORCAS layout to avoid confusion between the documentation format in VS2008 and the new Sandcastle documentation layout style.

Many thanks to the Sandcastle team for listening to feedback and making changes so quickly. Especially since I didn't actually contact them directly, and just made a comment in my blog about it.

The team also blogged about some of the community tools for Sandcastle, including a link to SFHB - a GUI front end to build documentation with Sandcastle, similar to what NDoc had.

There's also links to another GUI front end and a tool for MSBuild/VS2005 integration.

Very good stuff.

Jun 21, 2007

Paper Based Books with Hyperlinks

I don't quite know what to make of this. There's a prototype of a product called The Bluebook which is basically a normal paper based book printed with conductive ink.

In the back of it (or embedded in the jacket/spine) is a bluetooth chip. When you put your finger on an outlined section of text, the book sends a message to a nearby PC to do something, i.e. open a web page, play a music file or video, delete the contents of you hard disk, etc.
Have a look at the web site for some pictures of it and you'll get the idea.

There's a range of kids books that use a similar concept (the Leap Pad series) that works of spatial recognition and plug in cartridges instead of bluetooth/pc links. They work fairly well because the system is self contained and it's portable.

I don't know about the Bluebook though - reading a book within 10 feet of a PC is not my idea of fun. I think electronic ink might be a better basis for this sort of thing - just because the Sony Reader hasn't been successful doesn't mean the idea is wrong (anyone remember the state of mp3 players before the iPod?).

Jun 20, 2007

Outsourced Agile

Andrew has posted about a guy named Alex in Romania having a big rant about using agile in an outsourcing arrangement.

His argument is founded on everything that can go wrong with an agile project if you miss the fundamentals of agile and forget to "inspect & adapt" your process to fit the situation.  And while he doesn't outright say it, from what I can tell he's tried to use XP (extreme programming) for his engagements.  That in itself is a tough ask - XP is a very demanding methodology to follow (why do you think it's called extreme) and is really intended for internal short term projects, not outsourced arrangements where slightly more rigorous processes are more appropriate.

And in case you're wondering I've run agile projects for large external customers before, so I know a thing or two.

Let's take a look at some of his arguments:

1. The strong points of agile development don't turn out, in practice, to be that strong.  Less paperwork isn't a good thing.

Who said an agile project shouldn't have documentation?  Who said you can't document anything? And, um, are you nuts!! You can document requirements as you go - use cases, user stories, acceptance criteria, signed off UI mock ups, all sorts of things.  Documentation in an agile project should be "just enough documentation" not "no documentation".

2. Stress and Unproductivity

Apparently it's stressful and unproductive to communicate with your customers and tell them what you're doing.  I think someone needs to think seriously about what business they're in.  Outsourced development is a service, and implies that customer communication is important.  Agile methodologies stress the importance of communication.  I don't see how this is a negative.  If anything it's a positive as proper communication will result in improved outcomes for the client.

3. The First Meet-Up.  (You can't estimating the size of the job)

Apparently in an agile project you're not allowed to estimate.  What a crock!  I'd like to point you to Mike Cohn's book on Agile Estimating and Planning.  Agile says "don't lock down your estimates in stone", not "don't estimate".  Every project needs estimates, plans and priorities.  So make an estimate, and revisit that estimate as more information becomes available.

And how does the ability to do an initial up front estimate get impacted by the methodology you use to deliver it?  If a customer wants to know how long the piece of string is, it won't make any difference if you use waterfall or agile, it's still an estimate.  Waterfall just deludes the customer into thinking that the estimate is accurate, all the way up to the missed delivery date!

4. What you deliver has changed from what was asked for and now they're upset

The project draws close to conclusion and the client begins to realise that the delivered solution will be different to what was asked for at the beginning and that this is a surprise to them, and they'll make you redo large parts of it.  HELLO!!  If there's been any communication going on then the variations that have been made will be based on what the customer asked for and put in a prioritised list.  Oh, hang on, he didn't do any documentation and he doesn't like talking to the customer.  Hmm, no wonder things went pear shaped.

The statement that the "In the classic approach, it is impossible for both you and the client to be anything but happy" is just downright ludicrous!  A delivered to spec project does not equal customer happiness.  And while a spec may be "frozen" it's a guarantee that requirements will change.

5. Daily progress and showing the customer each day what has happened is bad.

I agree, so why do it?  Why not show the customer at the end of an iteration.  Every 2 - 4 weeks, and show how you've delivered on commitments (and possibly gotten ahead a little).  The customer doesn't need (or want) to sit shoulder to shoulder with your team 8x5, but they do want to know what's going on.

6 Daily change requests. (when the client changes their mind every day).

Hmmm.  And here I was thinking that in an agile project an iteration had a fixed scope, where commitments are made and delivered.  I think someone missed the point about planing in an agile project.  Change priorities, change scope, change as much as you like, but none of it takes effect until the next iteration.

And the statement that "With the classic approach, change requests like this are impossible, since the specifications are frozen." goes against point 4 above - does anyone really think that a customer with a plethora of unactioned change requests will still be happy when you deliver to the original spec?

7. Clients know programming and will tell you how to do your job.

Is he serious?! You're responsible for your work, the customer is responsible for the specs.  If you've got a customer who thinks they can manage your staff for you, then it won't matter how you run your project, it will fail.

8. Clients who want to run the show.

Why is a client getting involved in the programming?  I just can't fathom it.  The involvement of the customer in an agile project is for the clarification of requirements, business rules, etc.  NOT for assistance with the code.  Honestly, what is going on here?

 

All Alex's rant does is highlight the mistakes he's made.  It's another great article on what NOT to do when adopting agile practices, especially in a client/vendor arrangement.

So Far, So Good

I've now been at Readify for about 3 weeks now and I've got to say things are going pretty well.

I've just completed my second engagement; a review of a "Classic" ASP application and I'm starting to settle into the senior consultant role fairly comfortably.  It's still weird not being able to have water-cooler chats or random banter with colleagues during the day and I'm definitely missing some of those social elements that exist in an office, but at the same time working in a virtual company has it's advantages.  When I'm not on site, I'm able to save about 2.5 hours a day in travel, I can drop off and pick up my kids from school, I can duck out to the gym when I feel like it (instead of at 6 in the morning) and I can be flexible in the times I work and where that work happens.

I'm finding that a work from home day usually starts a bit earlier and finishes a bit later than a normal day used to (ie I start about 7:30am and finish about 9:00pm) but that's because of the gaps I make in it.  I still do a normal days work, it's just a lot more broken up then when I'm in an office.

One thing I'm not missing is the stress of management.  There were some things going on in the company I left that were quite draining and being able to leave all that behind and just concentrate on doing a top job for my clients is so much easier to deal with.

And what of the people I'm working with?  Well, they're the cream of the crop.  There's a large number of Microsoft MVPs in the company and some other very smart people on board.  The work these guys have done is fantastic, and I'm lucky to now be a part of it all.

Jun 19, 2007

TortoiseSVN & TFS

This is pretty cool. Garrett talks about a project that lets TortoiseSVN talk to Team Foundation Server.
At it's core SVNBridge converts the Subversion API calls made by the SVN client(s) into TFS API calls, effectively making TFS look like a subversion repository. Nice :-)

The VSORCAS Documentation Format

It appears I was slightly mistaken about what the new VSORCAS documentation format in Sandcastle actually is. It's actually a new "front end" to the documentation and for now is restricted just to documentation produced by Sandcastle itself (see the comment in my previous blog post).

Anyway a new Sandcastle CTP has just been released and the new "VSORCAS" (bad name) output format is included.

Here's what it looks like (click for larger image):



The use of tabs to only show specific syntaxes is quite nice and saves a fair bit of real estate. And the in line filtering of contents based on properties (private, public, etc) is useful when you have a large class you are looking at

Here's a filtered view of the same class



Sandcastle also handles Generics quite nicely now as well, as can been seen in this method's documentation.



While the documentation shown so far is HTML output, you also get the HxS output as well as CHM output, all of which exhibit the same behaviour. Here's a shot of the CHM output.



Sandcastle is progressing nicely now, and appears to be a viable replacement to NDoc. It still needs more work (especially a GUI front end) but it's making good progress.

Oh, I should also mention that there is another new format called "prototype" that acts much the same as the vsorcas format, but has a cleaner look and feel as shown here:

Jun 18, 2007

MSDN Documentation Format Set for Change

Looks like the upcoming Orcas release of visual studio will have a new documentation format.  Why?  Well let me quote: "This new documentation presentation layer targets developer audience who need to find information quickly and easily in our documentation that grows significantly with every Visual Studio/.NET Framework release."

If you've ever spent more than 3.75 secs looking for information in the 2005 MSDN Library help system looking for anything more esoteric than the syntax for an if statement can be frustrating.

What's even better is that this search "if statement c#" doesn't even return a relevant result the if-else statement in the top 5 results - it's the 8th on the list!!  Below the top 3 ranked results of "lock statement (C#)", "using statement (C#)" and "fixed Statement (C#)".  Any improvement will be of value after that little effort!

My concern though, is that the changes listed for the MSDN documentation don't seem to address the real problem.  The changes are about disk space, screen scrolling and views, and filtering and restricting search contexts.  They don't seem to have realised that filtering doesn't improve relevancy, it just hides the problem.  I'll wait and see how the beta's look, but at this stage I'm not that hopeful of any greatly improved usefulness - google will probably stay as the main source of MSDN docs for now.

By the way the first 5 results for "if statement c#" on Google are all about the if statement - including the MSDN documentation entry for the if-else statement.

Jun 15, 2007

Fiddler 2

I can't believe this is the first time I've run across it but Fiddler 2 is available. And it's been out since September last year!

Fiddler for those not in the know Fiddler is a debugging proxy you can use for examining what is going on when you hit a web page. It's very cool stuff (especially with all the new bits in it) but I can't help wondering what genius thought releasing version 2 under a completely different url was a good idea, and then not linking to it from the main Version 1.x site!

Jun 8, 2007

ReSharper Goodness

If you don't use ReSharper you're developing with only one arm. If you know about it but don't know if it's worth learning then have a look at 31 Days of ReSharper.

Even better, if watch it being used in anger and download the (33Mb) video of Ayende Rahien demoing Rhino Mocks in a TDD scenario. He uses ReSharper to knock out the code and it’s very cool to see just how quick someone can punch out code if they know how to use their tools properly. (the coding starts after about the 11-12 minute mark). Enjoy.

What's Filling My Disk?

When I had a disk space problem recently I didn't know and the thought of hunting down big files hidden in deep folder structures was anathema.

So I turned to my trusty browser, whipped out my Google branded shovel and got digging. I quickly found a great little utility - WinDirStat. Weird name, great app.

In a nutshell it looks at your folder structure and files and visually tells you what's chewing up the space. Here's what it looks like:


The bigger the square, the bigger the file. Colours show file types, and folders are grouped into rectangles to show relative size. As you can see my Windows folder eats up most of my disk, and within that are a number of very large files.

What's even better is that when it's working out what's where you get funky little animated pacman characters running around on your screen. True nerdy bliss :-)

Jun 6, 2007

Why Windows Needed A Transactional File System

If I could think of only one reason why Windows needed a transactional file system it's this: Windows Update and the software installation process.

I just picked up a temporary laptop as part of my new job. It came with an unpatched XP SP2 installation and Office 2007. The C: drive was kinda full, but there was plenty of space on the second disk.

So off I went and installed VS.NET 2003, VS2005, MSDN docs, SQL2005, etc on the D: drive, made sure they were all working and then went to Windows Update to get all the latest patches and go through the pain of umpteen reboots during the process.

Well Windows Update decided that there were 97 (yes, 97!) critical updates it needed to install so I clicked on the "go" button and walked away.

Fast forward half an hour and I come back for a look and see that Windows is complaining that I'm running out of disk space. I hurriedly remove the C:\Windows\$NTUndelete folders to free up some room and get through to the end of the process at which point Windows Update tells me that while it installed almost everything properly, it couldn't install .NET 1.1 SP1 or VS2005 SP1, and could I please reboot if I don't mind, thank you very much.

What's a guy to do? I reboot. But on start up I get all a whole lot of 0xc0000034 errors in programs that (as it turns out) rely on the .NET 2.0 framework. Why? Well, probably because Windows Update poo-ed itself and ran out of disk space in one of the updates.

Looking for information about the error and Google was surprisingly unhelpful, except for one small throwaway forum reply about corrupted .NET frameworks. "Hmmm" says I and so I tried running VS2005 and I got this lovely little gem Error. Error occurred while initializing fusion. Setup could not load fusion with LoadLibraryShim().

Well fortunately Google was a bit more helpful this time and turned up this post. The answer is not the one marked but a bit further down Filipe Janela mentions trashing the policies directory to get things working. Dangerous I know but when you're desperate... I did it and things looked OK for a while until VS2005 then complained that This application has failed to start because the application configuration is incorrect. I'll add that to the list of not-exactly-useful error messages I've seen.

So what next? I could try the ol' uninstall/reinstall trick right? Which I did, however the uninstall failed for some other equally bizarre error. The weird thing is that trying to uninstall again didn't even get started because some files were missing.

More googling turned up a post about Side-by-Side issues (interesting read!). So, thinking the .NET frameworks might be conflicting given the busted Windows Update, I tried removing all the .NET frameworks and reinstalling them. But that didn't work.

Finally, In last-attempt try-anything mode I stuck the VS2005 disk back in and ran setup. Even though Windows and control panel still thought VS2005 was installed, the setup program didn't. It let me run through setup as a fresh install and eventually I got VS2005 back and working again.

AND IT ONLY TOOK 6 HOURS TO RESOLVE!!!

Thanks Microsoft for your well written, handle all errors, Windows Update process!

If ever there was a reason for a transactional file system then this is it. Aborted or failed installations due to lack of disk space, file locks, or other errors. A clean roll back of all changes is required so you don't leave a machine in an unknown and possibly nonworking state. I was thiiiis close to trashing my whole machine and starting again.

Thankfully Vista has transactional file system support now and I can't help but wonder if Windows Update was the reason for it.

SQL 2008 CTP coming very soon

According to David Hayden the good folks at Microsoft have released a CTP for SQL 2008 (Katmai).

The SQL 2008 Downloads page currently has just the Books Online and some documents, but according to this forum post it'll be there shortly.

Now, how much of my download limit have I got left this month?

Jun 4, 2007

All Good Things Come to an End

Tonight I wrapped up my job with MXL, and got a very nice sendoff (though they did make sure I left the building!)

It's a weird feeling - for the past 2 years I've been investing a lot of time, energy and emotion into my staff and now I'm leaving all that behind and hoping it continues on without me. Whilst I'm excited to be starting something new at the same time I'm kind of depressed about leaving my team and the fact that I won't get to see them every (working) day anymore.

In some respects it feels a bit like leaving high school. Ah well, times change and people move on. Here's wishing everyone the best with their futures.

Tomorrow is a new day and tomorrow sees me start at Readify. That's something to look forward to!

Jun 1, 2007

A Real World CruiseControl.NET Setup

CruiseControl.NET is a fantastic open source tool for running Continuous Integration processes in your development team.

I've been using CCNet with teams for many years now and it works beautifully. Of course these days you can also do continuous integration with a number of other products like Microsoft's Team Foundation Server or any of the various other offerings out there, but CCNet is the still going strong with version 1.2 recently released.

The details below are for the configuration I've used on my most recent project (names changed for privacy). It's a line of business web application consisting of around 25 projects and a few hundred web forms.

The Environment

The development environment has a 4 CPU HP server running VMWare ESX and has a number of test servers and so forth setup on it. The build server itself is one of the virtual servers. It is configured for 2 CPU's, 1 GB Ram and 20 GB of disk.

For simplicity, this server is also where the Subversion source repository is located.

The disk space is split into 3 drives. The system disk has 8 GB on it, there is a 4 GB disk for build log files, and the rest is allocated to the build server and the subversion repository.

Developers have the TortoiseSVN client on their machines for Subversion access and the CCTray tool installed so they can see the current state of the build server (and know when someone breaks the build).

Gemini is used as the issue tracking system and we have integrated Subversion commits into the Gemini solution.

Server Software

The build server has the following software installed:

You'll notice that Visual Studio is NOT installed on the build server. Also, the software being built uses 3rd party components from Telerik, Dundas and others. These 3rd party products are also NOT installed on the build server.

Finally, we use MSBuild in the process. You'll need the MSBuild Logger in order to get MSBuild output readable in the Web Dashboard.

The Build Process

The build process is pretty simple. Here's how it works:

  • Developer commits code into Subversion
  • CruiseControl.NET detects that a commit has occurred
  • The buils direcortory is cleaned
  • CCNet checks out the code into the local working directory on the build server
  • 3rd party DLL's are copied into the bin directory for the web site
  • .config.template files are copied to .config files
  • Assembly numbers are updated to match the Subversion revision number
  • MSBuild is called to compile the solution
  • Unit tests are run (with NCover for code coverage)
  • NCoverExplorer is run over the NCover output
  • FxCop is run. This step can fail, as we expect some FxCop validation errors on legacy code
  • SourceMonitor is run

If any step (except FxCop) fails, we stop immediately.

If it all works, we then create a folder containing the application in a ready-to-release state as follows:

  • Clean out the existing contents in the release folder
  • Copy everything from PrecompiledWeb
  • Create default blank folders (e.g. upload folders, etc that are part of the applications standard configuration)
  • Copy in extra non-build items, including things such as deployment untilities and help files

And that's it.

One interesting thing to note is what we do with .config files. In VS2005 standard web applications require a web.config file to help figure out where references are, and so forth. Unfortunately in a development environment developers often use different config files to those of other developers and those on the build server (eg connection strings, logging and debug settings, etc). We use the concept of a baseline web.config file called web.config.template. This .config.template file is what is used by the build server to set the defaults for the application both in terms of compilation and standard deployment/runtime configuration options.

The Web Dashboard

The web dashboard is the web interface to the ccnet build server. We have configured this to show more information about the build process than that provided in the base CruiseControl.NET installation.

Our dashboard looks like this:

The build log now included the Subversion revision number and provides people with the ability to view the NCover output using NCoverExplorer, the MSBuild output (for finding why builds fail - as you can see, sometimes they do!) and SourceMonitor output.

How Is It Configured?

All the configuration information here assumes you've already set up CruiseControl.NET and understand the basics of how to configure it.

Let's start with the web dashboard. We've changed the <buildPlugins> section in dashboard.config to look like this:

P.S. Apologies for the formatting - blogger got all "helpful" and removed the leading whitespace


<buildPlugins>
<buildReportBuildPlugin>
<xslFileNames>
<xslFile>xsl\header_with_revision.xsl</xslFile>
<xslFile>xsl\modifications.xsl</xslFile>
<xslFile>xsl\compile.xsl</xslFile>
<xslFile>xsl\unittests.xsl</xslFile>
<xslFile>xsl\MsTestSummary.xsl</xslFile>
<xslFile>xsl\NCoverExplorerSummary.xsl</xslFile>
<xslFile>xsl\fxcop_new_summary.xsl</xslFile>
<xslFile>xsl\sourcemonitor-summary.xsl</xslFile>
<xslFile>xsl\compile-msbuild.xsl</xslFile>
</xslFileNames>
</buildReportBuildPlugin>
<buildLogBuildPlugin />
<xslReportBuildPlugin description="NUnit Details" actionName="NUnitDetailsBuildReport" xslFileName="xsl\tests.xsl" />
<xslReportBuildPlugin description="NUnit Timings" actionName="NUnitTimingsBuildReport" xslFileName="xsl\timing.xsl" />
<xslReportBuildPlugin description="NCover Report" actionName="NCoverBuildReport" xslFileName="xsl\NCoverExplorer.xsl" />
<xslReportBuildPlugin description="NCover Details" actionName="NCoverDetailsReport" xslFileName="xsl\NCover-details.xsl" />
<xslReportBuildPlugin description="FxCop Report" actionName="FxCopBuildReport" xslFileName="xsl\FxCop_new.xsl" />
<xslReportBuildPlugin description="MSBuild Report" actionName="MSBuildOutputBuildPlugin" xslFileName="xsl\msbuild.xsl"/>
<xslReportBuildPlugin description="NAnt Output" actionName="NAntOutputBuildReport" xslFileName="xsl\Nant.xsl" />
<xslReportBuildPlugin description="NAnt Timings" actionName="NAntTimingsBuildReport" xslFileName="xsl\NantTiming.xsl" />
<xslReportBuildPlugin description="SourceMonitor Top 30" actionName="SourcemMonitorTop15BuildReport" xslFileName="xsl\SourceMonitor-Top15.xsl"/>
<xslReportBuildPlugin description="SourceMonitor Files" actionName="SourcemMonitorFileBuildReport" xslFileName="xsl\sourcemonitor-group-by-file.xsl"/>
<xslReportBuildPlugin description="SourceMonitor Metrics" actionName="SourcemMonitorMetricBuildReport" xslFileName="xsl\sourcemonitor-group-by-metric.xsl"/>

There is a new .xsl file to add the subversion revision to the build output. See my previous post for more information.

We also add a few new build plugins to allow us to view the XML output from programs like SourceMonitor & NCover in the web dashboard.

The Project's CCNet Configuration

When CCNet is set up you need to edit ccnet.config to tell it about the projects you are building.

The configuration for our project is as follows

 <project name="xxxx">
<workingDirectory>D:\CCProjects\xxxx</workingDirectory>
<artifactDirectory>E:\CCArtifacts\xxxx</artifactDirectory>
<webURL>http://ccnet.server.local/ccnet</webURL>
<modificationDelaySeconds>0</modificationDelaySeconds>
<publishExceptions>true</publishExceptions>
<triggers>
<intervalTrigger seconds="60" />
</triggers>
<sourcecontrol type="svn">
<trunkUrl>"file:///D:/svnrepos/xxxx/trunk"</trunkUrl>
<workingDirectory>D:\CCProjects\xxxx</workingDirectory>
<executable>C:\Program Files\Subversion\bin\svn.exe</executable>
</sourcecontrol>
<tasks>
<nant>
<executable>C:\program files\nant-0.85-rc4\bin\nant.exe</executable>
<baseDirectory>D:\CCProjects\xxxx</baseDirectory>
<nologo>true</nologo>
<buildFile>default.build</buildFile>
<logger>NAnt.Core.XmlLogger</logger>
<targetList>
<target>compile</target>
</targetList>
<buildTimeoutSeconds>6000</buildTimeoutSeconds>
</nant>
</tasks>
<publishers>
<merge>
<files>
<file>D:\CCProjects\xxxx\_msbuild.xml</file>
<file>D:\CCProjects\xxxx\_nunit_*.xml</file>
<file>D:\CCProjects\xxxx\_ncover_*.xml</file>
<file>D:\CCProjects\xxxx\_coverageReport.xml</file>
<file>D:\CCProjects\xxxx\_fxcop_*.xml</file>
<file>D:\CCProjects\xxxx\_sm_*.xml</file>
</files>
</merge>
<xmllogger />
</publishers>
</project>

You can see that the sourcecontrol section is set up for Subversion (type="svn") and that we use an NAnt task to kick off the build when a commit is detected in Subversion.

The <publishers> section is where all the xml output files are processed and merged to produce the information displayed on the web dashboard. As a naming convention all XML build logs are named as _file.xml. This makes it easier to clean up things when the next build starts.

The actual NAnt task that is executed when a change is detected is shown here

<?xml version="1.0"?>
<project name="FullSolution" default="compile">
<property name="contrib" value="C:\program files\nantcontrib-0.85-rc4" overwrite="true" />
<property name="sourceDir" value="D:\CCProjects\xxxx" overwrite="true" />
<property name="svnUri" value="file:///D:/svnrepos/xxxx/trunk" overwrite="true" />
<property name="winDir" value="c:\windows" overwrite="true" />
<property name="nunit-location" value="c:\Program Files\NUnit-Net-2.0 2.2.8\bin" overwrite="true" />
<property name="nunit-console" value="${nunit-location}\nunit-console.exe" overwrite="true" />
<property name="fxcop-console" value="c:\Program Files\Microsoft FxCop 1.35\FxCopCmd.exe" overwrite="true" />
<property name="ncover-console" value="c:\Program Files\ncover\ncover.console.exe" overwrite="true" />
<property name="ncoverexplorer-console" value="C:\Program Files\NCover\NCoverExplorer\ncoverexplorer.console.exe" overwrite="true" />
<target name="*">
<nant target="${target::get-current-target()}" buildfile="xxxx/default.build" />
<nant target="releaseIt" buildfile="makeRelease.build" />
</target>
</project>

First we just set up a number of properties pointing to the location of the various tools we'll use when the build actually executes.

Next we call two NAnt targets, one that does to build, and one that does the release. If the build step fails then the release step is skipped.

The NAnt Build Script

Now, finally we get to the fun part. The NAnt build script.

There's a fair bit of detail in the build, but the targets are as follows:

SVN: Gets the source from Subversion and the revision number

Compile: Calls clean and SVN targets first, then set's the Assembly version numbers, checks codebehind vs codefile issues and executes MSBuild before running post build targets

Clean: Removes previous compilation output and xml log files

GetAssemblyVersion: Reads the assembly version number from AssemblyInfo.cs and stores is in NAnt properties

SetAssemblyVersion: Edits the AssemblyInfo.cs files for various projects using the Subversion revision number

CodeBehindToFile: Switches any "CodeBehind" ASP directives to "CodeFile" directives. Developers typically use the Web Application project in VS2005 so that local compiles are quick. The build server uses the Web Site project so that we get full compilation up front and avoid any nasty run time surprises. Unfortunately web applications use CodeBehind in the aspx pages, while the web site uses the CodeFile directive. This ensures that developers can use either without worrying about screwing up the build server.

unitTests: It runs the unit tests! But you'll note it does it via NCover so that we can get coverage analysis performed.

nCoverExplorer: It processes the NCover XML output using the console application and creates summarised XML output that makes it a lot easier to view results in the Web Dashboard.

FxCop: Pretty simple - just runs FxCop over all the .DLL's it can find. We skip 3rd party .DLL's as we can't really do anything about them if they have FxCop violations.

SourceMonitor: Runs the sourceMonitor console app to process the code and calculate cyclomatic complexity values and finds the nastiest, ugliest code it can so that we have a hot-list of methods that need refactoring.

Here's the NAnt build script in full (well, slightly edited for privacy):

<?xml version="1.0" ?>
<project name="xxxx" default="compile" basedir=".">
<description>Build the xxxx Application</description>
<property name="clean.pattern.bin" value="**/bin/**/*" />
<property name="clean.pattern.obj" value="**/obj/**/*" />
<property name="clean.pattern.precompiled" value="**/precompiledweb/**/*" />
<property name="clean.pattern.assemblyinfo" value="**/assemblyinfo.cs" />
<property name="clean.pattern.xml" value="_*.xml" />
<property name="build.major" value="1"/>
<property name="build.minor" value="0"/>
<property name="build.build" value="0"/>
<property name="build.revision" value="0"/>
<property name="svn.revision" value="0"/>
<property name="assemblyinfo.location" value="xxxx\App_Code\AssemblyInfo.cs"/>
<property name="counter" value="0"/>
<property name="codemetrics.output.dir" value="c:\temp"/>
<property name="sourcemonitor.executable" value="c:\Program Files\SourceMonitor\SourceMonitor.exe"/>

    <target name="svn" description="Get source code from Subversion">
<loadtasks assembly="${contrib}\bin\NAnt.Contrib.Tasks.dll" />
<echo message="loaded tasks from ${contrib}\bin\NAnt.Contrib.Tasks.dll" />
<svn-update destination="${sourceDir}" uri="${svnUri}"/>
<echo message="Retrieving Subversion revision number"/>
<exec
program="svn.exe"
commandline='log "${project::get-base-directory()}" --xml --limit 1'
output="_revision.xml"
failonerror="false"/>
<xmlpeek
file="_revision.xml"
xpath="/log/logentry/@revision"
property="svn.revision"
failonerror="false"/>
<echo message="Using Subversion revision number: ${svn.revision}"/>
</target>

    <target name="compile" depends="clean,svn">
<copy file="ThirdPartyAssemblies\RadAjax.Net2.DLL" tofile="xxxx/bin/RadAjax.Net2.DLL" overwrite="true"/>
<copy file="xxxx\xxxx.config.template" tofile="xxxx\xxxx.config" overwrite="true"/>
<call target="SetAssemblyVersion"/>
<call target="CodeBehindToFile"/>
<exec
program="${winDir}\microsoft.net\framework\v2.0.50727\msbuild.exe"
commandline='xxxx.sln /p:Configuration=Release /logger:ThoughtWorks.CruiseControl.MsBuild.XmlLogger,..\ThoughtWorks.CruiseControl.MSBuild.dll /noconsolelogger /nologo /noautorsp'
output="_msbuild.xml"
failonerror="true"/>
<call target="unitTests"/>
<call target="ncoverExplorer"/>
<call target="FxCop"/>
<call target="sourcemonitor"/>
</target>
<target name="unitTests">
<foreach item="File" property="filename">
<in>
<items>
<include name="xxxx\bin\*.dll"></include>
<include name="**\bin\*\*tests.dll"></include>
<exclude name="**\Castle.DynamicProxy.dll" />
</items>
</in>
<do>
<exec program="${ncover-console}" workingdir="${path::get-directory-name(filename)}" commandline="&quot;${nunit-console}&quot; ${filename} /xml:${project::get-base-directory()}\_nunit_${path::get-file-name-without-extension(filename)}.xml /nologo //x ${project::get-base-directory()}\_ncover_${path::get-file-name-without-extension(filename)}.xml" failonerror="true"/>
</do>
</foreach>
</target>

    <!-- Run NCover Explorer over results for summary output -->
<target name="ncoverExplorer">
<echo message="Starting NCoverExplorer report generation..."/>
<exec program="${ncoverexplorer-console}"
workingdir="${project::get-base-directory()}" >
<arg value="_ncover_*.xml" />
<arg value="/r:5"/>
<arg value="/x:_CoverageReport.xml" />
<arg value="/e" />
<arg value="/p:xxxx" />
<arg value="/m:85" />
</exec>
</target>

    <target name="clean" description="remove generated files">
<delete>
<fileset>
<include name="${clean.pattern.obj}"/>
<include name="${clean.pattern.bin}"/>
<include name="${clean.pattern.precompiled}"/>
<include name="${clean.pattern.assemblyinfo}"/>
<include name="${clean.pattern.xml}"/>
</fileset>
</delete>
</target>
<!-- Populates variables (build.major, build.minor, build.build, and build.revision) with values
from AssemblyInfo.cs. If property assemblyinfo.location is undefined, it will attempt to
read AssemblyInfo.cs from the current directory. -->
<target name="GetAssemblyVersion" description="Populates variables with the current version." >
<script language="C#">
<code><![CDATA[
public static void ScriptMain(Project project) {
string fileName = Path.Combine(project.BaseDirectory, project.Properties["assemblyinfo.location"]);
StreamReader reader = new StreamReader(fileName);
{
Regex expression = new Regex(@"AssemblyVersion");
string line = reader.ReadLine();
while (line != null) {
Match match = expression.Match(line);
if (match.Success) {
Regex pattern = new Regex("[0-9]+");
MatchCollection matches = pattern.Matches(line);
if (matches.Count != 4)
throw new Exception(string.Format("Version number in {0} has incorrect format.", fileName));
int major = int.Parse(matches[0].Value);
int minor = int.Parse(matches[1].Value);
int build = int.Parse(matches[2].Value);
int revision = int.Parse(matches[3].Value);
project.Properties["build.major"] = major.ToString();
project.Properties["build.minor"] = minor.ToString();
project.Properties["build.build"] = build.ToString();
project.Properties["build.revision"] = revision.ToString();
break;
}
line = reader.ReadLine();
}
}
reader.Close();
}
]]></code>
</script>
<echo message="major: ${build.major}"/>
<echo message="minor: ${build.minor}"/>
<echo message="build: ${build.build}"/>
<echo message="revision: ${build.revision}"/>
</target>

    <!--    Change CodeBehind=" instances to CodeFile="    -->
<target name="CodeBehindToFile" description="Fixes the web application web site annoyance">
<foreach item="File" property="filename">
<in>
<items>
<include name="xxxx\*.aspx"></include>
<include name="xxxx\UserControl\*.ascx"></include>
</items>
</in>
<do>
<script language="C#">
<code><![CDATA[
public static void ScriptMain(Project project) {
string contents;
using ( StreamReader reader = new StreamReader ( project.Properties [ "filename" ] ) )
{
contents = reader.ReadToEnd ();
reader.Close ();
}
contents = System.Text.RegularExpressions.Regex.Replace(contents,"CodeBehind=\"","CodeFile=\"",System.Text.RegularExpressions.RegexOptions.IgnoreCase);
using ( StreamWriter writer = new StreamWriter ( project.Properties [ "filename" ] , false ) )
{
writer.Write ( contents );
writer.Close ();
}
}
]]></code>
</script>
</do>
</foreach>
</target>

    <target name="SetAssemblyVersion" description="Increments/Sets the AssemblyVersion value" depends="GetAssemblyVersion">
<!-- Increment the build.revision value -->
<foreach item="File" property="filename">
<in>
<items>
<include name="xxxx\App_Code\AssemblyInfo.cs"></include>
<include name="Project2\AssemblyInfo.cs"></include>
<include name="Project3\AssemblyInfo.cs"></include>
</items>
</in>
<do>
<script language="C#">
<code><![CDATA[
public static void ScriptMain(Project project) {
string contents = "";
StreamReader reader = new StreamReader(project.Properties["filename"]);
contents = reader.ReadToEnd();
reader.Close();
string replacement = string.Format(
"AssemblyVersion(\"{0}.{1}.{2}.{3}\")]",
project.Properties["build.major"],
project.Properties["build.minor"],
project.Properties["build.build"],
project.Properties["svn.revision"]
);

string newText = Regex.Replace(contents, @"AssemblyVersion\("".*""\)\]", replacement);
StreamWriter writer = new StreamWriter(project.Properties["filename"], false);
writer.Write(newText);
writer.Close();
}
]]></code>
</script>
</do>
</foreach>
</target>

<target name="FxCop">
<exec program="${fxcop-console}" commandline="/file:precompiledweb/xxxx/bin /out:_fxcop_all.xml /directory:&quot;${nunit-location}" /searchgac" failonerror="false"/>
<!--
<foreach item="File" property="filename">
<in>
<items>
<include name="precompiledweb\xxxx\bin\*.dll"></include>
<exclude name="**\Castle.DynamicProxy.dll" />
</items>
</in>
<do>
<exec program="${fxcop-console}" commandline="/file:${filename} /out:_fxcop_${path::get-file-name-without-extension(filename)}.xml /directory:&quot;${nunit-location}" /searchgac" failonerror="false"/>
</do>
</foreach>
-->
</target>
<target name="sourcemonitor">
<!-- Create input command file -->
<property name="sourcemonitor.input" value="${codemetrics.output.dir}\sm_xxxx_cmd.xml" />
<echo file="${sourcemonitor.input}" append="false" failonerror="false">
<![CDATA[
<?xml version="1.0" encoding="UTF-8" ?>
<sourcemonitor_commands>
<write_log>true</write_log>
<command>
<project_file>${codemetrics.output.dir}\sm_xxxx.smp</project_file>
<project_language>CSharp</project_language>
<source_directory>${project::get-base-directory()}</source_directory>
<include_subdirectories>true</include_subdirectories>
<checkpoint_name>${svn.revision}</checkpoint_name>
<export>
<export_file>${project::get-base-directory()}\_sm_summary.xml</export_file>
<export_type>1</export_type>
</export>
</command>
<command>
<project_file>${codemetrics.output.dir}\sm_xxxx.smp</project_file>
<checkpoint_name>${svn.revision}</checkpoint_name>
<export>
<export_file>${project::get-base-directory()}\_sm_details.xml</export_file>
<export_type>2</export_type>
</export>
</command>
</sourcemonitor_commands>
]]>
</echo>
<!-- Execute the commands -->
<exec program="${sourcemonitor.executable}" commandline="/C ${sourcemonitor.input}" failonerror="false" />
<style style="SourceMonitor-Top15Generation.xsl" in="${project::get-base-directory()}\_sm_details.xml" out="${project::get-base-directory()}\_sm_top15.xml" />
<delete file="${codemetrics.output.dir}\sm_xxxx.smp" failonerror="false" />
</target>
</project>

Hopefully you find that useful, and that there's some ideas in there you can borrow. If you've got any questions please feel free to ask.