4

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?

Amir Gonnen
  • 3,525
  • 4
  • 32
  • 61

2 Answers2

4

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
Dov
  • 15,530
  • 13
  • 76
  • 177
Sergio Rykov
  • 4,176
  • 25
  • 23
  • Instead of the ?cleaner pattern we use in the Microsoft .targets files is: Of course if there's already a condition, you "and" it. – cheerless bog Apr 02 '12 at 02:38
  • Ugh, I just can't figure out how to make code look right in a comment. – cheerless bog Apr 02 '12 at 02:41
  • That answer is incorrect, I believe, cheerless bog. This is what I get when trying to use your "cleaner" pattern: error MSB4116: The condition "'%(SupportedFramework.Identity )' != ''" on the "CopySourceAsContent" target has a reference to item metadata. References to item metadata are not all owed in target conditions unless they are part of an item transform. – kzu Dec 05 '12 at 04:41
  • Is is necessary to specify `Outputs="_Non_Existent_Item_To_Batch"`? And if so, is it important that the chosen name is a uniquely named output? – CrashNeb Aug 03 '21 at 07:57
4

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)

cheerless bog
  • 914
  • 8
  • 11