MSBuild item collections, metadata, and batching

I recently took a new job with the software team at Centice. One of my first tasks was to get our unit tests running as part of the build. To do this, I created an MSBuild project that will run our unit tests for us. I created an ItemGroup where we list each of our NUnit test assemblies, and created a target in MSBuild that will:

  1. Update the .config file for each test assembly with the settings appropriate for the build server.
  2. Run the unit tests in each test assembly.

To update .config files, I used the Xml.ModifyFile task from the SDC Tasks Library. However, I quickly stumbled onto a small problem: Xml.ModifyFile takes a Path parameter that specifies a single file. That won’t work well with my ItemGroup. For example, if I write a target like the following:

   <Target Name=”Test”>
      <Xml.ModifyFile
         Path=”@(UnitTestConfigFiles)”
         XPath=”//My/XPath” NewValue=”Foo” />
   </Target>

I get errors like this:

   D:\Source\Test.proj(22,3): error : A task error has occured.
   D:\Source\Test.proj(22,3): error : Message = File not found
   "D:\Source\Artifacts.dll.config;D:\Artifacts\DatabaseTests.dll.config".

As we can see, the contents of the ItemGroup were expanded into a single semicolon-delimited list. Xml.ModifyFile task expects a single filename only and cannot handle such a list.

So how do we deal with this problem? MSBuild supports a concept called batching, whereby a collection of items is grouped into a set of batches based on the items’ metadata. All items with the same metadata value will be placed into the same batch, and the task will be invoked once for each batch.

In our case, we want each item in our collection to appear in its own batch that contains it and no other items. That way, the task will be invoked separately for each batch/file. To do this, we must use a piece of metadata that is guaranteed to be unique for each item. It so happens that MSBuild provides an element of well-known metadata called Identity that fits the bill. Identity metadata simply yields the value of the item itself.

So, to fix our problem, we can rewrite our MSBuild task as:

   <Target Name=”Test”>
      <Xml.ModifyFile
         Path=”%(UnitTestConfigFiles.Identity)”
         XPath=”//My/XPath” NewValue=”Foo” />
   </Target>

Now, the Xml.ModifyFile task will be invoked separately for each item in @(UnitTestConfigFiles).

For more about batching, check out the documentation on MSDN. It includes several good examples of what you can do using batches. I’ve worked with MSBuild projects quite a bit, but I never had occasion to learn about this concept before now.

Copyright © 2007 Thought Slices • Linear WordPress Theme • Powered by WordPress