Ben
Ben

Reputation: 69

MSBuild: How to update default metadata in an item?

I have the following problem in scripting with MSBuild: I create a default item "itemA" with two metadata "metadata1" and "metadata2", whereby metadata2 refers to metadata1.

When I define itemA later and overwrite metadata1, the metadata2 contains still the default value of metadata1. How can I make the metadata2 to refer to the "new" metadata1?

Illustration in code as below:

  <ItemDefinitionGroup>
    <itemA>
      <Metadata1>default</Metadata1>
      <Metadata2>%(itemA.Metadata1)</Metadata2>
    </itemA>
  </ItemDefinitionGroup>    
  <ItemGroup>
    <itemA Include="first" >
      <Metadata1>m_data1</Metadata1>
    </itemA>
  </ItemGroup>

But see the print

<Message Text="itemA.Metadata1 = %(itemA.Metadata1)" />
<Message Text="itemA.Metadata2 = %(itemA.Metadata2)" />

delivers:

itemA.Metadata1 = m_data1       ***<-- correctly updated***

itemA.Metadata2 = default       ***<-- why showing the default value, not* m_data1??**

how can I make itemA.Metadata2 to have the same value as itemA.Metadata1 after it has been updated?

Upvotes: 1

Views: 1158

Answers (2)

Chris Cavin
Chris Cavin

Reputation: 23

As palo states, since Metadata2 has already been evaluated, you'll have to explicitly overwrite the value. Your change to Metadata1 won't automatically propagate to other places where it was referenced during initialization.

However, you can "re-evaluate" your items' metadata by starting a new instance of MSBuild and passing the updated metadata in as a property. Running msbuild /t:Wrapper on this project from the command line will result in Metadata1 and Metadata2 printing the same value:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <DefaultMetadata1 Condition="DefaultMetadata1==''">default</DefaultMetadata1>
  </PropertyGroup>

  <ItemDefinitionGroup>
    <itemA>
      <Metadata1>$(DefaultMetadata1)</Metadata1>
      <Metadata2>%(itemA.Metadata1)</Metadata2>
    </itemA>
  </ItemDefinitionGroup>    
  <ItemGroup>
    <itemA Include="first" >
      <Metadata1>m_data1</Metadata1>
    </itemA>
  </ItemGroup>

  <Target Name="Wrapper">       
    <MSBuild 
      Projects="$(MSBuildProjectFile)"
      Targets="Worker"
      Properties="DefaultMetadata1=%(itemA.Metadata1)"
    />
  </Target>

  <Target Name="Worker">
    <Message Text="itemA.Metadata1 = %(itemA.Metadata1)" />
    <Message Text="itemA.Metadata2 = %(itemA.Metadata2)" />
  </Target>
</Project>

The usefulness of this approach will depend on what you're trying to accomplish. You can undoubtedly find an alternate solution using properties instead of item metadata.

While the solution above works for the case you describe, it can quickly get out of hand. There's probably a more simple solution that may involve some redundant code.

My recommendation would be to use the simple solution and eliminate as much redundancy as you reasonably can without inventing new ways to get around MSBuild's small feature set. Clever tricks here probably won't save you that many LOC at the end of the day and may result in less readable code, making it more difficult for newcomers to understand what's going on.

Upvotes: 1

Palo Misik
Palo Misik

Reputation: 761

I think this is not possible because order of evaluation Item Definitions - Value Sources - Note:

Item metadata from an ItemGroup is not useful in an ItemDefinitionGroup metadata declaration because ItemDefinitionGroup elements are processed before ItemGroup elements.

You have to override itemA's Metadata2 value in ItemGroup

  <ItemDefinitionGroup>
    <itemA>
      <Metadata1>default</Metadata1>
      <Metadata2>%(Metadata1)</Metadata2>
    </itemA>
  </ItemDefinitionGroup>    
  <ItemGroup>
    <itemA Include="first" >
      <Metadata1>m_data1</Metadata1>
      <Metadata2>%(Metadata1)</Metadata2>
    </itemA>
  </ItemGroup>

Upvotes: 2

Related Questions