No Free Time

Because my therapist says I need to let things out

Archive for the ‘nant’ Category

Build Helpers

Posted by andrewmyhre on February 26, 2008

I’ve made a couple of build helper files which may come in handy. They cover compilation, testing and deployment, and hopefully can be reused between projects with minimal need to be rewritten. I’ll share the compilation one today.

Compilation Build Scripts

As background my folder structure looks like this:

/Solution/ClassLibrary1
/Solution/ClassLibrary2
/Solution/Website1
/Solution/Resources
/Solution/Build
/Solution/Build/bin
/Solution/Build/src
/Solution/Build/testlib
/Solution/Build/deploy

Resources is where everything like NAnt, MBUnit, log4net, NCover etc all reside. Various things happen within the Build folder: src is where all projects are copied into before being compiled, bin is where each compiled assembly is placed for easy reference for other assemblies (e.g: where ClassLibrary2 depends on ClassLibrary1), testlib is where all test assemblies are compiled and run, and deploy is where our final compilation will end up.

The reason each assembly is copied into the src folder prior to compilation is so that we can do tricky things like substituting *.config and AssemblyInfo.cs files, as you’ll see below.

The build file starts this way:

    1 <?xml version="1.0"?>
    2 <!--EXTERNAL_PROPERTIES: output.dir;AssemblyName;debug;assembly.label-->
    3 <project name="general compilation">
    4   <target name="clean" >
    5     <delete dir="${output.dir}"/>
    6   </target>
    7
    8   <target name="init" depends="clean" >
    9     <mkdir dir="${output.dir}" />
   10     <mkdir dir="${output.dir}/bin" />
   11   </target>

Straight forward. Note that the ${output.dir}, ${AssemblyName}, ${debug} and ${assembly.label} properties are expected to be set – these will be set from your calling build file. ${output.dir} can simply be “c:pathSolutionBuild”. ${AssemblyName} is just the name of the assembly you want to compile at that time.

Assembly compilation:

   13 <target name="general.compile.assembly" description="compiles a satellite assembly">
   14     <echo message="*******************************" />
   15     <echo message="***COMPILING ${AssemblyName}" />
   16     <echo message="***TO ${AssemblyOutputFolder}" />
   17     <echo message="*******************************" />
   18
   19     <delete dir="${output.dir}src${AssemblyName}" />
   20
   21     <copy todir="${output.dir}src" flatten="false">
   22       <fileset>
   23         <include name="${AssemblyName}***.*" />
   24         <exclude name="${AssemblyName}bin" />
   25         <exclude name="${AssemblyName}config" />
   26         <exclude name="${AssemblyName}**AssemblyInfo.cs" />
   27       </fileset>
   28     </copy>
   29
   30     <call target="general.labelassembly" />
   31
   32     <csc target="library" output="${output.dir}bin${AssemblyName}.dll" debug="${debug}">
   33     <sources>
   34       <include name="${output.dir}src${AssemblyName}/**/*.cs" />
   35     </sources>
   36     <references refid="assembly.resources" />
   37     </csc>
   38     <copy file="${output.dir}bin${AssemblyName}.dll" tofile="${AssemblyOutputFolder}${AssemblyName}.dll" />
   39   </target>

As the description says, this target compiles a satellite assembly, or class library. It first deletes the src folder (/Solution/Build/src/AssemblyName) before copying the latest source into it. Then it calls the “general.labelassembly” target, which we’ll see shortly. Finally it compiles all .cs files into a dll.

Note line 36, where the references tag uses assembly.resources. This is because you should call the target like this:

    5     <!-- Class Library 1 -->
    6     <property name="AssemblyName" value="ClassLibrary1" />
    7     <assemblyfileset id="assembly.resources">
    8       <include name="${resources.dir}/log4net.dll" />
    9     </assemblyfileset>
   10     <call target="general.compile.assembly" />

This way you only specify exactly what you need to in order to build the assembly.

