Msbuild is microsoft build engine, and I showed some time ago how you can write a custom task to post in twitter the outcome of a build result. Now it is time to give a greater focus on how to write a good task.
Creating a Task is a simple matter of inheriting from the Task class but there are some key points you should keep in mind, first of all you should never throw an exception when executing the task, except for some specific system exception like OutOfMemoryException or StackOverflowException, then you should provide good logs to make simple for the user of your task to understand causes of failures.
Some of the tasks I miss most from transition to Nant to Msbuild is the couple XmlPeek and XmlPoke, used to read and manipulate xml files with xpath. Replicate them with LINQ to XML is a breeze. To actually build the task I first included all the logic in a different class.
With such an arrangement I can easily test my XML logic
[TestMethod] public void TestXmlPeek() { XElement element = XElement.Parse("<root>value</root>"); String value = XmlFunctions.Peek( element, "/"); Assert.AreEqual("<root>value</root>", value); }
After you have carefully tested your logic function it is time to test logic of the task, here is the full code of the task.
public class XmlPeek : Task { [Required] public String XPath { get; set; } [Required] public String FilePath { get; set; } [Required] public Boolean FailIfError { get; set; } [Output] public String Value { get; set; } public override bool Execute() { try { if (!File.Exists(FilePath)) { Log.LogError("The file {0} cannot be found", FilePath); return !FailIfError; } //can load the file XElement element = XElement.Load(FilePath); String foundValue = XmlFunctions.Peek(element, XPath); if (foundValue == null) { Log.LogError("No node found with XPath={0}.", XPath); return !FailIfError; } Log.LogMessage("Found node with XPath={0}", XPath); Value = foundValue; return true; } catch (OutOfMemoryException ex) { throw; } catch (StackOverflowException ex) { throw; } catch (Exception ex) { Log.LogErrorFromException(ex); return false; } } }
I enclosed the main body of the action in a try catch, but I need to rethrow exceptions that really cannot be catched. Then for every other exception I simply log it and then return false to the caller. For the main path of the action you should carefully validate input parameters, and give detailed error with the Log property from Task base class. Since good logs are really important, you should test them carefully.
public void TestNotFailDefault()
{
XmlPeek sut = new XmlPeek() {FilePath="notExists"};
sut.BuildEngine = MockRepository.GenerateStub<IBuildEngine>();
Assert.IsTrue(sut.Execute());
sut.BuildEngine.AssertWasCalled(
be => be.LogErrorEvent(null),
e => e.IgnoreArguments().Repeat.Once());
}
Thanks to Rhino Mock I create an implementation of IBuildEngine to pass to my action to simulate the msbuild execution environment, then I call my task with a file name that does not exists, and verify that the LogErrorEvent was called in the BuildEngine.
Alk.
Tags: Msbuild
Tags: Msbuild






August 2nd, 2010 at 10:15 pm
Why should you never throw an exception from a task, and why are OutOfMemory and StackOverflow exempt?
Thanks,
Ben
August 3rd, 2010 at 10:00 am
Because the task is a sort of plugin, and it should not throw exception to the core engine. The OutOfMemory and StackOverflow exception cannot be swallowed, so they absolutely need to be rethrown.
more detail in this book. (http://www.amazon.com/CLR-Via-.....0735621632)
alk.
August 3rd, 2010 at 1:42 pm
Thanks – that’s good to know.
I was originally searching for reasons why you should/would use Log.LogErrorFromException() rather than throwing as normal.
That must be the reason. Thanks.