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'
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).
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}'
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/>
.