In this post, I’ll show how to build an MsBuild (msdn2) task will automatically generate XSD schemas for your custom tasks. The post is separated in two sections: if you are interested to see how the task works, continue on reading. If you don’t care and want to get straight down to the beef, scroll down until you see TaskSchema reference.
MsBuild is the new .Net build system. It is based on XML files containing the projects, targets and tasks (see also Ant, NAnt). The framework comes with a set of tasks that allows easy compilation of solutions or projects and doing a couple more actions. Of course, everybody has different needs and you will probably end up writing your own specialized task to solve your problems.
When editing a MsBuild script, it is *very* convenient to take advantage of the “built-in intellisense” of XML by attaching it to the MsBuild schema file. You can do this in VS2005 in two mouse clicks (see below How to setup intellisense for msbuild). Unfortunately, this is not true for your custom tasks where you need to create yourself a XSD schema.
I’ll assume you have a basic knowledge of custom msbuild tasks in the following.
Note that NAnt has had this feature (nantschema) for a while now.
Before diving into the coding details, let’s see what our “final products” looks like. Let’s build a simple task to use it as our example during the article: the sleep tasks makes msbuild sleep x seconds (note that this implementation is very very poor):
using System;using System.Threading;using Microsoft.Build.Framework;using Microsoft.Build.Utilities;namespace Foo{ public sealed Sleep : Task { private int seconds; [Required] public int Seconds { get { return this.seconds;} set { this.seconds = value;} } public override bool Execute() { this.Log.LogMessage(“Sleeping {0} seconds”, this.Seconds); Thread.Sleep(this.Seconds * 1000); } }}
The sleep task has a single (required) parameter “seconds”. We expect the schema for this task to look as follows:
<
There’s a couple things to say about this schema:
Now that we know kind of output we are looking for, let’s see what “ingredients” we need.
Here’s a little summary of tools and techniques that we will use to generate the schemas:
The TaskSchema has the following properties (it actually has more properties but I’ll skip them for simplicity):
[Required]ITaskItem[] Assemblies {get;set}[Output]ITaskItem[] Schemas {get;}
Assemblies is a list of assemblies containing the tasks to “schematize”. Schemas is an output parameter that will contain the path of each schema, as we will generate one schema per assembly.
This is the high level “factored” Execute method (almost pseudo-code):
CreateAndInitializeSchema(); // (add namespace, includes, etc...)foreach(string assemblyName in Assemblies){ // load error handling comes here Assembly assembly = LoadAssembly(assemblyName); // iterating exported types foreach(Type type in assembly.GetExportedTypes()) { //if (type is not a task) continue; if(!typeof(ITask).IsAssignableFrom(type)) continue; // create a new schema element and name it after the type // create a simpleType + extension XmlSchemaElement element = CreateElement(type); foreach(PropertyInfo property in type.GetProperties()) { // if (property is defined in some base class) continue; if (property.DeclaringType != type) continue; // create schema attribute and name it after property name // create a simpleType that XmlSchemaAttribute attribute = CreateAttribute(property); // add attribute to current element AddAttribute(element, attribute); }}// saving to diskSaveSchema(schema);
There are a lot of interesting details to go through to implement this pseudo-code. I won’t go through all of them. I will rather focus on some parts:
An enumeration can be represented in XSD by simple type restriction. For example,
[C#]
public enum HelloWorld{ Hello, World}
[XSD]
This is a well know (and very handy trick) that add a generic type parameter to a method to “strongly type” it.
T GetAttribute<T>(ICustomAttributeProvider t) where T : Attribute{ object[] attributes = t.GetCustomAttributes(typeof(T), true); if (attributes != null && attributes.Length > 0) return (T)attributes[0]; else return null;}
Adding documention to the schema is straightforward, it is a matter of adding a annotation containing a document element:
The real trouble is to find what kind of documentation we are going to put there. We cannot rely solely on Reflection for this task because MsBuild custom attributes does not store any “documentation” data. Therefore, the best source of documentation is the XML documentation that is generated by the C# compiler (don’t forget to turn it on in Projects -> Properties -> Build). By default, the compiler dumps that file along side of the assembly so it is easy to find. Here’s a sample of how that file looks like:
<?
That is sweet. We can access the summary of each member very easily using XPath. For example:
//member[@name=”T:taskType.FullName”]/summary
This expression will return me the summary of the taskType type. Similar constructs can be mode for the property summary.
A Task that generates the XSD schemas for assemblies containing tasks.
Download: See Task on project distributor. Compiled against .Net 2.0 Beta 2.
There are a couple more options. Run TaskSchema to have the full schema!
This little msbuild project applies TaskSchema to itself (it is part of Churn.Tasks.dll):
[Output]
Microsoft (R) Build Engine Version 2.0.50215.44[Microsoft .NET Framework, Version 2.0.50215.44]Copyright (C) Microsoft Corporation 2005. All rights reserved.Build started 5/26/2005 8:19:51 PM.__________________________________________________Project "D:\Projects\Churn.NET\Churn.Tasks\taskschema.self.xml" (default targets):Target CreateSchema:Analysing bin\Debug\Churn.Tasks.dllFound documentation file at Debug\Churn.Tasks.xmlAnalyzing Churn.Tasks.TaskSchemaCreating Schema bin\Schemas\Churn.Tasks.Tasks.xsdCreate Task list bin\Schemas\Churn.Tasks.TasksBuild succeeded.0 Warning(s)0 Error(s)Time Elapsed 00:00:00.93
[Schema: TaskFoo.xsd]
Execute the sample and see for yourself!
Page rendered at Thursday, July 24, 2008 5:15:24 AM UTC
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.