Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cross-Join ItemGroups in MSBuild

Given something like so..

 <?xml version="1.0" encoding="utf-8"?>
    <Project DefaultTargets="test" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
        <ItemGroup>
            <ConfigFiles Include="*.config" />

            <DatabaseConfig Include="ABC">
                <Database>DB1</Database>
                <CsString>Database</CsString>
            </DatabaseConfig>

            <DatabaseConfig Include="DEF">
                <Database>DB2</Database>
                <CsString>Logging</CsString>
            </DatabaseConfig>
        </ItemGroup>


        <Target Name="test" >
            <!-- Some sort of join here (or somewhere)... -->
         <Message Text=" %(Combined.ConfigFile) %(Combined.Database) " />
        </Target> 
    </Project>

I'd like the Output to be something like this.. (given two files one.config & two.config)

one.config DB1
two.config DB1
one.config DB2
two.config DB2

(the order is not important, just the full cartesian product of the two ItemGroups)

like image 305
Dog Ears Avatar asked Apr 09 '13 23:04

Dog Ears


People also ask

What is itemgroup in MSBuild?

Identifies the ItemGroup. Defines the inputs for the build process. There may be zero or more Item elements in an ItemGroup. Required root element of an MSBuild project file. Starting with .NET Framework 3.5, the ItemGroup element can appear inside a Target element. For more information, see Targets.

What is an item function in MSBuild?

Starting with MSBuild 4.0, code in tasks and targets can call item functions to get information about the items in the project. These functions simplify getting Distinct () items and are faster than looping through the items.

What happens when multiple items are included in an itemgroup?

When multiple ItemGroup elements are used, items are combined into a single ItemGroup. For example, some items might be included by a separate ItemGroup element that's defined in an imported file. ItemGroups can have conditions applied by using the Condition attribute.

How do I apply conditions to items in an itemgroup?

ItemGroups can have conditions applied by using the Condition attribute. In that case, the items are only added to the item list if the condition is satisfied. See MSBuild conditions The Label attribute is used in some build systems as a way to control build behaviors.


2 Answers

This seems like a tidy solution:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="test" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <ItemGroup>
        <ConfigFiles Include="*.config" />

        <DatabaseConfig Include="ABC">
            <Database>DB1</Database>
            <CsString>Database</CsString>
        </DatabaseConfig>

        <DatabaseConfig Include="DEF">
            <Database>DB2</Database>
            <CsString>Logging</CsString>
        </DatabaseConfig>
    </ItemGroup>

    <Target Name="test" >
        <ItemGroup>
            <Combined Include="@(DatabaseConfig)">
                <ConfigFile>%(ConfigFiles.Identity)</ConfigFile>
            </Combined> 
        </ItemGroup>
    <Message Text=" %(Combined.ConfigFile) %(Combined.Database) " />
    </Target> 
</Project>
like image 161
Dog Ears Avatar answered Oct 08 '22 23:10

Dog Ears


There is a way you can do this with minimal changes to your existing sample code. You can combine metadata from ConfigFiles items and DatabaseConfig items into a new "combined" item and then output that "combined" item.

To combine the metadata, use target batching with the batched target running once for each DatabaseConfig item. Then you can call another target to output the combined metadata to get the output you described. Take a look at my extension of your sample code to see how this would all be accomplished:

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="test" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

  <ItemGroup>
    <ConfigFiles Include="*.config" />

    <DatabaseConfig Include="ABC">
      <Database>DB1</Database>
      <CsString>Database</CsString>
    </DatabaseConfig>

    <DatabaseConfig Include="DEF">
      <Database>DB2</Database>
      <CsString>Logging</CsString>
    </DatabaseConfig>
  </ItemGroup>

  <Target Name="test" DependsOnTargets="test_setup;test_output" >
    <!-- Logic here runs after targets listed in "DependsOnTargets". -->
  </Target>

  <!-- This will run once for each "DatabaseConfig" item. -->
  <Target Name="test_setup" Outputs="%(DatabaseConfig.Identity)">
    <PropertyGroup>
      <!-- Specify the Database for the current DatabaseConfig item -->
      <CurrentDb>%(DatabaseConfig.Database)</CurrentDb>
    </PropertyGroup>
    <ItemGroup>
      <!-- Add a new CombinedOutput item with each run, combining metadata. -->
      <CombinedOutput Include=" %(ConfigFiles.FileName)%(ConfigFiles.Extension) $(CurrentDb) " />
    </ItemGroup>
  </Target>

  <Target Name="test_output">
    <!-- Output the combined metadata from the CombinedOutput items -->
    <Message Text=" %(CombinedOutput.Identity) " />
  </Target>

</Project>

What's happening in the sample:

  1. The test target now just serves as a way to call two other targets to perform the work: test_setup, and test_output
  2. The test_setup target is batched and creates the new CombinedOutput items.
  3. The test_output target is called after test_setup to output the CombinedOutput items' metadata.

Output from test_output:

one.config DB1
two.config DB1
one.config DB2
two.config DB2
like image 39
Michael Avatar answered Oct 08 '22 21:10

Michael