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

强颜欢笑 提交于 2019-12-06 10:49:05

问题


So fiddling with MSBuild tasks, and I am finding that a Regex metadata property is evaluated once rather than per item.

For example

<!-- 
  actual items, we use standard project reference items and extend via 
  ItemDefinitionGroup. add project references through IDE to extend 
  coverage
-->
<ItemGroup>
  <ProjectReference Include="..\Example.UnitTests-x86\Example.UnitTests-x86.csproj">
    <Project>{7e854803-007c-4800-80f9-be908655229d}</Project>
    <Name>Example.UnitTests-x86</Name>
  </ProjectReference>
  <ProjectReference Include="..\Example.UnitTests\Example.UnitTests.csproj">
    <Project>{eaac5f22-bfb8-4df7-a711-126907831a0f}</Project>
    <Name>Example.UnitTests</Name>
  </ProjectReference>
</ItemGroup>

<!-- additional item properties, defined with respect to item declaring it -->
<ItemDefinitionGroup>
  <ProjectReference>
    <Isx86>
      $([System.Text.RegularExpressions.Regex]::IsMatch(%(Filename), '.*x86*'))
    </Isx86>
  </ProjectReference>
</ItemDefinitionGroup>

<!-- additional task target, invoke both x64 and x86 tasks here -->
<Target Name="AdditionalTasks">
  <Message 
    Text="%(ProjectReference.Filename) Isx86 '%(Isx86)' Inline 
    '$([System.Text.RegularExpressions.Regex]::IsMatch(%(Filename), '.*x86*'))'" 
    Importance="high" />
</Target>

Produces this output

Example.UnitTests-x86 Isx86 'False' Inline 'True'
Example.UnitTests Isx86 'False' Inline 'False'

回答1:


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/>.



来源:https://stackoverflow.com/questions/14103467/why-is-system-text-regularexpressions-regexismatch-evaluated-once-in-it

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