Updating Our Build Process - More Details

posted on 2005-05-12 at 23:18:14 by Joel Ross

Almost a month ago, I posted about updating our build process, and asked if anyone wanted to see more details. I got a few people asking for it, so I figured I'd provide some more details of how we did some of things we did.

First, let's review what I changed. I added a "Prod" target, which does the following things:

 Stops the CruiseControl.NET service (this prevents builds from interfering with our prod build)
 Cleans both the dev and test directories
 Gets the latest into the dev directories
 Builds in release mode
 Copies files into the test directories
 Zips up the builds so they can be copied up to the production servers
 Optionally, it labels VSS with a command line specified label.
 Starts the CruiseControl.NET service again.

I'll go through the details of each one, and what the nant task is that will accomplish it.

To stop (and start) the cruise control service, it's pretty simple. First, you need to know how to start and stop services from the command line (net stop [ServiceName] and net start [ServiceName]). The service name for cruise control is ccservice, so the nant task to stop it is:

<exec program="net" commandline="stop ccservice" failonerror="false" />

I set it up to not fail on errors because it's not a fatal error if the service doesn't stop. I only stop the service so that if check-ins occur during the production build, the automatic build process doesn't kick off, and potentially screw up the production build. Starting the service is just as simple - change "stop ccservice" to "start ccservice" and you're good to go.

Before we talk about cleaning the directories, I need to back up a second and talk properties in Nant. A property is pretty easy to define. Here's the format:

<property name="Project.UseVss" value="true" />

This sets Project.UseVss to true. I highlight this property because I use this property to determine if the directories should be cleaned. We use Visual Sourcesafe in our build server, but all of our developers use SourceOffSite to access the source control, because our dev box is in California, while half of the developers are in Michigan. Even the guys in CA use SOS, because of some of the issues with solution and project files under source control - if two people use different source control systems, it constantly asks you to check out the project files. Anyway, if a developer wants to run the nant build locally, they probably don't want to get latest, so each developer can maintain their own properties in a separate XML file. Add this line to your build file:

<include buildfile="Properties.xml" failonerror="false" />

This tells Nant to look for a file called Properties.xml, and each developer can override any properties (or add new ones). The nice thing is that Nant doesn't puke if that file isn't found, so there's no need to keep this file under source control - each developer maintains their own copy. It's a lot like an alternate config file defined in web.config.

So each developer would set the Project.UseVss property to false. This tells our build process that any source control operations should be ignored. It also means that we probably don't want to clean the dev folders. When a developer builds locally, they are most likely testing a change they made - overwriting those changes would be counterproductive. So here is how our cleaning task is set up:

<if propertytrue="Project.UseVss">
 <call target="Clean" />
 <call target="UpdateAll" />
</if>

This also handles getting latest, which I'll talk about more later. The first thing to notice is that it checks to see if Project.UseVss is true. If so, then it calls two tasks - Clean and UpdateAll.

The clean target is fairly straight forward, with one exception. You have set each file to have normal attributes (not read-only) before you can delete it, and since most of our files are read-only (since they are coming from VSS), we have to do that. That's pretty simple:

<attrib normal="true">
 <fileset basedir="${Project.Root}\MyFolder\">
  <includes name="**/*" />
 </fileset>
</attrib>

This sets every file in \MyFolder\ (and sub folders) to have normal file attributes. Notice another property in use - Project.Root defines where $/ folder in VSS is mapped to - that way, each developer can use whatever folder structure they want - for example, I use c:\sourcesafe\NCM\ for my root, but on our build server, it's just c:\sourcesafe\.

Deleting the files is pretty easy too:

<delete>
 <fileset basedir="${Project.Root}\MyFolder\">
  <includes name="**/*" />
 </fileset>
</delete>

This recursively deletes all files. When I did the web folders, I was more explicit about what files I deleted - I didn't want to delete all of the files, such as project files, so I specified the types explicitly:

