05 September 2012

ccnet 1.8.* and powershell

Two weeks ago I finally got my Windows Server 2008 R2! Hurray! I can now start on my buildserver for all the projects that I'm working on.

It cannot be over-emphasized how important it is to have a buildserver for your project. In fact, it is even an item in the c++ coding standards book: "Use an automated build system"
Some people ask me what the use is of a buildserver, and I answer them: "Do you want to take the responsibility to release a product that has not been build by a buildserver? Do you want to release a build that has been put together manually by somebody of the team, in the hope he remembers to take all the right steps, that he is completely in sync with the cvs, without locally changed files? Or even worse, say you want to release a second version, this time another person must do the build (the first one is on holliday), are you sure he knows all the steps, that he is in sync and that he has the correct compiler installed?"

This is only one reason why you'd use a buildserver, I can think of many more. Even for a small project, that only you yourself work on, it is good to let the buildserver build it. One reason (there are more): small one-person projects tend to become bigger projects and attract more developers and before you know it there is a small team working on it, or it is used in a bigger project. I've seen this happen more often than not.

But that's not the topic that I want to write about. For years I've been using ccnet, an open source, .Net based build server. Extremely easy to use, very versatile.
Recently the latest version 1.8.0 was released so I installed it on my new server. I haven't had the time yet to review the new features because I need my projects up and running fast.

One of the important features I always use is the assembly version labeller, which for me looks like:

<labeller type="assemblyVersionLabeller">
<major>1</major>
<minor>2</minor>
<incrementOnFailure>false</incrementOnFailure>
</labeller>

In combination with perforce this gives me a version number as an environment variable that looks like 1.2.{buildnumber}.{versionnumber}. From that number I know what build it was, and with what version on perforce it was build. With this number it is key to incorporate it as mush as possible in the application; in the installer, in the exe, in the assemblies, etc. Because with this information, if your application is in the field and you start receiving bugs and feedback, you always know exactly about which version people are talking, you know exactly with what files that exe was build that had a specific error, you know when they fucked up and when you did. It's something I've learned from dire experience at Larian. There were a lot of versions of Divinity II made and the first ones did not have a good version number, which made it impossible to track bugs and user's versions.

So for C# exe's and assemblies I use this powershell script:

$version = [environment]::GetEnvironmentVariable("CCNetLabel","Process")
foreach($arg in $args)
{
$file = [System.IO.Path]::Combine($arg, "Properties\AssemblyInfo.cs")
$old = "`"[0-9]+.\s?[0-9]+.\s?[0-9]+.\s?[0-9]+`""
$new = "`"" + $version + "`""
(Get-Content $file) | % {$_ -Replace $old , $new} | Set-Content -Path $file
}

As argument you give it the folder of your assembly, and it will change the version numbers to the ccnet label version number. This needs to be done before the devenv task and the result is an assembly or exe with correct version numbers if you look at the details of the file. This is extremely convenient to ask a user what version of a certain file they have, or to check yourself what version you're using.

And then hell started for me, somehow the script that I've used on several other ccnet servers, didn't work on the new one. I had the executing policy correct (even for 64bit Windows) which is the common pitfall, but no luck.
To make a long story short: the 1.8.0 and 1.8.1 version of ccnet place the arguments for your script before your script instead of after in the command line, a typo I guess. Luckily it is open source so you can easily create a quick fix until it is fixed in a new version (it really is easy, the source compiles right out of the zip).

But what I forgot was that the "scriptsDirectory" parameter must be absolute or the task takes the default one (which does not exist for the LocalSystem user). You'd expect that you can give a relative path as in all other ccnet tasks, but not for the powershell task. Apparently this is a bug that's always been in there, but I forgot. I looked in the source to fix it but it appears te be a little more tricky than I imagined.

In any case, you're warned now, and you can follow this thread on the ccnet user group if you'd like to know if it gets resolved. There's also some more info there.

3 comments:

.ig said...

Well, at Larian we still have the bad habbit to not check in the build scripts themselves. If there is any advice I can add to your post, it is to put them under version control. I know because I had to re-write old DragonCommander scripts to set up the DKS build. (DKS is re-released as part of Larian's 10 Y antology)

Nevertheless, I spend about 2 or 3 days getting the buildmanager and ccnet builds in place, then a few more days to tweak things like publisher/autorun/launcher etc..

I'm glad there is something like the buildmanager, even if it isn't perfect.

Ruben Willems said...

CCNet 1.8.2 has been released to fix this problem with the modules error.

as for the scriptsDirectory item :
would it not better be that this defaults to empty?

I think most people just use the script element only. And this way it should be a lot easier to support relative paths also.
if the powershell script calls other powershell scripts, those should also be relative to the first script.

Alex Vanden Abeele said...

@Ruben:

That would certainly be easier, the problem is though that if you leave the scriptsDirectory empty, the powershell task takes a default one in the user's home directory. But for the LocalSystem user that directory does not exist. Maybe if the powershell task did not set a scriptsDirectory at all when starting powershell, it would work... I'll try that tomorrow.