Thursday, March 26, 2009

ItemGroup Gotcha

At work, we have been deploying the latest build from the build server to the test server manually for a while. I know this is not smart but the fact is, it really doesn’t take that much time to copy and paste files from build server to the test server.

Nevertheless, one day I was finally sick of this dumb process and decided to roll up my sleeves to make the build server do this monkey job automatically.  I wrote a MsBuild script, which automatically copies files to the test server from build server after finishing a build and the script below is the simplified version of it:

<PropertyGroup> 
  <Root>$(MSBuildProjectDirectory)</Root> 
  <SourceFolder>$(Root)\Source</SourceFolder> 
  <OutputFolder>$(Root)\Output</OutputFolder> 
  <DeployFolder>$(Root)\Deploy</DeployFolder> 
</PropertyGroup> 

<ItemGroup> 
  <DllFiles Include="$(OutputFolder)\*.dll" /> 
</ItemGroup> 

<Target Name="BuildAll"> 
  <Csc 
    Sources="$(SourceFolder)\MyClass1.cs" 
    TargetType="library" 
    OutputAssembly="$(OutputFolder)\MyClass1.dll" 
    EmitDebugInformation="True" /> 
  <Csc 
    Sources="$(SourceFolder)\MyClass2.cs" 
    TargetType="library" 
    OutputAssembly="$(OutputFolder)\MyClass2.dll" 
    EmitDebugInformation="True" /> 
</Target> 

<Target Name="DeployDlls" DependsOnTargets="BuildAll"> 
  <Copy 
    SourceFiles="@(DllFiles)" 
    DestinationFolder="$(DeployFolder)" /> 
</Target>

When the target DeployDlls is invoked with following command:

msbuild /t:DeployDlls build.proj

It is supposed to call the target BuildAll first due to the dependency, which builds the .cs files and then the DeployDlls target will run the Copy task to copy output files to the deploy folder. This seems to be a pretty straight forward script but it has a major bug in it. It does not work with a clean build.

The problem is that the ItemGroup clause is actually evaluated before any target was invoked. Therefore, when this script is executed against a clean build (i.e. no DLL files in the Build folder yet), the DllFiles item group will get evaluated into an empty array and therefore nothing will be copied at all after the build finished.

To fix this problem, the DeployDlls target must be updated to:

<Target Name="DeployDlls" DependsOnTargets="BuildAll">
  <CreateItem Include="$(OutputFolder)\*.dll">
    <Output
      TaskParameter="Include"
      ItemName="DllFiles"/>
  </CreateItem>
  <Copy
    SourceFiles="@(DllFiles)"
    DestinationFolder="$(DeployFolder)" />
</Target>

This way, the CreateItem task will populate DllFiles item group after the build is finished and right before the Copy task is executed.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.