December 5, 2006

NAnt doesn't suck, but MSBuild does it big time

I was building NAnt and MSBuild tasks for the nxslt tool last two days and the bottom line of my experience is "previously I thought NAnt sucks, but now I know NAnt is brilliant and it's MSBuild who sucks really big way".

My complaints about NAnt were that

  1. NAnt being .NET Ant clone somehow has different license - while Java Ant is under Apache License, NAnt is under GPL. Now that Sun GPL-ed Java it might sound no big deal, but I personally was in a situation when a project manager said no we won't use NAnt because it's GPL and we don't want such a component in our big bucks product.
  2. NAnt core dlls aren't signed. That in turn means I can't sign my assembly and so can't put it into GAC. Weird.

Really minor ones as I realize now. Besides - NAnt is brilliant. While MSBuild appears to be more rigid and limited. Apparently it's impossible to create MSBuild task that uses something more than just attributes. I mean in NAnt I have this:

<nxslt in="books.xml" style="books.xsl" out="out/params1.html">
  <parameters>
    <parameter name="param2" namespaceuri="foo ns" value="param2 value"/>
    <parameter name="param1" namespaceuri="" value="param1 value"/>
  </parameters>
</nxslt>

 MSBuild doesn't seem to be supporting such kind of tasks. MSBuild task only can have attributes, not children elements. It can have references to some global entities defined at the project level, such as properties and task items. At first I thought task items seem good candidates for holding XSLT parameters, because task items can have arbitrary metadata. And that's exactly how the Xslt task from the MSBuild Community Tasks Project passes XSLT parameters:

<ItemGroup>
  <MyXslFile Include="foo.xsl">
    <param>value</param>
  </MyXslFile>
</ItemGroup>
            
<Target Name="report" >
  <Xslt Inputs="@(XmlFiles)"
    Xsl="@(MyXslFile)" 
    Output="$(testDir)\Report.html" />
</Target>

 Parameters here get attached to an XSLT file item definition, which seems to be reasonable until you realize that you might want to run the same stylesheet with different parameters?

And what worse - above is actually plain wrong because it only provides "name=value" for a parameter, while in XSLT a parameter name is QName, i.e. XSLT parameter is a "{namespace URI}localname=value". And item metadata happens to be limited only to plain name=value. Metadata element can't have attributes or namespace prefix or be in a namespace... It's clear that MSBuild task item is a bad place to define XSLT parameters for my task.

Last option I tried and on which I settled down is defining XSLT task parameters as global MSBuild project properties. Thanks God at least properties can have arbitrary XML substructure! Here is how it looks:

<PropertyGroup>
  <XsltParameters>
    <Parameter Name="param1" Value="value111"/>
    <Parameter Name="param2" NamespaceUri="foo ns" Value="value222"/>
  </XsltParameters>
</PropertyGroup>

<Target Name="transform">
  <Nxslt In="books.xml" Style="books.xsl" Out="Out/params1.html" 
    Parameters="$(XsltParameters)"/>
</Target>

 And here is how you implement it: create a string property "Parameters" in your task class. At the task execution time this property will receive <XsltParameters> element content (as a string!). Parse it with XmlReader and you are done. Beware - it's XML fragment, so parse it as such (ConformanceLevel.Fragment).

Two problems with this approach - it makes me to define parameters globally, not locally (as in NAnt) - hence if I have several transformations in one project I should carefully watch out which parameters are for which transformation. Second - XML content as a string??? Otherwise it's good enough.

Tomorrow I'm going to finish documenting the nxslt NAnt/MSBuild task and release it.

...