Welcome to Atalasoft Community Sign in | Join | Help

If you build it, They will come

Thoughts, props and rants in the world of Build Czar at Atalasoft
Extending NAnt with your own types

Last time I talked about how to nest existing elements in your custom NAnt tasks.  Now that you've had a good look at that, and Jacob's custom task, there's only one other thing to cover in this subject: a new type.  

Types end up looking a lot like a new task, but throwing this all together should be a lot of fun.

Last time, I explained how I envisioned this working; a neat little task that I would give a FileSet to, and a regular expression plus a replacement string on match.  Considering this is a build process, you're going to be manipulating a lot of files with the same regex, that we have covered.  But what if I want to apply more than one regex to each file that's processed; I'd like to pass a collection of regex elements to my AtalaFileRegex task along with my FileSet.  For that you need the BuildElementCollection decorator on one of your field types, like this:

         /// <summary>
        /// A collection of regex/replace pairs that will be used on the file
        /// </summary>
        [BuildElementCollection("regexs", "regex", Required = true)]
        [StringValidator(AllowEmpty = false)]
        public AtalaRegexCollection Regexs
        {
            get { return _regexs; }
        }

As Jacob explained, we're requiring this field exist in our task, and that it cannot be empty.  But what about those first two arguments to BuildElementCollection?  Those define the outer element's name, and the inner elements name, respectively.  This will make your task have the following form:

<task someattribute=something someotherattribute=somethingelsle>

  <regexs>

    <regex ...>

    <regex ...>

  </regexs>

  <fileset>

   <include ...>

   <exclude ...>

  </fileset>

</task> 

 

Now, all we're missing is the two types we said we're going to use in the BuildElementCollection.

First, we need our regex type so that we can work with it in our collection.  For a type, you decorate the class itself, extend the Element type, and then decorate elements within the class as we've done before:

using System;
using NAnt.Core;
using NAnt.Core.Attributes;

namespace Atalasoft.NAnt.Types
{
    /// <summary>
    /// Represents an regex pattern
    /// </summary>
    [ElementName("regex")]
    [Serializable()]
    public class AtalaRegex : Element
    {

        #region Public Instance Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="AtalaRegex" />
        /// class.
        /// </summary>
        public AtalaRegex()
        {
        }

        #endregion Public Instance Constructors

I threw in the empty constructor since it's uninteresting.

Cool, now, we said we want the regex type to accept both a matching pattern and a replacement string, so let's get a couple of instance fields going on to hold those:

        #region Private Instance Fields

        private string _value;
        private string _replace;

        #endregion Private Instance Fields

 And finally, our getters and setters, properly decorated:

        #region Public Instance Properties

        /// <summary>
        /// The actual regex string
        /// </summary>
        [TaskAttribute("value")]
        [StringValidator(AllowEmpty = false)]
        public string Value
        {
            get { return _value; }
            set { _value = value; }
        }

        /// <summary>
        /// The replacement string on match
        /// </summary>
        [TaskAttribute("replace")]
        [StringValidator(AllowEmpty = true)]
        public string Replace
        {
            get { return _replace; }
            set { _replace = value; }
        }
        #endregion Public Instance Properties
    }
}

Notice on each, we've named the attribute (I called the regex string 'value' because I didn't want an element like <regex regex...).  Also note, the replace string can be empty.

That's all there is to making your own, simple type.  We'll move on to the more "complicated" part of the task at hand.

Second, realize that we told NAnt it's a collection, so when we make our regexs type it's going to need to extend the CollectionBase abstract class.  You need nothing from the NAnt core.  Let's set up the class, and the default constructors:

using System;
using System.Collections;

namespace Atalasoft.NAnt.Types
{
    /// <summary>
    /// Contains a strongly typed collection of <see cref="AtalaRegex" /> objects.
    /// </summary>
    [Serializable()]
    public class AtalaRegexCollection : CollectionBase
    {

