为什么$([System.Text.RegularExpressions.Regex] :: IsMatch())在ItemGroupDefinition中计算一次?

时间:2012-12-31 15:41:47

标签: regex msbuild msbuild-4.0 msbuild-propertygroup

如此摆弄MSBuild任务,我发现Regex元数据属性只被评估一次而不是每个项目。

例如

<!-- 
  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>

生成此输出

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

1 个答案:

答案 0 :(得分:4)

问题

文档ItemDefinitionGroup Element (MSBuild)引用Item Definitions,其中有一条注明:

  

ItemGroup中的项元数据在ItemDefinitionGroup元数据声明中没有用,因为ItemDefinitionGroup元素在ItemGroup元素之前处理。

这意味着无法展开%(Filename)中的<ItemDefinitionGroup/>元数据参考。您可以使用以下代码片段自行查看。在代码段中,.ToString()调用将结果转换为字符串对象,从而阻止MSBuild进一步扩展它。 (如果我离开了.ToString(),MSBuild将留下一个System.RegularExpressions.Match对象。将元数据定义保留为Match对象似乎会将扩展延迟到字符串,直到{{1}评估<Message/>,导致MSBuild对其进行字符串扩展传递,导致Text在您可能不期望的情况下进行扩展。此延迟扩展也在以下代码段中得到证明。)

%(Identity)

输出:

<?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>

MSBuild文档中的注释表明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” 中没有项元数据。从使用<ItemDefinitionGroup/>开始,property function expansion正在处理Regex.Match(),或者在您的情况下,%(Identity)作为未加引号的自由格式字符串。因此,由于您使用与我在上面的示例中调用%(Filename)相同的语法调用Regex.IsMatch(),因此您的Regex.Match()正在尝试检查文字字符串Regex.IsMatch()是否包含%(Filename)(可选地后跟任意数量的6,其存在或不存在不会影响匹配)。

解决方案

我知道根据现有元数据动态计算Item元数据的唯一方法是创建一个从原始项派生的新Item。例如,要创建包含所需元数据的x8列表,可以使用以下项定义来生成<ProjectReference/>项。在使用property function syntax[MSBuild]::ValueOrDefault()转换为属性扩展上下文中的字符串后,我选择使用that you can dynamically update an Item’s metadata if you do so in a <Target/>以便我可以使用ProjectReferenceWithArch(正则表达式对您的情况有点矫枉过正,但您可以轻松如果需要,修改它以匹配正则表达式)。我更新了您的String.Contains()以打印出<Message/>元数据,以证明此元数据可以保留在新商品的定义中。

Project

输出:

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

替代解决方案(编辑)

我刚注意到{{3}}。语法如下所示:

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}'

只需确保此目标在您需要检查<Target Name="AdditionalTasks"> <ItemGroup> <ProjectReference> <Isx86>$([MSBuild]::ValueOrDefault('%(Filename)', '').Contains('x86'))</Isx86> </ProjectReference> </ItemGroup> </Target> 元数据的目标之前运行,或者在Isx86中需要元数据之前显示<ItemGroup/>