Now, websites. First we need to prepare a few things:

   41 <target name="prepare.web" >
   42     <mkdir dir="${output.dir}src${website.name}" />
   43     <echo message="*******************************" />
   44     <echo message="***COMPILING ${website.name}" />
   45     <echo message="*******************************" />
   46
   47     <copy todir="${output.dir}src" flatten="false">
   48       <fileset>
   49         <include name="${website.name}***.*" />
   50         <exclude name="${website.name}config" /> <!-- this are where we put environment-specific config files -->
   51         <exclude name="${website.name}bin*.*" />
   52         <exclude name="${website.name}web.config" />
   53       </fileset>
   54     </copy>
   55     <copy todir="${output.dir}src${website.name}bin" flatten="true">
   56       <fileset>
   57         <include name="${output.dir}bin*.*" />
   58       </fileset>
   59     </copy>
   60     <copy todir="${output.dir}src${website.name}bin" flatten="true">
   61       <fileset refid="website.resources" />
   62     </copy>
   63   </target>

Then we can compile the site itself. Note that we don’t copy the web.config over with the website. This is because I have a seperate target to copy a config file, replacing environment specific variables as it copies. I’ll come to that in a bit.

The next target is straightforward – compile the website to the /Build/Deploy/WebsiteName folder.

   65 <target name="general.compile.website" description="compiles a website" depends="prepare.web">
   66     <mkdir dir="${output.dir}Deploy${website.name}" />
   67     <exec program="aspnet_compiler.exe"
   68         basedir="C:WINDOWSMicrosoft.NETFrameworkv2.0.50727"
   69         workingdir="${output.dir}"
   70         commandline="-u -v /${website.name} -p ${output.dir}src${website.name} ${output.dir}Deploy${website.name}"  />
   71   </target>

Here’s a target used to copy a config file.

   73 <target name="general.compile.website.copyconfig" description="Copies an XML file to the website's web.config, replacing tokens as specific in fileset">
   74     <copy file="${template.sourcefilename}" tofile="${output.dir}Deploy${website.name}web.config">
   75       <filterchain refid="config.settings" />
   76     </copy>
   77   </target>

And finally the target to label the assembly.

   79   <target name="general.labelassembly">
   80     <asminfo output="${output.dir}src${AssemblyName}AssemblyInfo.cs" language="CSharp">
   81       <imports>
   82         <import namespace="System.EnterpriseServices" />
   83         <import namespace="System.Reflection" />
   84         <import namespace="System.Runtime.CompilerServices" />
   85       </imports>
   86       <attributes>
   87         <attribute type="AssemblyVersionAttribute" value=""${assembly.label}"" asis="true" />
   88         <attribute type="AssemblyTitleAttribute" value=""Canon Create"" asis="true" />
   89         <attribute type="AssemblyDescriptionAttribute" value=""assembly for Canon Create"" asis="true" />
   90         <attribute type="AssemblyCopyrightAttribute" value=""Copyright (c) 2008"" asis="true" />
   91         <attribute type="ApplicationNameAttribute" value=""${AssemblyName}"" asis="true" />
   92       </attributes>
   93       <references>
   94         <include name="log4net.dll" />
   95       </references>
   96     </asminfo>
   97   </target>

Using the build scripts

You can invoke the website compiler like so:

   13 <!-- build the frontendwebsite website -->
   14     <property name="website.name" value="FrontEndWebsite" />
   15
   16     <assemblyfileset id="website.resources">
   17       <include name="${resources.dir}/DynamicPDF.Generator.Server.dll" />
   18       <include name="${resources.dir}/DynamicPDF.Merger.Server.dll" />
   19       <include name="${resources.dir}/NetSpell.SpellChecker.dll" />
   20       <include name="${resources.dir}/FreeTextBox.dll" />
   21       <include name="${resources.dir}/log4net.dll" />
   22     </assemblyfileset>
   23     <!-- compile the site -->
   24     <call target="general.compile.website" />

And to copy config settings:

   26     <!-- copy the frontendwebsite web.config with replacing tokens -->
   27     <filterchain id="config.settings">
   28       <replacetokens>
   29         <token key="token.defaultConnectionString" value="server=engelbart;database=CANON_CREATE_V6;uid=sa;pwd="/>
   30         <token key="token.smtpserver" value="10.111.1.55" />
   31         <token key="token.baseSiteUrl" value="http://localhost:4870" />
   32         <token key="token.XHTMLTemplateUploadPath" value="C:Inetpubvirtual2005ProjectsCanonCanonCreate2005FrontEndWebsitexhtmltemplateuploads" />
   33         <token key="token.ConfigurationFilesDirectory" value="C:Inetpubvirtual2005ProjectsCanonCanonCreate2005ConfigurationFiles" />
   34       </replacetokens>
   35     </filterchain>
   36
   37     <!-- copy the frontendwebsite.config.template.xml file to /frontendwebsite/web.config with tokens replaced -->
   38     <property name="template.sourcefilename" value="template.config.FrontEndWebsite.xml" />
   39     <call target="general.compile.website.copyconfig" />

