TheGeekery

The Usual Tech Ramblings

MSBuild, and Evaluating ItemGroups

As previous mentioned, I’ve been dropped the lovely project of managing, and automating the build processes for all of our application suites. I’m getting to learn some interesting stuff about automated builds, and finding some interesting quirks, such as the evaluation of ItemGroups.

One of the things I’ve been working on, is building packages for releases. As I mentioned previously, I’ve started using MSBuild Community Tasks. It adds a very handy “Zip” task to the list. Per the documentation, creating a zip file is pretty simple…

    <ItemGroup>
        <ZipFiles Include="**\*.*" Exclude="*.zip" />
    </ItemGroup>
    <Target Name="Zip">
        <Zip Files="@(ZipFiles)" 
                    ZipFileName="MSBuild.Community.Tasks.zip" />
    </Target>

You define an ItemGroup, and then call the Zip task. That’s about as complicated as it gets. Or so I thought. Take the example above, the files to include are **\*.*. Those not familiar with MSBuild, that basically expands to all files, and sub-directories.

Mine was a little more complicated, and looked like this:

  <ItemGroup>
    <DeployFolders Include="$(ReleaseFolder)\Project1" />
    <DeployFolders Include="$(ReleaseFolder)\Project2" />
  </ItemGroup>

  <Target Name="BuildPackages">
    <Time Format="yyyyMMddHHmmss">
      <Output TaskParameter="FormattedTime" PropertyName="buildDate" />
    </Time>
    <Zip
      Files="@(DeployFolders)"
      WorkingDirectory="$(ReleaseFolder)"
      ZileFileName="$(ReleaseFolder)\Product.$(buildDate).zip" />
  </Target>

Unfortunately, what I hadn’t realized was that MSBuild evaluates (processes/expands) ItemGroups on start-up. This causes a problem when your output directory doesn’t contain any files, so the ItemGroup was expanding to an empty list. This resulted in an empty zip file. If I’d run the same script again, without deleting the output files, it’d zip them up, and exclude any new items.

I did some stumbling around on Google, and found Donn Felker’s blog, which had a post detailing the same issue, and references back to an MSDN blog explaining the issue. He basically details how to get around this issue, by using CreateItem in the Target block. So in my case, my code would end up having to look like this:

    <Target Name="BuildPackages">
        <Time Format="yyyyMMddHHmmss">
            <Outpt TaskParamter="FormattedTime" PropertyName="buildDate" />
        </Time>
        <CreateItem Include="$(ReleaseFolder)\Project1">
            <Output PropertyName="OutputProject1" />
        </CreateItem>
        <CreateItem Include="$(ReleaseFolder)\Project2">
            <Output PropertyName="OutputProject2" />
        </CreateItem>

        <Zip
          Files="@(OutputProject1);@(OutputProject2)"
          WorkingDirectory="$(ReleaseFolder)"
          ZipFileName="$(ReleaseFolder\Product.$(buildDate).zip" />

  </Target>

This worked perfectly. However, this isn’t quite so maintainable because if I add another project to the build, I have to create a new CreateItem, and update the zip line. I’ll be working on some new ideas to save some work here, probably the easiest would be to build based on the $(ReleaseFolder), rather than individual projects. Using that methodology, allows me to have one CreateItem, instead of several.

Comments