I have a few minutes tonight, so in case anyone asks, I wanted to share my barebones build files that I put together for my CI BoF tomorrow night. These are two very simple NAnt scripts that you can use with a Cruise Control .NET (CCNET) setup. One of my biggest goals when putting these together was to make them as reusable as possible. I wanted to be able to take these from one project to the next and not have to make tons of edits to get them to work. Since my demo uses Subversion for source control, these files reference the SVN.EXE command line tool. If should be a simple matter to switch to a different provider.
The first one is the CCNET Bootstrapper file. The basic purpose of this file is to be the one that CCNET can use to clean, checkout and then build the project. I don't include this file in the actual project source tree. Rather it is part of the CCNET setup. It is completely parameterized so that the same file can be used for multiple CCNET projects on the same server.
<project name="ccnet.bootstrap" default="go">
<!--
Required Properties - You must set the following properties before running this build script!
project.dir The directory for the project
svn.url The subversion URL for the source checkout
build.file The buildfile to launch to build the project (path is relative to project.dir)
build.target The target to use when launching the build script
Use it like this from the command line to test it.
C:\CCNet\Projects>nant -buildfile:bootstrap.build -D:project.dir=CCNetDemo -D:svn.url=svn://localhost/svn/CCNetDemo -D:build.file=CCNetDemo\CCNetDemo.build -D:build.target=go
NOTE: Be careful about using relative paths in there! It can make things not work well...
Once you know it works, pass those -D arguments from your ccnet.config file in production.
-->
<property name="svn.executable" value="C:\Program Files\Subversion\bin\svn.exe" />
<target name="go" depends="clean, checkout, build" />
<target name="clean">
<attrib readonly="false">
<fileset basedir="${project.dir}">
<includes name="**/*"/>
</fileset>
</attrib>
<delete>
<fileset basedir="${project.dir}">
<includes name="**/*"/>
</fileset>
</delete>
<mkdir dir="${project.dir}"/>
</target>
<target name="checkout">
<!-- checkout all files out of construction -->
<exec program="${svn.executable}" commandline="checkout ${svn.url} ${project.dir}" />
</target>
<target name="build">
<nant buildfile="${build.file}" target="${build.target}" />
</target>
</project>
That is the file that is responsible for getting a fresh, clean checkout of the source tree and then lanching the master build file for that project. Here is the starting build file for my demo project.
<project name="default" default="go" basedir=".">
<!-- Project Directories -->
<property name="results.dir" value="testresults"/>
<!-- Project Properties -->
<property name="solution.config" value="Debug" />
<property name="solution.file" value="CCNetDemo.sln" />
<!-- Fileset Patterns -->
<property name="fxcop.include.pattern" value="./**/bin/${solution.config}/*.dll" />
<property name="nunit.include.pattern" value="./**/bin/${solution.config}/*.Tests.dll" />
<property name="clean.pattern.bin" value="./**/bin" />
<property name="clean.pattern.obj" value="./**/obj" />
<!-- EXE Locations -->
<property name="svn.executable" value="C:\Program Files\Subversion\bin\svn.exe" />
<property name="fxcop.executable" value="C:\Program Files\Microsoft FxCop\fxcopcmd.exe" />
<!-- Primary Entry Point -->
<target name="go">
<call target="clean" />
<call target="compile" />
<call target="nunit" />
<call target="fxcop" />
</target>
<!-- Alternate Targets -->
<target name="clean">
<delete>
<fileset>
<includes name="${clean.pattern.obj}"/>
<includes name="${clean.pattern.bin}"/>
</fileset>
</delete>
</target>
<target name="update">
<exec program="${svn.executable}" commandline="update" />
</target>
<target name="compile">
<solution configuration="${solution.config}" solutionfile="${solution.file}"/>
</target>
<target name="nunit">
<nunit2 verbose="true" haltonfailure="true">
<formatter type="Xml" usefile="true" extension=".xml" outputdir="${results.dir}" />
<test fork="true" failonwarning="true">
<assemblies>
<includes name="${nunit.include.pattern}" />
</assemblies>
</test>
</nunit2>
</target>
<target name="fxcop">
<foreach item="File" property="assembly.name">
<in>
<items>
<includes name="${fxcop.include.pattern}" />
</items>
</in>
<do>
<regex pattern="^(?'path'.*(\\|/)|(/|\\))(?'file'.*)$" input="${assembly.name}" />
<exec program="${fxcop.executable}"
commandline="/o:${results.dir}/${file}-fxcop.xml /c /f:${assembly.name} /s"
failonerror="false"/>
</do>
</foreach>
</target>
</project>
(This is all in one build file, which works for a simple setup like this, but if your build system gets more complicated you might want to consider splitting it into multiple build files and either launching them with the task or including them and calling them with the task.)
Like I said, I'm trying to make this as reusable as possible, and I may not have succeeded completely. I haven't actually used this script on a large project yet, so some of my "tricks" may not work at all. The main trick is that I am trying to get NUnit and FxCop to automatically figure out which assemblies to scan based on their assembly names. This works for my projects because we say in our coding standards that all test assemblies will be named XXX.Tests.dll. So I can run NUnit on those and FxCop on the others.
I would be very interested to hear what any of you think about these scripts.