oliwa
oliwa

Reputation: 1890

How to batch in MSBuild?

I can't figure out how to pass values into an MSBuild task like I would a method. Take the following project file...

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="Main">
<PropertyGroup>
    <Var1>Foo</Var1>
    <Var2>Bar</Var2>
</PropertyGroup>

<Target Name="Main">
    <Message Text="$(Var1)" Importance="high" />
    <Message Text="$(Var2)" Importance="high" />
</Target>   
</Project>

I want to refactor the Message task into a target and then pass over Var1 and Var2 to it to get the same output. This is a very simplified example but the concept is the same.

Upvotes: 3

Views: 1991

Answers (2)

binki
binki

Reputation: 8308

This is meant to complement, not replace, @BryanJ’s answer.

There are two types of batching. One is Task batching which happens when you use %(ItemName.MetaData) syntax. You just specify this value into a task parameter as if %(ItemName.MetaData) would only ever expand to one particular value. MSBuild then automatically executes the task multiple times, avoiding the need for the task to explicitly support iterating over a list of items.

Another batching type is Target batching. Target batching happens when you use the Inputs and Outputs attributes of <Target/>. To batch over an arbitrary set of Items in such a way that the target gets executed exactly once per Item, you can specify Inputs="@(ItemName)" Outputs=%(Identity).bogus. What’s important is that %(Identity) is present. Batching will look at all the possible expansions of Inputs and Outputs and decide its batching based on these expansions. Thus, you must make sure that each item gets a unique expansion if you want the Target to run separately for each item. I give @BryanJ’s code with modifications to use Target batching of this style:

<?xml version="1.0"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0" DefaultTargets="all">
  <ItemGroup>
    <Messages Include="Message1">
      <Text>Hello from Message1</Text>
      <Group>1</Group>
    </Messages>
    <Messages Include="Message2">
      <Text>Hello from Message2</Text>
      <Group>1</Group>
    </Messages>
    <Messages Include="Message3">
      <Text>Hello from Message3</Text>
      <Group>2</Group>
    </Messages>
  </ItemGroup>
  <Target Name="all" DependsOnTargets="TestMessage;TestMessageGrouping" />
  <!--
      Use the Inputs/Outputs attributes to specify Target
      batching. The metadata value I am batching over is
      Identity. Since Identity is unique per item, this means the
      Target will get run in full once for every value in
      Messages. We provide something bogus for Outputs. It is
      important that our bogus values do not coincide with real
      filenames. If MSBuild finds a file with the name of a value
      in Outputs and another file, with an older timestamp,
      matching the corresponding value in Inputs, it will skip
      running this Target. (This is useful in many situations, but
      not when we want to just print out messages!)
  -->
  <Target Name="TestMessage" Inputs="@(Messages)" Outputs="%(Identity).bogus">
    <Message Text="I will print the Text metadata property of %(Messages.Identity)" />
    <Message Text="%(Messages.Text)" />
  </Target>
  <!--
      If you want to combine Task and Target batching, you can specify
      a different metadata value than Identity to group the items
      by. I use the Group metadata I specified in the ItemGroup.
  -->
  <Target Name="TestMessageGrouping" Inputs="@(Messages)" Outputs="%(Group).bogus">
    <Message Text="I will print the Text metadata property of all messages from Group %(Messages.Group)" />
    <!--
        Now, within the Target batch, we use Task batching to print
        all of the messages in our %(Messages.Group) at once.
    -->
    <Message Text="%(Messages.Text)" />
  </Target>
</Project>

with output:

TestMessage:
  I will print the Text metadata property of Message1
  Hello from Message1
TestMessage:
  I will print the Text metadata property of Message2
  Hello from Message2
TestMessage:
  I will print the Text metadata property of Message3
  Hello from Message3
TestMessageGrouping:
  I will print the Text metadata property of all messages from Group 1
  Hello from Message1
  Hello from Message2
TestMessageGrouping:
  I will print the Text metadata property of all messages from Group 2
  Hello from Message3

Upvotes: 3

BryanJ
BryanJ

Reputation: 8563

I think you want to do something like this:

<ItemGroup>
  <Messages Include="Message1">
    <Text>Hello from Message1</Text>
  </Messages>
  <Messages Include="Message2">
    <Text>Hello from Message2</Text>
  </Messages>
</ItemGroup>

<Target Name="TestMessage">
  <Message Text="%(Messages.Text)"/>
</Target>

This produces the following output:

TestMessage:
  Hello from Message1
  Hello from Message2

Upvotes: 4

Related Questions