- | rssFeed | My book on MSBuild and Team Build | Archives and Categories Friday, 22 January 2010

MSBuild 4.0: Inline Tasks Part 2

This post contains based on .NET 4.0 Beta 2 and Visual Studio 2010 Beta 2 which may change.

The other day I wrote my first post on Inline Tasks in MSBuild 4.0, this post will add onto that topic. In the previous post we covered some basics, but there were a lot that was skipped. Last time we demonstrated how to pass parameters but we never declared what type those parameters were. If you declare a parameter and leave off the type, then it will be declared as a string. If you need to declare parameters of other types then you need to use the ParameterType attribute on the parameter. You have to pass in the full name of the type to be used. For example if you need to declare an int you must use System.Int32, not int and not Int32 but System.Int32. It would be good if they supported aliases like int, string, long, etc but right now they don't. Below you'll find a new inline task which can be used to perform a substring. I've placed this in a file named IT-Substring-01.proj.

<!--

Sample Demonstrates Inline Tasks

© 2010 Sayed Ibrahim Hashimi

-->

<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

 

  <UsingTask

    TaskName="Substring"

    TaskFactory="CodeTaskFactory"

    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >

    <ParameterGroup>

      <Input Required="true" />

      <StartIndex Required="true" ParameterType="System.Int32" />

      <Length ParameterType="System.Int32" />

      <Result Output="true" />

    </ParameterGroup>

    <Task>

      <Code Type="Fragment" Language="cs">

        <![CDATA[

        if (Length > 0)

        {

            Result = Input.Substring(StartIndex, Length);

        }

        else

        {

            Result = Input.Substring(StartIndex);

        }

        ]]>

      </Code>

    </Task>

  </UsingTask>

 

  <Target Name="Demo">

    <Substring StartIndex="8" Input="Demo of Inline Tasks">

      <Output PropertyName="taskValue" TaskParameter="Result"/>

    </Substring>

    <Message Text="Value from task: $(taskValue)" />

   

    <Substring StartIndex="0" Length="4" Input="Demo of Inline Tasks">

      <Output PropertyName="taskValue" TaskParameter="Result"/>

    </Substring>

    <Message Text="Value from task: $(taskValue)" />

   

  </Target>

</Project>

In the task above I have declared 4 parameters, from those two have the type specified to be int. If we execute the Demo target here is the result.

From the above image you can see that the task performs the actions requested. One thing to make a mental note of above is my usage of a CDATA tag to wrap the definition of the task. I would suggest that you do so for all inline tasks that you write.

The types supported for parameters on inline types are the same for normal types. For a detailed account of that see my book, but as a general guideline it accepts string, primitive types, ITaskItem, and arrays of all three.

Let's see how we can use an array in an inline task. I created a simple task which will create a list of Guids and place them into an array and return the result back to the calling MSBuild script, the file I placed this is in named IT-CreateGuid-02.proj. The contents of that file are shown in the snippet below.

<!--

Sample Demonstrates Inline Tasks

© 2010 Sayed Ibrahim Hashimi

-->

<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

 

  <UsingTask

    TaskName="CreateGuid02"

    TaskFactory="CodeTaskFactory"

    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >

    <ParameterGroup>

      <NumToCreate ParameterType="System.Int32" Required="true" />

      <Guids ParameterType="System.String[]" Output="true" />

    </ParameterGroup>

    <Task>

      <Code Type="Fragment" Language="cs">

        <![CDATA[

            List<string> guids = new List<string>();

            for (int i = 0; i < NumToCreate; i++)

            {

                guids.Add(Guid.NewGuid().ToString());

            }

            Guids = guids.ToArray();

        ]]>

      </Code>

    </Task>

  </UsingTask>

 

  <Target Name="Demo">

    <CreateGuid02 NumToCreate="1">

      <Output ItemName="Id01" TaskParameter="Guids" />

    </CreateGuid02>

    <Message Text="Id01: @(Id01)" Importance="high" />

 

    <CreateGuid02 NumToCreate="4">

      <Output ItemName="Id02" TaskParameter="Guids" />

    </CreateGuid02>

    <Message Text=" " Importance="high" />

    <Message Text="Id02: @(Id02)" Importance="high" />

  </Target>

 

</Project>

Above you can see that I declared the parameter Guids as System.String[]. I typically prefer to use generic lists instead of arrays so in my task definitions I will use a list and then just call ToArray when I assign the output parameter. Here is a nifty, but very simplistic task. Given an item list, it will filter them based on a Regular Expression passed in.

<!--

Sample Demonstrates Inline Tasks

© 2010 Sayed Ibrahim Hashimi

-->

<Project ToolsVersion="4.0" DefaultTargets="Demo" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

 

  <UsingTask

    TaskName="FilterList"

    TaskFactory="CodeTaskFactory"

    AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" >

    <ParameterGroup>

      <ListToFilter ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" />

      <Filter Required="true" />

      <FilteredList ParameterType="Microsoft.Build.Framework.ITaskItem[]" Output="true" />

    </ParameterGroup>

    <Task>

      <Using Namespace="System.Text.RegularExpressions" />

      <Code Type="Fragment" Language="cs">

        <![CDATA[

            var results = (from l in ListToFilter

                           where Regex.IsMatch(l.ItemSpec, Filter)

                           select l).ToList();

 

            FilteredList = results.ToArray();

        ]]>

      </Code>

    </Task>

  </UsingTask>

 

  <ItemGroup>

    <Source Include="src\01.cs" />

    <Source Include="src\02.cs" />

    <Source Include="test\test01.cs" />

    <Source Include="test\sub\test02.cs" />

    <Source Include="test\sub\test03.cs" />

    <Source Include="test\sub\sub2\test04.cs" />

  </ItemGroup>

 

  <Target Name="Demo">

    <FilterList ListToFilter="@(Source)" Filter="test">

      <Output ItemName="_filteredList" TaskParameter="FilteredList" />

    </FilterList>

    <Message Text="Filter: test. Results: @(_filteredList)" />

   

    <!-- Clear the list before calling again -->

    <ItemGroup>

      <_filteredList Remove="@(_filteredList)" />

    </ItemGroup>

 

    <Message Text="======" />

    <FilterList ListToFilter="@(Source)" Filter="sub\\">

      <Output ItemName="_filteredList" TaskParameter="FilteredList" />

    </FilterList>

    <Message Text="Filter: .\sub. Results: @(_filteredList)" />

  </Target>

</Project>

Here you see that you can you can perform LINQ queries inside of inline tasks without any additional setup. Here is the result of executing the Demo target on this script.

Another thing to notice is the Using element under the Task element. This injects a Using statement into the generated class for the given namespace. If you need to reference another assembly you can do this with the Reference element under the Task element.

 

Sayed Ibrahim Hashimi

inline task | msbuild | MSBuild 4.0 | Visual Studio 2010 Friday, 22 January 2010 04:01:46 (GMT Standard Time, UTC+00:00)  #     |