Colourblind

Welcome to Colourblind.

This is the personal web space of Tom Milsom. As much as possible everything is free (as in speech and as in beer).


Make text: Smaller Bigger

Making MSBuild in NANT act like MSBuild in Visual Studio

Posted by Tom on 21/12/2011 09:45:37

So I was making some NANT scripts for an ASP.NET MVC project.

Rather than going through the rigmarole of maintaining references and dependencies in two places (the VS project file and the NANT scripts), this time round I started using the <msbuild> task from nantcontrib instead of invoking the compiler directly using the plain old <csc> task. After making sure I set my OutDir I run NANT and get a failed build while copying a file, which is odd because I don't want MSBuild doing anything of the sort. It builds fine from Visual Studio, but after a little investigation it turns out that MSBuild is attempting to copy a file which is in the project but is missing from the file system. It's trying to drop it, along with the rest of the project contents, into _PublishedWebsites which has attaching itself to my output directory, like a warty blemish or tumour on my otherwise pristine build.

By setting the verbosity level of MSBuild to Diagnostic in both NANT and Visual Studio I could diff the two outputs and see where the processes started to diverge. The output of the NANT ran to a svelte 26k lines, but a quick search on '_PublishedWebsites' turns up the part of the log which includes the failing build. The copy operation is happening within a _CopyWebApplication target. Time to do a quick search in the Visual Studio MSBuild log . . .

Aha! Here's the slippery little devil:

