Reputation: 3727
I would like to create a new item collection with modified metadata. For example, change the delimiters of the ClCompile.AdditionalIncludeDirectories. In order to do so I first create an item collection from the AdditionalIncludeDirectories metadata, and then transform it:
<ItemGroup>
<IncludeDirs0 Include="%(ClCompile.AdditionalIncludeDirectories)">
<key>@(ClCompile)</key>
</IncludeDirs0>
</ItemGroup>
<ItemGroup>
<IncludeDirs Include="@(IncludeDirs0 -> '-I %(Identity)', ' ')">
<key>%(IncludeDirs0.key)</key>
</IncludeDirs>
</ItemGroup>
<ItemGroup>
<Compile Include="@(ClCompile)">
<IncludeDirs>@(IncludeDirs)</IncludeDirs>
</Compile>
</ItemGroup>
The problem is how to filter IncludeDirs on Compile item group, such that each Compile item will have its right include dir. (so that ClCompile identity equals IncludeDirs key).
I've tried adding a condition such as: Condition="'%(IncludeDirs.key)'=='%(ClCompile.Identity)'"
but without any success.
I could have used the IncludeDirs directly: <Message Text="%(IncludeDirs.key) : @(IncludeDirs)"/>
but I feel this misses the point, since the Compile collection should contain all the metadata.
What did I miss here?
Upvotes: 4
Views: 4637
Reputation: 4286
You can do it with one additional target which collects needed items. See MSBuild Batching.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="1" />
<ClCompile Include="2">
<AdditionalIncludeDirectories>2.1;2.2;2.3</AdditionalIncludeDirectories>
</ClCompile>
</ItemGroup>
<Target Name="TransformClCompile"
Inputs="%(ClCompile.Identity)"
Outputs="_Non_Existent_Item_To_Batch_">
<PropertyGroup>
<IncludeDirs>%(ClCompile.AdditionalIncludeDirectories)</IncludeDirs>
</PropertyGroup>
<ItemGroup>
<IncludeDirs Include="$(IncludeDirs)" />
<Compile Include="@(ClCompile)">
<IncludeDirs>@(IncludeDirs ->'-I %(Identity)', ' ')</IncludeDirs>
</Compile>
</ItemGroup>
</Target>
<Target Name="Build" DependsOnTargets="TransformClCompile">
<Message Text="compile %(Compile.Identity) %(Compile.IncludeDirs)" />
</Target>
</Project>
Output:
compile 1
compile 2 -I 2.1 -I 2.2 -I 2.3
Upvotes: 4
Reputation: 934
There are ways to do it more concisely in 4.0+, using Property Functions.
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClCompile Include="1" />
<ClCompile Include="2">
<AdditionalIncludeDirectories>2.1;2.2;2.3</AdditionalIncludeDirectories>
</ClCompile>
</ItemGroup>
<Target Name="Build" >
<Message Text="compile %(ClCompile.Identity)" Condition="'%(ClCompile.AdditionalIncludeDirectories)' ==''"/>
<Message Text="compile %(ClCompile.Identity) /I $([System.String]::Join(' /I ', $([System.Text.RegularExpressions.Regex]::Split('%(ClCompile.AdditionalIncludeDirectories)', ';'))))" Condition="'%(ClCompile.AdditionalIncludeDirectories)' !=''"/>
</Target>
</Project>
Output
compile 1
compile 2 /I 2.1 /I 2.2 /I 2.3
It isn't that pretty, but it's a little better, I think. The Regex.Split has to be used instead of String.Split because the latter needs an array of splitters and that's a little tricky to get.
The MSBuild binder needs some improvements, I think.
Dan (msbuild)
Upvotes: 4