Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle errors while using target batching in msbuild?

Tags:

msbuild

I'm using batching for a target and I want to be able to do OnError cleanup that's specific to the iteration where the error occurred. Here's a completely genericized example:

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

<ItemGroup>
    <Example Include="Item1">
        <Color>Blue</Color>
    </Example>
    <Example Include="Item2">
        <Color>Red</Color>
    </Example>
</ItemGroup>

<Target Name="Build"
    Inputs="@(Example)"
    Outputs="%(Example.Color).txt">
    <Error Text="An error occurred during %(Example.Color)" />
    <OnError ExecuteTargets="HandleErrors" />
</Target>

<Target Name="HandleErrors">
    <Message Text="Do some cleanup about %(Example.Color)" Importance="high" />
</Target>

</Project>

The Build target fails when Color is Blue. But the HandleErrors Target runs twice, once for each Color. Is there a way to make it run only for the Color that was active during the failure?

like image 552
Jeff Youngstrom Avatar asked Jan 17 '23 22:01

Jeff Youngstrom


1 Answers

I think it's not clear what exactly is happening here. Before we get to your question let's take a look at what's happening, by modifying your proj file.

Below is a modified file, sample-no-error.proj, in which I removed the error portions and just added a Message task there.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Build">
  <ItemGroup>
      <Example Include="Item1">
          <Color>Blue</Color>
      </Example>
      <Example Include="Item2">
          <Color>Red</Color>
      </Example>
  </ItemGroup>

  <Target Name="Build"
      Inputs="@(Example)"
      Outputs="%(Example.Color).txt">

    <Message Text="Inside of Build, color: %(Example.Color)" />
  </Target>  
</Project>

When I build this from the command line the result is. enter image description here From this we can see that the target itself was executed two times. This is because on the target you have decorated it with Outputs=%(Example.Color).txt. When you use %(...) you start MSBuild batching. There are two kinds of batching; target batching, task batching. Target batching is where an entire target is executed per batch (this is what you have going on here). Task batching is where a task is executed per batch.

I've got another sample which is exactly what you have except that I added an additional Message statement inside of the Build target.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Build">
  <ItemGroup>
      <Example Include="Item1">
          <Color>Blue</Color>
      </Example>
      <Example Include="Item2">
          <Color>Red</Color>
      </Example>
  </ItemGroup>

  <Target Name="Build"
      Inputs="@(Example)"
      Outputs="%(Example.Color).txt">

    <Message Text="Inside of Build, color: %(Example.Color)" />

    <Error Text="An error occurred during %(Example.Color)" />
    <OnError ExecuteTargets="HandleErrors" />
  </Target>

  <Target Name="HandleErrors">
    <Message Text="Inside of the HandleErrors target" />
    <Message Text="Do some cleanup about %(Example.Color)" Importance="high" />
  </Target>
</Project>

When I build this the result is. enter image description here Based on this we can see that the Build target was executed once, and an error occurred so it wasn't executed the second time like it was in the previous example where there was no error. After that the HandleErrors target kicked in. Inside of that it encountered the element.

<Message Text="Inside of the HandleErrors target" />

And the message was sent to the loggers (note this was only invoked once). After that it came to.

<Message Text="Do some cleanup about %(Example.Color)" Importance="high" />

Now this produced two message statements. That is because task batching has started to kick in here. The task is invoked once per unique batch of %(Example.Color) which is Red and Blue. So we know that the target was only invoked once (where the original error occurred), but you are passing in the entire set of Example items to it.

If you want to know which value produced the error you'll have to keep track of that in your Build target. You can just place the current color in a property and then reference that in your HandleErrors target. For example take a look at sample-id-errors.proj below.

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Build">
  <ItemGroup>
      <Example Include="Item1">
          <Color>Blue</Color>
      </Example>
      <Example Include="Item2">
          <Color>Red</Color>
      </Example>
  </ItemGroup>

  <Target Name="Build"
      Inputs="@(Example)"
      Outputs="%(Example.Color).txt">
    <PropertyGroup>
      <_LastUsedColor>%(Example.Color)</_LastUsedColor>
    </PropertyGroup>

    <Message Text="Inside of Build, color: %(Example.Color)" />

    <Error Text="An error occurred during %(Example.Color)" />
    <OnError ExecuteTargets="HandleErrors" />
  </Target>

  <Target Name="HandleErrors">
    <Message Text="Inside of the HandleErrors target" />
    <Message Text="The color which caused the error was: $(_LastUsedColor)"/>
  </Target>
</Project>

Here you can see that inside of Build I just set the value for the _LastUsedColor property and then if an error occurs I just use that same property inside of that target. When I build this file the results are.

enter image description here

I think this is what you are trying to accomplish. Batching is pretty confusing if you don't know how it works. I've got a bunch of resources online regarding batching at http://sedotech.com/resources#Batching.

like image 88
Sayed Ibrahim Hashimi Avatar answered Feb 02 '23 07:02

Sayed Ibrahim Hashimi