Here’s how I’m labelling my build. Generally I just want to use the CCNetLabel variable passed through from CCNet, but for added flexibility The target expects the ${assembly.label} variable. I simply check for CCNetLabel and copy it to the new variable.

   68 <!-- label the assembly -->
   69     <ifnot propertyexists="CCNetLabel">
   70       <fail message="CCNetLabel property not set, so can't create labelled distribution files" />
   71     </ifnot>
   72     <trycatch>
   73       <try>
   74         <echo message="build number ${CCNetLabel}" />
   75         <property name="assembly.label" value="${CCNetLabel}" />
   76       </try>
   77       <catch>
   78         <property name="assembly.label" value="1.0.0.1" />
   79       </catch>
   80     </trycatch>

Posted in CI, nant | Tagged: , | Leave a Comment »

Small NAnt Gotcha

Posted by andrewmyhre on February 7, 2008

Say I have two XML build files. The first is intended to call a target in the second.

Example1.xml:

.cf { font-family: Courier New; font-size: 8pt; color: black; background: white; } .cl { margin: 0px; } .cln { color: #2b91af; } .cb1 { color: blue; } .cb2 { color: #a31515; } .cb3 { color: red; }

    1 <?xml version="1.0"?>
    2 <project name="Master build file" default="build" basedir=".">
    3
    4   <echo message="first" />
    5
    6   <include buildfile="example2.xml" />
    7
    8   <target name="build" description="Main target">
    9     <echo message="second" />
   10     <call target="secondary.target" />
   11   </target>
   12 </project>

Example2.xml:

.cf { font-family: Courier New; font-size: 8pt; color: black; background: white; } .cl { margin: 0px; } .cln { color: #2b91af; } .cb1 { color: blue; } .cb2 { color: #a31515; } .cb3 { color: red; }

    1 <?xml version="1.0"?>
    2
    3 <target name="secondary.target">
    4   <echo message="third" />
    5 </target>

I had expect that the secondary.target target would not be called until after the build target was called. Instead:

.cf { font-family: Courier New; font-size: 8pt; color: black; background: white; } .cl { margin: 0px; }

NAnt 0.85 (Build 0.85.2478.0; release; 14/10/2006)
Copyright (C) 2001-2006 Gerry Shaw
http://nant.sourceforge.net

Buildfile: file:///C:/Inetpub/virtual2005/Projects/Canon/CanonCreate2005/example1.xml
Target framework: Microsoft .NET Framework 2.0
Target(s) specified: build

[echo] first
[echo] third

build:

[echo] second

BUILD FAILED

Target 'secondary.target' does not exist in this project.

Total time: 0 seconds.

Huh? Why did the output go “first”, “third”, then “second”? And why does it say secondary.target doesn’t exist?

The documentation for the <include> task states:

Any global (project level) tasks in the included build file are executed when this task is executed. Tasks in target elements are only executed if that target is executed.

Which means my build file should be executing correctly. It’s in a target, isn’t it?

So I tried wrapping the secondary target in a project tag, and lo and behold:

.cf { font-family: Courier New; font-size: 8pt; color: black; background: white; } .cl { margin: 0px; }

NAnt 0.85 (Build 0.85.2478.0; release; 14/10/2006)
Copyright (C) 2001-2006 Gerry Shaw
http://nant.sourceforge.net

Buildfile: file:///C:/Inetpub/virtual2005/Projects/Canon/CanonCreate2005/example1.xml
Target framework: Microsoft .NET Framework 2.0
Target(s) specified: build

[echo] first

build:

[echo] second

secondary.target:

[echo] third

BUILD SUCCEEDED

Total time: 0 seconds.

So the NAnt docs aren’t entirely complete, are they. So I suppose the tag <project> is really a way of saying “this is build script which I want to control the execution for, instead of just processing the whole file as NAnt reads it”.

Posted in CI, nant | Tagged: , | Leave a Comment »