MSBuild Tasks, code generation and a tangent I've been stuck on for a while...

I'm still quite distracted by MSBuild. I'm also a big fan of code generation. Any time you can combine a few passions you should. If I could find a way to mix in some seafood, a Willamette Valley (Oregon) wine and a Dave Matthew's Band album believe me, I would.

As I noted in an earlier post I needed a simple task that would validate that a project contained a file link to a specific file (BldVer.cs). Soon after I needed several other tasks. One needed many (over 100) potential properties. Certainly writing this all by hand was going to be a tedious process. I started looking at ways I could take the common tasks, like the property accessors and generic validation, and generate that all from a simple XML file.

In the next several posts I'll be walking through a simple implementation of that idea.

This first post will introduce you to the intended input and output.

The input is an XML file. It contains a collection (Tasks) of one or more Task objects. Each Task has several attributes and one or more Property objects. Each Property has several possible attributes. Since we're using XML the easiest way to describe and validate that XML is an XSD. The following is the schema for the input file format.

<?xml version="1.0" encoding="utf-8"?>

<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="https://www.w3.org/2001/XMLSchema">

    <xs:element name="Tasks">

        <xs:complexType>

            <xs:sequence>

                <xs:element maxOccurs="unbounded" name="Task">

                    <xs:complexType>

                        <xs:sequence>

                            <xs:element maxOccurs="unbounded" name="Property">

                                <xs:complexType>

                                    <xs:attribute name="Name" type="xs:string" use="required" />

                                    <xs:attribute name="Required" type="xs:boolean" use="optional" default="false"/>

                                    <xs:attribute name="Type" type="PropertyType" use="required" />

                                    <xs:attribute name="IsArray" type="xs:boolean" use="optional" default="false" />

                                    <xs:attribute name="OneOf" type="xs:string" use="optional" />

                                    <xs:attribute name="Range" type="xs:string" use="optional" />

                                    <xs:attribute name="Default" type="xs:string" use="optional" />

                                </xs:complexType>

                            </xs:element>

                        </xs:sequence>

                        <xs:attribute name="Namespace" type="xs:string" use="required"/>

                        <xs:attribute name="Class" type="xs:string" use="required" />

                        <xs:attribute name="BaseClass" type="xs:string" use="optional" />

                    </xs:complexType>

                </xs:element>

            </xs:sequence>

        </xs:complexType>

    </xs:element>

    <xs:simpleType name="PropertyType">

            <xs:restriction base="xs:string">

                  <xs:enumeration value="ITaskItem" />

                  <xs:enumeration value="System.Boolean" />

                  <xs:enumeration value="System.Byte" />

                  <xs:enumeration value="System.Char" />

                  <xs:enumeration value="System.DateTime" />

                  <xs:enumeration value="System.Decimal" />

                  <xs:enumeration value="System.Double" />

                  <xs:enumeration value="System.Int16" />

                  <xs:enumeration value="System.Int32" />

                  <xs:enumeration value="System.Int64" />

                  <xs:enumeration value="System.SByte" />

                  <xs:enumeration value="System.Single" />

                  <xs:enumeration value="System.String" />

                  <xs:enumeration value="System.UInt16" />

                  <xs:enumeration value="System.UInt32" />

                  <xs:enumeration value="System.UInt64" />

            </xs:restriction>

      </xs:simpleType>

</xs:schema>

As you can see it's quite straight forward. Each Task can have a Namespace, Class and BaseClass and each Property can have a Name, Required, Type, IsArray, OneOf, Range and Default attribute.

To make this clearer let's look at a simple example:

<?xml version="1.0" encoding="utf-8" ?>

<Tasks>

      <Task Namespace="SampleNamespace" Class="SampleClass">

            <Property Name="Property1" Type="System.String"/>

      </Task>

</Tasks>

Should generate a the following:

// Auto-generated Task: 3/4/2004 2:13:13 PM

namespace SampleNamespace

{

    using System;

    using Microsoft.Build.Framework;

    using Microsoft.Build.Utilities;   

   

    public abstract class SampleClass : Microsoft.Build.Utilities.Task

    {

       

        #region Property1

        private string m_Property1;

       

        public virtual string Property1

        {

            get

            {

                return this.m_Property1;

            }

            set

            {

                this.m_Property1 = value;

            }

        }

        #endregion

    }

}

Now we have a foundation on which to work. The steps it took to get from the concept to here will be the next topic of discussion ... tomorrow (or when I get a chance).