<delete>
 <fileset basedir="${Project.WebRoot}\MySiteFolder\">
  <includes name="\**.cs" />
  <includes name="\**.aspx" />
  <includes name="\**.aspx.cs" />
  <includes name="\**.aspx.resx" />
  <includes name="\**.asax" />
  <includes name="\**.asax.cs" />
  <includes name="\**.asax.resx" />
  <includes name="\**.css" />
  <includes name="\**.js" />
  <includes name="\**.scc" />
  <includes name="\**.htm" />
  <includes name="\**.jpg" />
  <includes name="\**.gif" />
  <includes name="\**.xml" />
  <includes name="\**.txt" />
  <includes name="web.config" />
  <includes name="\**.ascx" />
  <includes name="\**.ascx.cs" />
  <includes name="\**.ascx" />
  <includes name="\**.ascx.resx" />
  <includes name="bin\*.dll" />
 </fileset>
</delete>

Here's another use of a property - Project.WebRoot. Since some developers prefer to set up their virtual folders manually, so it's still under the source root and others let VS.NET choose where (usually c:\inetpub\wwwroot\), I added this so each developer could define their own path, as well as having a different path on our build server. Using the \**.ext format recursively deletes any file that ends with .ext.

The cleaning process goes through every project and cleans all of the folders in dev, as well as cleaning all of the test folders, which is where we deploy our test builds (yes, I know, having our test build and dev build on the same machine isn't best practice, but it could just as easily be cleaning and deploying over mapped drives).

Now that we've cleaned our folders, it's time to get the latest version of the source so we can build. Here's a full target for updating part of our source:

<target name="UpdateShared">
 <if propertytrue="Project.UseVss">
  <if propertyexists="Project.BuildLabel">
   <vssget user="${Project.VssUid}" password="${Project.VssPwd}" version="${Project.BuildLabel}" localpath="${Project.Root}\MyFolder\" recursive="false" replace="true" writable="false" dbpath="${Project.VssRoot}\srcsafe.ini" path="$/MyFolder" />
  </if>
  <ifnot propertyexists="Project.BuildLabel">
   <vssget user="${Project.VssUid}" password="${Project.VssPwd}" localpath="${Project.Root}\MyFolder\" recursive="false" replace="true" writable="false" dbpath="${Project.VssRoot}\srcsafe.ini" path="$/MyFolder" />
  </ifnot>
 </if>
</target>

A few things to notice here. First, we only get latest if Project.UseVss is true. The next item is checking to see if Project.BuildLabel exists. If this is defined, we tell VSS that we don't want the latest version, but we want the version that was labeled with the value in Project.BuildLabel. This property isn't defined anywhere in our build file. If you want to get a label, on the command line you add -D Project.BuildLabel=1.0.0 - this would grab the code as it was when the label 1.0.0 was applied. Note: I had issues retrieving to a label with a space in it.

There are multiple gets, depending on what is being built, but they all look like the above target. Building is pretty straight-forward. We use the solution tasks, but they are in different build files. Calling into another build file is pretty simple:

<nant buildfile="MySubBuildFile.build" target="TestBuild" inheritall="true" />

This tells nant to call a target in another build file. This target builds our project in release mode in the dev directories and then copies those files over to the test directories. Those are pretty straight forward, and I've covered them before, so I won't rehash that.

The zip task is also pretty easy:

<zip zipfile="${Project.TestRoot}\Web.zip">
 <fileset basedir="${Project.TestRoot}\Current\Web\">
  <include name="**/*" />
 </fileset>
</zip>

You give a target for the zip, and then tell it what files to include.

Now for the last piece that's a little complicated. Labeling. I've got it set up that you can provide a label on the command line, and the following command will label the repository for me:

<if propertytrue="Project.UseVss">
 <if propertyexists="Project.ApplyLabel">
  <vsslabel user="${Project.VssUid}" password="${Project.VssPwd}" dbpath="${Project.VssRoot}\srcsafe.ini" path="$/CurrentCode" comment="${Project.ApplyLabel}" label="${Project.ApplyLabel}" />
 </if>
</if>

Here's what's happening. First, I check to see if we're using VSS, then if we've supplied a label. If that's the case, then the vsslabel command will label our CurrentCode folder, where we store our current code. Again, I had trouble with spaces in the label. Luckily, I didn't have  to worry about that.

So there you have it. Its' not rocket science, but it is one of the more complicated build files I've ever worked on. Let me know if you have any questions or comments!

Categories: Development