EM0
EM0

Reputation: 6327

How to get MSBuild output parameter for each execution of a batched task?

I have an MSBuild target that runs an Exec task on several items. How do I get the exit code for each of them?

I can use this to get all the exit codes and to run only if the input files have changed since the last run (i.e. incremental builds):

<ItemGroup>
    <TestItem Include="MyFile1" />
    <TestItem Include="MyFile2" />
    <TestItem Include="MyFile10" />
</ItemGroup>

<Target Name="PrepareItemTest">
    <ItemGroup>
        <TestItem>
            <!-- The timestamp of the "done file" is used below to detect if this target has been run since the inputs have been modified. -->
            <DoneFilePath>%(TestItem.RelativeDir)%(TestItem.Filename).done/DoneFilePath>
        </TestItem>
    </ItemGroup>
</Target>

<Target Name="ItemTest"
      DependsOnTargets="PrepareItemTest"
      Inputs="@(TestItem);@(OtherInputs)"
      Outputs="%(TestItem.DoneFilePath)">

    <Exec Command="ECHO Running on %(TestItem.Identity) |findstr 1" IgnoreExitCode="true">
        <Output TaskParameter="ExitCode" PropertyName="TestExitCode"/>
    </Exec>

    <Error Condition=" '$(TestExitCode)' != '0' " Text="Error $TestExitCode" />
    <WriteLinesToFile Condition=" '$(TestExitCode)' == '0' " File="%(TestItem.DoneFilePath)" Lines="Ran successfully on %(TestItem.Filename)%(TestItem.Extension)" />
</Target>

Output:

Ran ItemTest. Exit code=0
Ran ItemTest. Exit code=1

So this prints all the different exit codes returned, but it doesn't associate them with the input item, so I don't know which inputs were processed successfully and which were not.

Upvotes: 1

Views: 1365

Answers (1)

Rainer Sigwald
Rainer Sigwald

Reputation: 835

The easiest way to accomplish this is to batch over the target rather than the task itself. That effectively "moves the loop up a level", from (pseudocode):

target PrepareItemTest {
    foreach item in TestItem {
        TestExitCode.append(exec().result))
    }
    warn("exit codes: " + TestExitCode)
}

to

target PrepareItemTest {
    foreach item in TestItem {
        TestExitCode.append(exec().result))
        warn("exit code: " item + " " + TestExitCode)
    }
}

If the items represent files that produce outputs, this is straightforward and you should batch in the Inputs attribute (and define Outputs for correct incremental behaviors).

For the example you've given, it's slightly confusing, because the target is not producing a set of output files. That means you have to misuse the target's Outputs attribute to get the batching behavior. You can do:

<Target Name="ItemTest" Outputs="%(TestItem.Identity)">
    <Exec Command="ECHO Running on @(TestItem->'%(Identity)') |findstr 1" IgnoreExitCode="true">
        <Output TaskParameter="ExitCode" PropertyName="TestExitCode"/>
    </Exec>

    <Warning Text="Ran ItemTest for @(TestItem). Exit code=$(TestExitCode)" />
</Target>

Which produces

Project "D:\work\batch-over-target.proj" on node 1 (default targets).
ItemTest:
  ECHO Running on MyItem1 |findstr 1
  Running on MyItem1
D:\work\batch-over-target.proj(14,5): warning : Ran ItemTest for MyItem1. Exit code=0
ItemTest:
  ECHO Running on MyItem2 |findstr 1
  The command "ECHO Running on MyItem2 |findstr 1" exited with code 1.
D:\work\batch-over-target.proj(14,5): warning : Ran ItemTest for MyItem2. Exit code=1
ItemTest:
  ECHO Running on MyItem10 |findstr 1
  Running on MyItem10
D:\work\batch-over-target.proj(14,5): warning : Ran ItemTest for MyItem10. Exit code=0
Done Building Project "D:\work\batch-over-target.proj" (default targets).

The reason this works is that within a batch, an item accessed with @(ItemName) includes only the results that fit in the batch. If you are careful to make sure that each batch is only one item, you can correlate outputs with inputs.

Upvotes: 2

Related Questions