We made a decision early on in the project to adopt Apache Maven 1.0 and CruiseControl
(+JUnit etc) for Continous Integration in the Java sections of the project as we had dependencies on many other teams for artifacts and in turn many teams were dependant on our published proxies. Thus versioning and build quality became very pertenant issues very quickly.
I'll cover our Maven joys and woes in another blog, however what became apparent from Maven and CruiseControl was that the continous integration environment bought us a massive amount in terms of keeping the code base stable, finding and fixing bugs quickly, Deploying distributions and designatiung responsibility when a developer broke a build.
After an aborted attempt to integrate our dotnet components into Maven (again a blog for another time) I was told by a collegue about a new Java based tool called Ivy which provided all the dependency management functionality of Maven + a bit more but in a separate Java library. As it was only this that was missing from our Nant scripts (all the SCM checkouts/Build etc were already in tasks) I decided to spend a few late nights trying to knock up an example of a Maven type build in dotnet.
The tasks I wanted my build to perform were:
- Checkout the newest version of the source from our SCM (Source Control Management) software.
- Download the dependant binaries (dll's) from a repository (our Maven repos primarily). Using Ivy.
- Build the dll's including a version number in the file name
(Example: "Company.System.Subsystem-0.0.0.1.dll" or "Company.System.Subsystem-SNAPSHOT.dll")
- Run the Unit Tests (NUnit) during the build process.
- Manage/Update the version numbers using the AssemblyInfo.cs.
- Depoly the artifact to our repository
- When distributing to clients build the documentation.
- Checkin the updated AssemblyInfo.cs to SCM.
To keep things as simple as possible I chose to use all of the naming conventions/build conventions from Maven so that the team could understand the build across the languages.
Task 1: Creating a Standard Structure in SCM
The first task I did then was to do a a little refactoring of the code base to make our dotnet stuff a bit more Maven (sharp intake of breath from the Microsoft faithful)
As I initially used the Dotnet naming convention for all our libraries the code existed in our SCM in a structure like this.
LibraryName/Company.System.Subsystem/**/*.cs
LibraryName/Company.System.Subsystem.UnitTest/**/*.cs
To make things a little easier for myself I changed this to be, again this was easy as I was still in the proposal stage for this idea. If I was changing the entirity of our dotnet codebase this may well not be possible. However the prinicpals that follow apply no matter how you format your code:
/* this contained all our source for the actual component */
trunk/svn-module/Company.System.Subsystem/src/**/*.cs
/* this contained all our source for the unit tests */
trunk/svn-module/Company.System.Subsystem/test/**/*.cs
/* this contained all the config stuff and extra files */
trunk/svn-module/Company.System.Subsystem/etc/**/*.config
Task 2: Getting stuff to compile with version numbers
Because adding version numbers to dll name causes havoc with Visual Studio's references mechanism I decided to use the
<csc>Nant task rather than use the easier but more restrictive
<solution>option. This allowed me to add references at build time whose filenames do not affect the compile. It also allowed me to specify where and what I wanted the resulting artifact to end up/be called.
Task 3: Using Ivy for dependency management/publishing
As I hinted at above. Ivy is a Java solution to managing dependencies at build time for a given library. Even though it is written in Java it is in no way limited to managing purely Java projectsas I hope to illustrate. It also does some nifty stuff like transitive dependencies (for a tutorial see ivy or maven2 explanation) and reports etc.
The basics of Ivy's model are that It uses 1 or n number of "resolvers" to locate your dependencies in a local or remote file system. In a configuration file you tell Ivy which libraries you need and it will use these "resolvers" to download your libraries from the remote file system so you can compile against them. It them allows you to publish the component you have just built to one of these file systems using its "resolvers". This is obviously a massive over simplification of the tool. But the thing to keep in mind is that before you do a build it will get the files that your build depends on and after it will publish the successful results of your build (dll's configs, xml files etc) so that others can reference them within their projects.
For a more detailed view on Ivy please see: Jayasoft's Docco
As I wanted to execute Ivy from within Nant I used the
<exec>task to perform the retrieve and publish functions to/from the Ivy repository. See source on How this worked.
Task 4: Using Putty to publish the results of the build to a web server.
In my organisation access to all our boxes is generally through SSH. And thus to publish artifacts to a web server I needed to use SCP. For our Maven stuff this is done for us. But for the dotnet components I needed to create a hom grown solution.
Luckily the swiss army knife of remote terminals/ssh communication, Putty came to my rescue. The binaries that it has in its distribution are very happy to be used a command line. So wrapping this into another exec task I managed to publish my local ivy repository to our linux based maven repository. without any issues.
Where to go from here?
- This solution really caters for all of our immediate needs and can be extended to all of our library based dotnet projects but possible enhancements include:
- To work out how to compile a web project using Nant and this method.
- Create an Ant task to generate/modify the .proj Visual Studio file based on the result of the Ivy -retrieve command. Thus developers wouldn't have to manually add the references when they open a project. This would function much like the eclipse::generate-classpath goal in Maven 1.0.
Follow ups.
As I haven't included the configuration file/description for CruiseControl I'll do this in a follow up post but for those who want to dive straight in. I am using Java CruiseControl 2.2.1 with the nant build type.