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

joh*_*y g 2 regex msbuild msbuild-propertygroup msbuild-4.0

如此摆弄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>
Run Code Online (Sandbox Code Playgroud)

产生此输出

Example.UnitTests-x86 Isx86 'False' Inline 'True'
Example.UnitTests Isx86 'False' Inline 'False'
Run Code Online (Sandbox Code Playgroud)

bin*_*nki 5

问题

文档ItemDefinitionGroup元素(MSBuild)引用了“ 项目定义”,其中有一条注释指出:

ItemGroup中的项目元数据在ItemDefinitionGroup元数据声明中没有用,因为ItemDefinitionGroup元素先于ItemGroup元素进行处理。

这意味着您无法扩展%(Filename)中的元数据引用<ItemDefinitionGroup/>。您可以通过以下代码片段自己查看。在代码段中,.ToString()调用将结果转换为字符串对象,从而阻止MSBuild进一步扩展它。(如果我离开.ToString()了,的MSBuild会一直留下了一个System.RegularExpressions.Match对象。离开元数据定义的Match对象似乎延迟扩展到字符串,直到<Message/>Text评估,造成的MSBuild过它做一个字符串扩展过程,导致%(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>
Run Code Online (Sandbox Code Playgroud)

输出:

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”
Run Code Online (Sandbox Code Playgroud)

MSBuild文档中的注释表明Item元数据在中不可用<ItemDefinitionGroup/>。通过使用Regex.Match(),似乎属性函数扩展正在处理,%(Identity)或者在您的情况下,将其%(Filename)视为未引用的自由格式字符串。因此,由于您Regex.IsMatch()使用的语法Regex.Match()与上述示例中调用的语法相同,因此您Regex.IsMatch()将尝试检查文字字符串是否%(Filename)包含x8(可选地,后面跟任意数量的6,其存在或不存在都不会影响匹配)。

我知道根据现有元数据动态计算项目的元数据的唯一方法是创建一个从原始项目派生的新项目。例如,要创建<ProjectReference/>具有所需元数据的的列表,可以使用以下项目定义来产生ProjectReferenceWithArchItems。我选择在将属性[MSBuild]::ValueOrDefault()转换为属性扩展上下文中的字符串后使用属性函数语法,以便我可以使用String.Contains()(对于您的情况,Regex有点过大,但是如果需要,您可以轻松地对其进行修改以与正则表达式匹配)。我更新了您<Message/>Project文档以打印出元数据,以证明该元数据可以保留到新Item的定义中。

<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>
Run Code Online (Sandbox Code Playgroud)

输出:

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}'
Run Code Online (Sandbox Code Playgroud)

替代解决方案(编辑)

我刚刚注意到,如果您在中进行更新,则可以动态更新项目的元数据<Target/>。语法如下所示:

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

只需确保此目标在您需要检查Isx86元数据的目标之前运行,或者确保该目标在您的中需要元数据<ItemGroup/>之前出现<Target/>