My Blahg

August 14, 2008

CruiseControl.NET, and… Delphi?

Filed under: Continuous Integration/CruiseControl.NET, Delphi, NAnt — treyhutcheson @ 9:33 am

I recently began working on one of my company’s legacy applications, written in Delphi, using Borland Developer Studio 2006. I got sick of every developer doing one-off builds every time some one needed a new executable, so I made it my mission to integrate the project into our Automated Build Environment. It was a long and tortuous process, but completely worth it. During the past 6 months, the build environment has spit out exactly 662 successful builds of this project, averaging 3.6 successful builds per day(including weekends).

Command Line Interface & Licensing
I’m not a Delphi guru, so this entire effort was full of exploration. The starting point was working with Delphi/BDS via the command line. I did some research, and found that there were two command-line candidates: the actual compiler, dcc32.exe, and the executable for the development environment, bds.exe.

I spent maybe a day trying to get dcc32.exe to work. I consulted documentation, checked newsgroups, googled 25 different search terms, and I still couldn’t get it to work. I decided to simplify the problem, and create a new command line project with one source file. I turned on the option in the environment to show the command line. I used the exact same command line that the environment was telling me it was using, and I still couldn’t get it to compile. That was well over 6 months ago, so I can’t remember the exact compiler error messages at this point. Regardless, pursuing dcc32 wasted a day, so I then turned to bds.exe.

The documentation for BDS 2006 states that the bds.exe executable does support a command line interface, so I was in luck. The funny thing is that I couldn’t find any info on the CLI through the integrated help, but when I ran “bds /?” from the console, the BDS environment started up, and opened the integrated help to the correct topic. I don’t know how much it will help, but the url(in the integrated help) for the docs is: ms-help://borland.bds4/bds4ref/html/idecommandline.htm, and the navigation path is Borland Help > Developer Studio 2006 (Common) > Refreence > Command Line Switches > IDE Commnad Line Switches and Options.

With the aid of the documentation, it was pretty simple to run bds from the console. However, there were a few hiccups. First, you must remember that bds.exe is a gui application first that appears to have had a CLI tacked on at the end. This is unfortunate. The executable does not write to STDOUT or STDERR, nor does it support process exit codes. The other problem is that your environment must be setup correctly! More on that in a moment.

After I had decided the practical matter of how I would handle the builds from the console, I needed to check on licensing. BDS 2006 is quite expensive, and the installation/licensing mechanism is quite complex. I needed to check with Borland(or Inprise, or CodeGear, or whatever the company name was that quarter) on the licensing terms. I called, went through phone limbo for a while, until I was finally connected to a sales person. I explained that the company already had x licenses of the product and none were free, and that I needed another so I could perform automated builds in a dedicated build environment. I was transferred multiple times until I finally reached some licensing specialist. I explained the scenario again, and was put on hold again. When the “specialist” returned, I was told that no one had ever inquired about using licenses for this purpose before. I was then told that as long as it was installed in a dedicated environment, and that this new installation would not be used for actual interactive development, that I did not need a separate license. That was good news.

This is a good point to ask a question. I’ve already established that I was the first person in the world to attempt to develop a cruisecontrol.net dashboard plugin, and now it appears that I was the first person to ever contact the company about automated builds. The question: how do I end up doing all these freaky fringe things? Sorry for the tangent.

Anyway, having gotten the OK on the license front, it was time to implement a solution.

Environment Setup is Critical
As I previously mentioned, environment setup is critical. The bds executable is a gui application, and does not behave as one would expect from a CLI. When bds.exe is executed from the console, it actually launches the development environment interactively, loads a specified project, and attempts to compile it. You can actually watch it compile. When its completes, it closes. However, there are cases where the environment does not close. For example, if your project references a component or package that cannot be found, the environment will complain via a modal message box. Also, if the act of compiling changes some file in the project, the environment will ask you if you wish to save the changes before it closes. Specifically, this happens if you have the “Include version information in project” option selected.