        #region Public Instance Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="AtalaRegexCollection"/> class.
        /// </summary>
        public AtalaRegexCollection()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AtalaRegexCollection"/> class
        /// with the specified <see cref="AtalaRegexCollection"/> instance.
        /// </summary>
        public AtalaRegexCollection(AtalaRegexCollection value)
        {
            AddRange(value);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AtalaRegexCollection"/> class
        /// with the specified array of <see cref="AtalaRegex"/> instances.
        /// </summary>
        public AtalaRegexCollection(AtalaRegex[] value)
        {
            AddRange(value);
        }

        #endregion Public Instance Constructors

From here, there's a lot of things to implement from the CollectionsBase class, and an enumerator.  Suffice it to say, you're going to end up filling in a lot of stubs for insert, add, copyto, remove, etc.  There's not much to cover here, it's a stong-typed collection.

Next time we'll put these things to work in our task.

 

Posted Thursday, May 08, 2008 6:51 AM by dterrell | 1 Comments

Filed under: , ,

Writing NAnt tasks with nested types

If you follow the Atalasoft Blogs by whatever means, and you're reading this, it's quite possible you've also read Jacob's blog post about extending NAnt.  If you haven't already, go check it out since he's covered a huge amount of ground related to what I'm going to show in this and subsequent posts.  In fact, I'll assume you've either read his post, or have some general idea about extending NAnt.

First, let me give some background on why I did this task.

At Atalasoft, our builds (built by Jacob) can run in parallel, but our .NET 2.0 and 64-bit builds cannot.  Before our installer is created we want the .NET 2.0 and 64-bit assemblies to have the same version numbers.  In order for that to happen, we introduced a dependency on the .NET 2.0 build by the 64-bit build (the version number is a guaranteed increasing, randomly generated number that, in the case of the 64-bit build, is obtained from the built .NET 2.0 assemblies).  For continuous integration, the version numbering is internal, and not as important to our engineering department.  With that in mind, it should be easy to break the dependency just for a nightly build, but how?  Enter ChangeSetId, LastChangeLabeller, and a special Atalasoft NAnt task called AtalaFileRegex (the "Atala" prefix is somewhat of a running joke internally, but helps future build engineers realize it's not a built-in task for NAnt, yet).  The ChangeSetId and LastChangeLabeller topics will be covered in another, upcoming post.

Now, you ask, why the FileRegex task when we're dealing with version numbers?  Ah, well, assembly version numbers are gotten a few different ways, and I wanted to alleviate a pain in our current build setup (preceding Jacob) where we use no less than 3 different approaches for updating version numbers for 3 different file types (AssemblyInfo.cs/.cpp, and any .rc files).  Enter AtalaFileRegex!

I imagined a task that I could give a FileSet to, a regular expression to match, and a string to replace that match with.  It's a sharp (no pun intended) tool that you can easily break things with if you're not careful, but incredibly powerful if you are careful!

So we set out to make our task (this is where you read Jacob's blog).  And in our shiny new task, we'd like to have a FileSet.  Easy, peasy!  In your public instance properties, make a field that is the NAnt.Core.Types.FileSet, and add some getter/setter code, like the following:

        /// <summary>
        /// All the matching files in this set will be updated according
        /// to the given regex/replace
        /// </summary>
        [BuildElement("fileset")]
        [StringValidator(AllowEmpty = false)]
        public FileSet RegexFileSet
        {
            get { return _fileSet; }
            set { _fileSet = value; }
        }

Notice I use the BuildElement() decorator here, instead of the already familiar TaskAttribute() decorator.  This means that it's a nested element.  The way you'd write your NAnt task with this is

<taskname taskattribute=something taskattribute2=somethingelse>

      <fileset basedir="${Dir.Source}">
        <include name="**\AssemblyInfo.cs" />
        <include name="**\AssemblyInfo.cpp" />
      </fileset>

</taskname>

Sweet!  That was easy...

Next time I'll write a walk through on how to make your own type, and add that to your new task. 

Posted Wednesday, May 07, 2008 4:04 PM by dterrell | 2 Comments

Filed under: , ,

Update on another post

You may have seen my post in the 31 Apps in 31 Days blog.  If you haven't already seen the 31 Apps in 31 Days blog, definitely check it out!  There's already a week's worth of applications you can make using the Atalasoft family of tools in one way or another.

I got some requests to expand the functionality of my autorotater application, namely that it have some additional rotational functionality.  Sometimes you'll stumble on those pictures that were taken with an orientation 90 degrees to the right or left, or even upside-down.  Some viewers will read this orientation information and rotate the picture for you while viewing, but at the same time they don't update the orientation flag of the image.  As a result the image may continue to be rotated by other viewers, or in this case, it'll be rotated by the AtalaAutoRotater.

 To fix this, I've added two new buttons to the autorotater.  This functionality was pretty easy to add with these few steps (technical stuff here, skip to MultiSelect if you don't like techie stuff).

  1. Modify the selectedIndexChanged event handler to enable/disable the rotation buttons.  This is just an added visual to let users know they must select one or more images before rotating
  2. Add two buttons, and give them appropriate names btnRotLeft and btnRotRight.
  3. Add a wrapper function rotate_() that will gather a collection of thumbnails into a new ICollection<Thumbnail>() before calling rotate()
  4. Add a new enum that contains Left, Right, All
  5. Have the btnRotLeft and btnRotRight (modify btnRotate) call the wrapper with its direction
  6. Modify rotate() so that it accepts an ICollection<Thumbnail> and a Rotation enum.  Check, before the switch, whether it's rotating all images, or a selection and update the orientation int if it's a selection Left/Right.
  7. Let the rest of the code do the work!

 

MultiSelect

But wait!  You don't want your users having to select individual files and hit rotate buttons each time.  So we'll make it possible to select a whole set of images to rotate.  To select multiple images, users can either hold down the Ctrl key while selecting individual pictures, or the Shift key to select two endpoints of a range of photos.

For this functionality, the FolderThumbnailViewer needs to be updated as well.  This might be a bit confusing, because there are two spots where you need to change options to get multiselect working, both in the FolderThumbnailView properties matrix.  First, you have to enable MultiSelect, then you have the change the SelectionMode to MultiSelect from Single.  Once that's done, the app is ready to go!

 

New buttons!


To make this a bit easier, I've included the source in the installer this time so there's only one file to download.

Get the installer

Posted Wednesday, May 07, 2008 7:43 AM by dterrell | 0 Comments

CI Hurts!

I've only recently begun working as the Build Czar here at Atalasoft, and immediately there are pains.  The builds (from what I see) are really complicated.  True: building a very complicated product is going to be complicated; however, it's thought that I can work myself out of this job, making it everyone's responsibility not just one person.  How can I do this?

Currently, Atalasoft employs Cruise Control.net, CCTray, NUnit and NAnt to make our CI ship go.  Each piece of this puzzle is extraordinarily powerful, the problem is getting them to shine in the way they shine on their own.  If I put the pieces in the wrong places, I'm going to overextend one of these applications, and end up paying the price for sure.

Welcome to my blog!  For a while I'll be concentrating on overhauling an enterprise build process, the trials, tribulations, pains and glories therein.  At then end of this process I can only hope the title and content of my blog will change, as that signals one of two things:

  1. I accomplished what I set out to do, and the builds are no longer owned by me.  My Build Czar title will be retired, and the entire engineering team will work together to ensure the CI process continues.
  2. The builds break me.

Let's hope it's the former.

 

Dave Terrell 

Build Engineer 

Posted Thursday, April 03, 2008 2:19 PM by dterrell | 0 Comments

Filed under: