Why is $([System.Text.RegularExpressions.Regex]::IsMatch()) evaluated once in ItemGroupDefinition?

久未见 提交于 2019-12-04 19:22:41

Problem

The documentation ItemDefinitionGroup Element (MSBuild) refers to Item Definitions which has a note which states:

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

This means that your %(Filename) metadata reference in the <ItemDefinitionGroup/> cannot be expanded. You can see this yourself with this following snippet. In the snippet, the .ToString() call converts the result to a string object, preventing MSBuild from further expanding it. (If I had left .ToString() out, MSBuild would have been left with a System.RegularExpressions.Match object. Leaving the metadata definition as a Match object seems to delay expansion to string until the <Message/>’s Text is evaluated, causing MSBuild to do a string expansion pass over it, resulting in %(Identity) being expanded when you might not expect it to be. This delayed expansion is also demonstrated in the following snippet.)

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <MyItem Include="MyItem’s value" />
    <MyItem Include="MyItem’s second value" />
  </ItemGroup>
  <ItemDefinitionGroup>
    <MyItem>
      <ItemDefinitionMatchedText>$([System.Text.RegularExpressions.Regex]::Match(%(Identity), ".*").get_Groups().get_Item(0).ToString())</ItemDefinitionMatchedText>
      <ItemDefinitionMatchedTextDelayed>$([System.Text.RegularExpressions.Regex]::Match(%(Identity), ".*").get_Groups().get_Item(0))</ItemDefinitionMatchedTextDelayed>
    </MyItem>
  </ItemDefinitionGroup>
  <Target Name="Build" Outputs="%(MyItem.Identity)">
    <Message Text="Data being matched against for item “%(MyItem.Identity)” is “%(ItemDefinitionMatchedText)”"/>
    <Message Text="Delayed string conversion causes delayed expansion: “%(MyItem.ItemDefinitionMatchedTextDelayed)”"/>
  </Target>
</Project>

Output:

Build:
  Data being matched against for item “MyItem’s value” is “%(Identity)”
  Delayed string conversion causes delayed expansion: “MyItem’s value”
Build:
  Data being matched against for item “MyItem’s second value” is “%(Identity)”
  Delayed string conversion causes delayed expansion: “MyItem’s second value”

The note from MSBuild’s documentation indicates that Item metadata is not available in <ItemDefinitionGroup/>. It appears, from using Regex.Match(), that the property function expansion is treating %(Identity) or, in your case, %(Filename) as an unquoted free-form string. Thus, since you invoke Regex.IsMatch() with the same syntax as I invoke Regex.Match() in the above example, it follows that your Regex.IsMatch() is trying to check if the literal string %(Filename) contains x8 (optionally followed by any number of 6s whose presence or absence will not affect the match).

Solution

The only way I know of to dynamically calculate an Item’s metadata based on existing metadata is to create a new Item derived from the original item. For example, to create a list of <ProjectReference/>s with the metadata you need, you could use the following item definition to produce ProjectReferenceWithArch Items. I chose to use property function syntax after using [MSBuild]::ValueOrDefault() to turn it into a string in property expansion context so that I could use String.Contains() (Regex is a bit overkill for your case, but you could easily modify this to match against a regular expression if needed). I updated your <Message/> to print out the Project metadata to demonstrate that this metadata survives into the new Item’s definition.

<ItemGroup>
  <ProjectReferenceWithArch Include="@(ProjectReference)">
    <Isx86>$([MSBuild]::ValueOrDefault('%(Filename)', '').Contains('x86'))</Isx86>
  </ProjectReferenceWithArch>
</ItemGroup>
<Target Name="AdditionalTasks">
  <Message 
    Text="%(ProjectReferenceWithArch.Filename) Isx86 '%(Isx86)' Inline '$([System.Text.RegularExpressions.Regex]::IsMatch(%(Filename), '.*x86*'))' Project '%(Project)'" 
    Importance="high" />
</Target>

Output:

AdditionalTasks:
  Example.UnitTests-x86 Isx86 'True' Inline 'True' Project '{7e854803-007c-4800-80f9-be908655229d}'
  Example.UnitTests Isx86 'False' Inline 'False' Project '{eaac5f22-bfb8-4df7-a711-126907831a0f}'

Alternative Solution (EDIT)

I just noticed that you can dynamically update an Item’s metadata if you do so in a <Target/>. The syntax looks like this:

<Target Name="AdditionalTasks">
  <ItemGroup>
    <ProjectReference>
      <Isx86>$([MSBuild]::ValueOrDefault('%(Filename)', '').Contains('x86'))</Isx86>
    </ProjectReference>
  </ItemGroup>
</Target>

Just ensure that this target runs before the target in which you need to check the Isx86 metadata or that the <ItemGroup/> appears before the metadata is needed in your <Target/>.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!