Let's pick up where we left off.
If you go to the MSBuild wiki, you'll see that the topic "How do I write a task?" is still blank. There is an MSDN article (in a 3 part series) that explains this, so I'll start from another perspective: I have some existing NAnt tasks and I want to use these in MSBuild.
Let's start with a simple one: Version (actually, it's a nantcontrib task, but that's the same, right?). In NAnt this task reads a version number from a file, increments it and uses it as a NAnt property.
Change the using statements
using
NAnt.Core;
using NAnt.Core.Attributes;
using NAnt.Core.Util;
To their MSBuild equivalents:
using
Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
Also, don't forget to set the namespace to something more appropriate.
Next, you should rename the task not to include the 'Task' suffix. In NAnt, the taskname you use in the buildfile is set with a custom attribute on the class [TaskName("name")], but MSBuild uses the classname. The good way to do this imo, is to leave off the 'Task' suffix, as this is the way the standard MSBuild tasks are named.
You will see something like the above with the public properties exposed by the task. In both NAnt and MSBuild, you can set/get these in xml. NAnt uses the technique of marking these properties wit the [TaskAttribute("name")] custom attribute, and MSBuild just uses the correct name of the property. So remove the attributes on the properties.
On some properties, in the NAnt version you will see the attribute [StringValidator(AllowEmpty=false)]. Replace this with the MSBuild [Required] attribute.
NAnt also contains a package called "StringUtils". Unless you want to re-use this, you're going to have to remove any references to this package, e.g.
StringUtils.ConvertEmptyToNull(value)
becomes
(string.IsNullOrEmpty(value)) ? null : value;
Also, the BuildException class is NAnt-specific. Replace these with generic Exceptions for the time being.
Next we come to the body of the implementation. Replace the NAnt ExecuteTask() method with its MSBuild Execute() equivalent. The first difference you'll notice is that the MSBuild version returns a boolean value, true if the task succeeded, so you'll need a mechanism (combined with try/catch) that determines the outcome.
Replace the logging mechanism with the MSBuild Log class:
Log(Level.Info, "Build number '{0}'.", buildNumber);
becomes
Log.LogMessage(BuildEventImportance.Low, "Build number '{0}'.", _buildNumber);
The rest of the implementation is the same, the compiler will help you with any NAnt-specific things (mostly BuildExceptions).
When you look at the code that comes with this post, you'll see that I added a method called "WriteVersionInfoFile" and that this is executed if the corresponding property is set to true in the buildfile. This is used to create a VersionInfo.cs file that you can use in your solution to represent the version information for the assemblies. If you want all assemblies in a solution to have the same version number, you need to remove all AssemblyVersion attributes from the AssemblyInfo.cs files, and put this information in a solution file called "VersionInfo.cs". You can then add this file as a link to all projects in the solution (not as file, because then it is copied) and it will be used to set the version number for the assemblies.
A quick recap:
1. replace the "using" statements
2. remove the NAnt [TaskXXX] attributes and name the class and its properties correctly
3. replace the NAnt [StringValidator(XXX)] attributes with MSBuild or other equivalents
4. solve the StringUtils issue (either remove it or import it)
5. remove all references to BuildException
6. replace the logging mechanism
You can find the source here.
You can find the original 'VersionTask.cs' file in the nantcontrib source redistributable. I used the one from v0.84, but the 0.85RC1 version is the same.
Now for some licensing issues. I don't know if the above was entirely legal. I haven't removed the header stating that this class is 'free software under the GPL', but I've changed the name and the NAnt (c) statement. If this is in some way wrong, let me know.
[Edit] I forgot to mention how to start using this task. See the previous post on MSBuild for instructions on how to make this task available in MSBuild buildfiles. An example of the task in action is below:
<Version
BuildType="increment"
RevisionType="increment"
Path="$(BuildNumberFile)"
WriteVersionInfo="true"
VersionInfoPath="$(VersionInfoFile)">
<Output TaskParameter="BuildNumber" PropertyName="BuildNumber"/>
</Version>