Target "_CopyWebApplication" skipped, due to false condition; (!$(Disable_CopyWebApplication) And '$(OutDir)' != '$(OutputPath)') was evaluated as (!False And 'bin\' != 'bin\').

But why? Why would you do that? What is the OutputPath even supposed to be? According to MSBuild\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets . . .

<!--
============================================================
_CopyWebApplication

This target will copy the build outputs along with the content files into a 
_PublishedWebsites folder.

This Task is only necessary when $(OutDir) has been redirected to a folder 
other than ~\bin such as is the case with Team Build.

The original _CopyWebApplication is now a Legacy, you can still use it by 
setting $(UseWPP_CopyWebApplication) to true. By default, it now change to 
use _WPPCopyWebApplication target in Microsoft.Web.Publish.targets. It allow 
to leverage the web.config trsnaformation.
============================================================
-->

Fine. Whatever. Regardless of the rationale, if you set your OutDir and OutputPath properties to the same value in the NANT <msbuild> task:

<msbuild project="${dir.src}/${project::get-name()}/${project::get-name()}.csproj"
      target="Rebuild" verbosity="Minimal">
   <property name="Configuration" value="Release" />
   <property name="OutDir" value="../../${dir.build}/${project::get-name()}/bin/" />
   <property name="OutputPath" value="../../${dir.build}/${project::get-name()}/bin/" />
</msbuild>

Then your case of _PublishedWebsites should clear up in no time. (Alternatively you could set the Disable_CopyWebApplication property, but I'm attempting to get our NANT builds as close to our VS builds as possible.) As an addendum, once I knew what I was looking for I found quite a bit of confusion related to OutputPut, including a blog post from Mark Needham who also ran into problems related to OutputPath when tweaking OutDir and who turned up this nugget from MSDN:

OutputPath: This property is typically specified in the project file and resembles OutDir. OutputPath has been deprecated and OutDir should be used instead whenever possible.

At this point I stopped caring and reversed out of the rabbit hole.

Tags: ASP, NAnt

Comments (0)

Creating Custom Tasks in NAnt

Posted by Tom on 08/07/2010 21:16:07

If you want to get custom functionality into NAnt you have a couple of options. One is the script task, which enables you to embed C# directly into your build scripts. The other is to create a custom task. As promised, here's a brief rundown on what it takes to create your own NAnt tasks.

Your custom task will need to inherit from NAnt.Core.Task in the NAnt.Core assembly. The class must also be decorated with a TaskName attribute (this will be the tag name of the XML element you use when you call your task in the build script), and the method which actually does the work is ExecuteTask. The skeleton of your task will therefore look something like this:

   1:  using System;
   2:  using NAnt.Core;
   3:  using NAnt.Core.Attributes;
   4:   
   5:  [TaskName("custom")]
   6:  public class CustomTask : NAnt.Core.Task
   7:  {
   8:    protected override void ExecuteTask()
   9:    {
  10:        // Do stuff
  11:    }
  12:  }

It doesn't achieve a whole lot, but that's our starting point.

Passing values from NAnt to your task . . .

Arguments are passed into your task on the NAnt side of things using attributes on the corresponding XML element. These are mapped onto properties in your C# class using the TaskAttribute decorator.

   1:  [TaskAttribute("stringproperty", Required = true)]
   2:  [StringValidator(AllowEmpty = false)]
   3:  public string StringProperty
   4:  {
   5:      get;
   6:      set;
   7:  }

Validation is provided for string, booleans, ints and DateTimes via the StringValidator, BooleanValidator, Int32Validator and DateTimeValidator attributes. In addition to the expected type validation, the Int32Validator will allow you to specify a range, and the StringValidator will also match regexes. Anything else you'll have to deal with yourself.

. . . And back again

The Project property of the Task base class acts as an interface to the guts of the NAnt projects. Amoung other things it gives you both read and write access to the project's properties via a string-indexed dictionary.

Project.Properties["propertyname"]

This means that should you need to get anything back into NAnt from your task, you can fire it into one of the project properties and then toy with it back in your build script. While we're on the subject, the Project object also exposes the target .NET version, base directory, project name and host of other handy features. I can't seem to find any API docs anywhere, so either crack out Reflector and go assembly spelunking or just dive around in Intellisense for a bit. It's all fairly self-explanatory.

Logging

NAnt's Task object exposes a Log property which can be used to send text back to the parent NAnt process, which then dumps it to stdout. When you send anything to the log you also supply a log level (Info, Warning, Error, Debug or Verbose) which NAnt uses to decide whether or not to display the message. So if the task has it's 'verbose' property (again, part of the base Task class) set to true then anything set with a log level of Verbose will be displayed, but hidden otherwise. NAnt uses log4net for its logging so if you've used that before then all of this should look familiar.

~fin

Finally, here's the source of the Compares Favourably NAnt task, as an example of all of the above tied together.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using NAnt.Core;
   4:  using NAnt.Core.Attributes;
   5:  using ComparesFavourably.Engine;
   6:   
   7:  namespace ComparesFavourably.Nant
   8:  {
   9:      [TaskName("ComparesFavourably")]
  10:      public class Task : NAnt.Core.Task
  11:      {
  12:          #region Properties
  13:   
  14:          [TaskAttribute("settingsfile", Required = true)]
  15:          [StringValidator(AllowEmpty = false)]
  16:          public string SettingsFilename
  17:          {
  18:              get;
  19:              set;
  20:          }
  21:   
  22:          [TaskAttribute("failondifferences", Required = false)]
  23:          public bool FailOnDifferences
  24:          {
  25:              get;
  26:              set;
  27:          }
  28:   
  29:          [TaskAttribute("resultproperty", Required = false)]
  30:          [StringValidator(AllowEmpty = false)]
  31:          public string ResultPropertyName
  32:          {
  33:              get;
  34:              set;
  35:          }
  36:   
  37:          private int Result
  38:          {
  39:              get;
  40:              set;
  41:          }
  42:   
  43:          #endregion
  44:   
  45:          #region Constructors
  46:   
  47:          public Task()
  48:          {
  49:              FailOnDifferences = false;
  50:              Result = 0;
  51:          }
  52:   
  53:          #endregion
  54:   
  55:          #region Methods
  56:   
  57:          protected override void ExecuteTask()
  58:          {
  59:              Log(Level.Verbose, "Loading comparison settings");
  60:              ComparisonSettings settings = null;
  61:              try
  62:              {
  63:                  settings = ComparisonSettings.Load(SettingsFilename);
  64:              }
  65:              catch (System.IO.FileNotFoundException ex)
  66:              {
  67:                  string message = "Could not find Compares Favourably settings file: " + SettingsFilename;
  68:                  Log(Level.Error, message);
  69:                  throw new BuildException(message, ex);
  70:              }
  71:   
  72:              Log(Level.Verbose, "Running comparison");
  73:              Comparison compare = new Comparison(settings);
  74:              compare.Run();
  75:   
  76:              Log(Level.Verbose, "Analysing results");
  77:              List<string> different = new List<string>();
  78:              List<string> onlyInLeft = new List<string>();
  79:              List<string> onlyInRight = new List<string>();
  80:              foreach (ComparisonResultLine resultLine in compare.Result)
  81:              {
  82:                  switch (resultLine.Result)
  83:                  {
  84:                      case ComparisonResult.Different:
  85:                          different.Add(String.Format("{0}.{1}", resultLine.Type, resultLine.Name));
  86:                          break;
  87:                      case ComparisonResult.ExistsOnlyInLeft:
  88:                          onlyInLeft.Add(String.Format("{0}.{1}", resultLine.Type, resultLine.Name));
  89:                          break;
  90:                      case ComparisonResult.ExistsOnlyInRight:
  91:                          onlyInRight.Add(String.Format("{0}.{1}", resultLine.Type, resultLine.Name));
  92:                          break;
  93:                  }
  94:              }
  95:   
  96:              if (different.Count > 0)
  97:              {
  98:                  Log(Level.Info, "Different: " + String.Join(", ", different.ToArray()));
  99:                  Result += 1;
 100:              }
 101:              if (onlyInLeft.Count > 0)
 102:              {
 103:                  Log(Level.Info, "Only In Left: " + String.Join(", ", onlyInLeft.ToArray()));
 104:                  Result += 2;
 105:              }
 106:              if (onlyInRight.Count > 0)
 107:              {
 108:                  Log(Level.Info, "Only In Right: " + String.Join(", ", onlyInRight.ToArray()));
 109:                  Result += 4;
 110:              }
 111:   
 112:              // Store result in the property if one is specified
 113:              if (!String.IsNullOrEmpty(ResultPropertyName))
 114:                  Project.Properties[ResultPropertyName] = Result.ToString();
 115:   
 116:              // Throw a build error if that is the desired behaviour
 117:              if (different.Count + onlyInLeft.Count + onlyInRight.Count > 0)
 118:              {
 119:                  if (FailOnDifferences)
 120:                      throw new BuildException("Comparison showed differences in database schemas");
 121:              }
 122:          }
 123:   
 124:          #endregion
 125:      }
 126:  }

This has been a somewhat abridged version, but hopefully enough to get you started. As always, comments are welcome.

Tags: dotNet, NAnt

Comments (0)

Compares Favourably and NAnt, BFF

Posted by Tom on 23/06/2010 16:00:38

The original plan for Compares Favourably was to integrate it with our build and deployment process. This means we would be alerted to any differences in the database schema between dev and staging or staging and live at each release. In the end I got sidetracked into learning WinForms and to be honest I probably get more use out of the app because of it. However, my original problem still remains - if we do a release with an out-of-date database schema things break. Pages yellowscreen. Clients cry. Burning sulphur rains from the heavens. Around the world albino bulls are born and explode, killing all in the blast radius. It's pretty bad, and completely avoidable.

We use NAnt for builds, and since there is a command line front end for Compares Favourably I could just use <exec>, dump the output to a file and then parse it, but that seems a little bit pedestrian. Especially when the fine people responsible for NAnt make it so easy to write plugins.

As of revision 48 the ComparesFavourably NAnt task is in the SVN repository. In order to use it from a NAnt build script you will need to do one of the following:

  • Copy the ComparesFavourably.Nant, ComparesFavourably.Engine and Colourblind.Core assemblies into your NAnt /bin/ folder
  • Use NAnt's loadtasks task to load the plugin explicitly.

Once that's done you can run Compares Favourably from within NAnt using the following:

<ComparesFavourably 
    settingsfile="..\etc\SampleProjectFile.cmp" 
    resultproperty="result" 
    verbose="false" 
    failondifferences="false"
/>

Here's a brief explanation of the properties.

Property Type Required Default Description
settingsfile string true N/A This is a path to the Compares Favourably project file to use for the comparison
resultproperty string false N/A The name of the property to fill place results in. The result is a bitfield with bit 0 set if there are differences, bit 1 set if items exist in left but not right, and bit 2 set if items exist in right and not left. So a result of 5 shows that some items are different and others are in the destination database but not the source.
failondifferences bool false false Throw a build error there are any differences as a result of the comparison
verbose bool false false Use this property to output more detailed information as the task runs

I'll do a follow-up post later in the month about how to actually write custom NAnt tasks, but if you're impatient then you can get the code right this very moment in the Subversion repository. Instant gratification!

Questions? Bugs? Comments? Feature requests? I am available on the Internets.

Comments (0)

Adding Subversion Revision Numbers to your .NET NANT Builds

Posted by Tom on 02/12/2009 21:47:28

If you use .NET, Subversion and NAnt as your personal constellation of technologies then you've probably found yourself wondering if you can tie the lot together and have NAnt include your Subversion revision number in as the version number of your assembly. I've tried a few different approaches over the years and settled on this one (for now!)

The clever bit of the code (the part which actually grabs the revision number and gets it into NAnt) has been leeched wholesale from Jonathan Malek, with the added twist that it creates a C# file with the required attributes in it.

<?xml version="1.0"?>
<project name="SetVersion" default="all" basedir="../../">

    <target name="compile.setversion">

        <property name="svn.revision" value="0"/>
        <exec
            program="svn"
            commandline='log "${project.src.dir}" --xml --limit 1'
            output="${project.src.dir}\_revision.xml"
            failonerror="true" />
        <xmlpeek
            file="${project.src.dir}\_revision.xml"
            xpath="/log/logentry/@revision"
            property="svn.revision"
            failonerror="true" />
        
        <echo file="${project.src.dir}\Version.cs" append="false" message="
            [assembly: System.Reflection.AssemblyVersion("1.0.${svn.revision}")]
            [assembly: System.Reflection.AssemblyFileVersion("${svn.revision}")]" />
        
    </target>
</project>

That sits in its own file. Then, whenever we need to stuff a version number into an assembly we simply make sure we're including it:

<include buildfile="etc/nant/SetVersion.build" />

and call it just before our csc task:

<property name="project.src.dir" value="project/src" />
<call target="compile.setversion" />
<csc target="library" output="${out.dir}/${project.name}.dll">
    <sources>
        <include name="${project.src.dir}/**/*.cs" />
    </sources>
</csc>

Job done!

Tags: dotNet, NAnt, SVN

Comments (0)