This is a Big Deal. If you’re running CruiseControl.NET as a service, and the BDS environment prompts one of these modal dialogs, your process will hang. You can get around this problem by setting a timeout in the ccnet project configuration, but that will cause the build to fail. Here are a few tips for getting around this problem:

  • Setup your build environment, including installed packages and the like, first.
  • Do not include version info in your project
  • Don’t mess with project resources
  • The point is, getting the environment setup correctly is the most important part of the process. This integration is pretty fragile, and doesn’t take much to break. You’re going to spend quite a bit of time logged in to your build environment, interactively testing your build script, before you finalize it.

    Versioning and Project Resources
    Our build script handles the versioning of the project. It uses a set of custom NAnt tasks to generate and increment the version number for the build. I’ll cover the minutiae of auto-versioning in a different post. Regardless, after the version number is generated, the build script injects the version number into the .bdsproj file before the bds.exe is launched. This method allows the project output to include the version number, but avoids the problem of hanging the bds environment because a file changed during the compile phase.

    The version number is separated into its components: Major, Minor, Build, and Release, via calls to custom embedded c# script functions. That information is then injected into the .bdsproj file via the NAnt xmlpoke task. It is important to note here that Delphi reverses the Build and Release members of a version, at least when compared to a dotnet version structure. So you’ll see in the script that these values are swapped when injected. Here’s the code listing for the version injection:

    <!-- extract the version members -->
    <property name="version.major" value="${script::get-version-major(version)}" />
    <property name="version.minor" value="${script::get-version-minor(version)}" />
    <property name="version.build" value="${script::get-version-build(version)}" />
    <property name="version.revision" value="${script::get-version-revision(version)}" />
    
    <xmlpoke
    	file="${file.proj}"
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo[@Name='IncludeVerInfo']"
    	value="True" />
    <xmlpoke
    	file="${file.proj}"
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo[@Name='AutoIncBuild']"
    	value="False" />
    <xmlpoke
    	file="${file.proj}"
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo[@Name='MajorVer']"
    	value="${version.major}" />
    <xmlpoke
    	file="${file.proj}"
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo[@Name='MinorVer']"
    	value="${version.minor}" />
    <xmlpoke
    	file="${file.proj}"
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo[@Name='Release']"
    	value="${version.build}" />
    <xmlpoke
    	file="${file.proj}"
    	xpath="BorlandProject/Delphi.Personality/VersionInfo/VersionInfo[@Name='Build']"
    	value="${version.revision}" />
    <xmlpoke
    	file="${file.proj}"
    	xpath="BorlandProject/Delphi.Personality/VersionInfoKeys/VersionInfoKeys[@Name='FileVersion']"
    	value="${version}" />
    <xmlpoke
    	file="${file.proj}"
    	xpath="BorlandProject/Delphi.Personality/VersionInfoKeys/VersionInfoKeys[@Name='ProductVersion']"
    	value="${version}" />
    

    As you can see, this code injects the version components individually into the .bdsproj file, and also sets the output’s File version and Product version values. Next, we’re going to deal with resources.

    At one point a few months ago, our build process was brought to its knees. It turns out that some one changed the application’s main icon and checked in the modified files. For some reason, this caused some checksum to be out of sync with some other file(I believe it was the project’s .drc file), which caused BDS to believe that some change had been made during the compile phase, which resulted in BDS prompting the user to save changes. Because this was occurring during a non-interactive service, the process hung until it finally timed out. That’s why I mentioned that you do not want to mess with a project’s resources.

    To get around the problem of the icon, I figured out how to generate the application’s icon from the build script. I removed the application’s icon, saved the project, and committed the changes. I then added a separate .ico file to the repository, named [Project].ico. Next, I created a resource script named [Project].rc. Finally, before I launch the bds.exe executable, I run brcc32.exe to compile the resource script, which outputs the resource into [Project].res.

    The resource script is a simple one line text file. Here’s an example: MAINICON Icon Project1.ico
    This little work around has a few benefits. First, bds was no longer prompting us to save changes, and second, to change the app’s icon, one need only commit a new [Project].ico to the repository.

    Building the Project
    Now that the version information has been set, and the main application resource has been generated, it’s time to finally launch bds.exe and perform the build. This process is actually fairly easy, so I’ll let the following snippet of NAnt script do all the talking:

    <target name="build" depends="clean">
    	<property name="file.bds.args" value="-ns -nw -b ${file.proj}" />
    
    	<!-- generate the new version number -->
    	<call target="gen-version" />
    
    	<!-- delete the current .res file, and generate the new one -->
    	<delete file="project.res" />
    	<exec program="${env.borland_home}\brcc32.exe" commandline="Project.rc" />
    
    	<exec program="${file.bds}"	commandline="${file.bds.args}" basedir="E:\Program Files\Borland\BDS\4.0\Bin"
    	failonerror="true" timeout="600000" />		
    
    	<!-- revert the transient project files -->
    	<call target="revert-project-temp-files" />
    </target>
    

    File Reversion
    The compile phase emits some files and changes a few others. Some of these files are transient, and I don’t want the changed files to be committed back to the repository. Hence the custom target “revert-project-temp-files”.

    We’re using cvs as our repository, so the script logs in to cvs, deletes a series of files, and performs a CVS UPDATE on each file that was deleted. This ensures that the transient files are up to date before the next build begins. The list of transient files includes the .bdsproj file(we changed it when we generated the version information), the project.cfg file, the project.drc file, the project.res file, and any .tlb/_TLB.pas files.

    Conclusion
    That should be enough to get you started, though the process is not yet complete. The next post will discuss handling the .err file, and including the compile-related errors and warnings in the CruiseControl.NET build report.

    No Comments Yet »

    No comments yet.

    RSS feed for comments on this post. TrackBack URI

    Leave a comment

    Blog at WordPress.com.