Click here to Skip to main content
Click here to Skip to main content

Breaking from NAnt loop

, 16 Feb 2012
Rate this:
Please Sign up or sign in to vote.
Custom NAnt loop task that allowas breaking out on a While condition.

Introduction

Any "decent" software company should use a Continuous Integration process and system. If yours does not — my condolences Wink | ;)

The Continuous Integration (CI) concept includes automated software build/test/deployment/release etc.

A very popular combination for CI tools is CruiseControl.NET — NAnt tandem. While CruiseControl.NET (CCNet) is the CI engine, providing all basic functionality, including the CCNet website, for many more specific tasks it calls NAnt to perform anything that is tricky and/or customized.

They call NAnt "BAT file on steroids."It is a powerful and flexible tool. But users keep asking for more.

Background

I was in bad need for the NANt loop task that would allow breaking from the loop on a specific condition. There are many cases when one might need it. In my specific case, NAnt had to loop through thousands of files in specified folder tree in search of the file with specific data. And even if it was lucky enough to find the needed data in, say, the 3rd file it examined, NAnt still had to loop through the rest of tons of files in idle cycles.

I Googled around for existing solutions and could not find any. Instead, I found that many more people were asking for the same functionality. Therefore I just grabbed the source code of NAnt 0.85 and created my own <foreachwhile> task by just subclassing the NAnt <foreach> task.

When using so popular a tool as NAnt, it is not nice to have your own heavily customized version. You might have problems upgrading your NAnt version, migrating your CI scripts, etc. Therefore I have humbly addressed the NAnt developer team, using the email distribution list I was able to find, asking if I may do this small contribution to the NAnt.Contrib library. No answer so far.

Meanwhile I would like to help those people who are looking for a solution for this problem.

Using the Code Properly

Proper usage of this code would be adding this custom task to the NAnt.Contrib library. Where it should stay until the NAnt dev team accepts the solution and includes it into NAnt.Core. Unfortunately I do not have the NAnt.Contrib source (it"™s not a problem to get it, the thing is I just did not bother). Besides I was doing it for my personal/test usage. Therefore it is in the NAnt.Core namespace.

Using the Code as Is

As I mentioned before, the task is in the NAnt.Core namespace. It is easy to fix — just download the NAnt.Contrib code from SourceForge: http://sourceforge.net/projects/nantcontrib/ and modify the namespace.

To use it "as is", in the NAnt.Core namespace, just include the provided LoopWhileTask.cs into the NAnt solution, rebuild NAnt.Core.dll, and replace the one in the NAnt installation directory with your patched version.

Now you can write the following script, for example:

<property name="flag" value="true" readonly="false"/>
<!""- this custom loop will break as soon as filename exactly 24 chars long is found: -->
<foreachwhile item="File" in="C:\Squish\Utils" property="filename" while="flag" >
<echo message="${filename}" />
<echo message="LEN: ${string::get-length(filename)}" />
<property name="flag" 
  value="${convert::to-boolean(string::get-length(filename) != 24)}" 
  readonly="false"/>
<echo message="flag = ${flag}" />
</foreachwhile>
<!-"" this core loop will not break; value of each property 
          of the task is evaluated on the task object creation -->
<foreach item="File" in="C:\Squish\Utils" property="filename" if="${flag}">
<echo message="${filename}" />
<property name="flag" value="false" readonly="false"/>
<echo message="flag = ${flag}" />
</foreach>
...

There are some limitations to the custom task though; they will be described in the Limitations section below.

Task Implementation

To keep it simple and to minimize the "disturbance" I have caused in NAnt.core, I have just inherited the existing NAnt LoopTask:

/// <summary>
/// Exactly the same as Loop task, but with additional "while" attribute
/// </summary>
[TaskName("foreachwhile")]
class LoopWhileTask : LoopTask
{ ... }

LoopTask has private instance fields:

private string _prop;
private string[] _props;
...

These fields are used in particular to remember initial values of properties. Private fields of the base class are not accessible in the derived LoopWhileTask class.

That leads to the Limitations section below.

protected override void ExecuteTask() of the base class was overridden in the derived class. It was a blunt copy-paste of the base method, minus saving/restoring _props, plus re-evaluating the boolean While condition in every loop:

foreach (FileInfo file in files)
{
    if (IsBreak()) break; // re-evaluation of the While condition
        DoWork(file.FullName);
}
...

I know and respect the style; I know it should be written like this:

if (IsBreak())
{
    break;
}
...

But to emphasize my simple addition, I made it into one line of code.

Here goes my evaluation function:

/// <summary>
/// Checks WhileCondition value
/// </summary>
/// <returns>true if WhileCondition evaluates to false</returns>
private bool IsBreak()
{
    bool isBreak = false;
    string value = Project.CurrentTarget.Properties[_while];
    try
    {
        isBreak = !Convert.ToBoolean(value);
    }
    catch (FormatException)
    {
        Log(Level.Error, "Unable to convert '{0}' to a Boolean.", value);
    }
    return isBreak;
}

Finally I had to brazenly copy-paste from the base class private void DoWorkOnFileLines(string filename) method, because it was not accessible in the derived class.

Limitations

Because of the simplistic while-condition evaluation function and missing saving/restoring property task attribute value, there are some limitations on our custom loop task. But in my point of view, they are not important at all. In my simplistic approach to coding, I would have avoided using these features whenever possible to keep the script simple, readable, and reliable.

So, the limitations are as many as two, and they go as follows:

  1. The value of the (single) task attribute property will not be restored after task execution. Therefore if you are using nested tasks, avoid using the same name for the property. Like in the script example above, if you are using property="filename" in the top task, use something like property="filename1" in the embedded task, and so on.
  2. While attribute should have the value of the boolean NAnt property name that we are using to break out from the loop, it cannot be an expression. We should use it like while="flag", and not like while="${flag}" or while="${convert::to-boolean(string::get-length(filename) != 24)}".

If someone would kindly point to me cases when these limitations are critical, we could think of how to bring these missing functionalities back to our code.

History

No history so far. Let us see if the topic will bring any attention and if it will require any continuation.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

G. Filkov

Canada Canada
No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 16 Feb 2012
Article Copyright 2012 by G. Filkov
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid