MSBuild–Workflow via items

 

In an earlier installment, I covered using MSBuild item metadata to serve as the high-level definition (name) of behaviour. This post will continue, where the previous left off, and show how the continuous overwriting of items can be used to simulate conditional method invocation inside a MSBuild program.

Now to the code:

 <Project
    xmlns="https://schemas.microsoft.com/developer/msbuild/2003"
    DefaultTargets="Intro">
 
  <ItemDefinitionGroup>
    <MyTask>
      <CallTarget>Default</CallTarget>
    </MyTask>
  </ItemDefinitionGroup>
 
  <Target Name="DefineTasks">
    <Message Text="Insert Target DefineTasks ... "/>
    <Message Text="Defining MyTask items ..."/>
    <ItemGroup>
      <MyTask Include="Task1Item">
        <CallTarget>Task1</CallTarget>
      </MyTask>
    </ItemGroup>
    <Message Text="MyTask: @(MyTask)"/>
  </Target>
 
  <Target Name="Intro" DependsOnTargets="DefineTasks">
    <Message Text="Inside Target Intro ... " Importance="normal"/>
    <CallTarget Targets="@(MyTask->'%(CallTarget)')" />
    <CallTarget Targets="@(MyTask->'%(CallTarget)')" />
  </Target>
 
  <Target Name="Default">
    <Message Text="Inside Target Default ... "/>
  </Target>
 
  <Target Name="Task1" >
    <Message Text="Inside Target Task1 ... "/>
    <Message Text="Before @(MyTask) | %(MyTask.CallTarget)"/>
    <ItemGroup>
      <MyTask Condition=" '%(CallTarget)' == 'Task1' ">
        <CallTarget>Special</CallTarget>
      </MyTask>
    </ItemGroup>
    <Message Text="After @(MyTask) | %(MyTask.CallTarget)"/>
  </Target>
 
  <Target Name="Special">
    <Message Text="Inside Target Special ..."/>
  </Target>
 
</Project>

What is done here, is the following:

  1. A ItemDefinitionGroup is created having a metadata property with default value Default.
  2. Now, one MSBuild item Task1Item (of type MyTask) is created having CallTarget of Task1.
  3. The MSBuild target Intro runs.
  4. For all items of type MyTask, of which there is only one Task1Item, MSBuild Target1 is invoked. That is, is is invoked for Task1Item.
  5. Inside Target1, we want to mark Task1Item as being processed by Target1  - being ready for the next processing stage, i.e. Special (MSBuild target Special).
  6. This is achieved by overriding all items of MyTask having CallTarget = Task1. The CallTarget value is set to Special.
  7. Upon return from Task1, i.e. the processing stage Task1 has finished for all designated items of type MyTask, further processing of MyTask items is executed via invoking the MSBuild task CallTarget once more.

As can be seen in the picture below, the execution flow is going through a series of MSBuild targets, which can be used to implement stages within an item-processing pipeline where the implementation of local and global behaviour can be separated nicely. For example, imagine each MSBuild target must comply with a signature, requiring BeforeTargets and AfterTargets. This can be used to implement handover between pipelined item processing stages in your complete MSBuild workflow.

1