Bryan Slatner
Bryan Slatner

Reputation: 1608

Confused about contents of MSBuild ItemGroup

I'm working on an MSBuild script whose job is to build a solution and then copy the build output from two projects into a unified directory. Simple enough.

I'm doing this with the Copy task. Like so:

<ItemGroup>
    <OutputFiles1 Include="Project1\bin\Release\*.*" />
    <OutputFiles2 Include="Project2\bin\Release\*.*" />
</ItemGroup>

<Target CopyOutput>
    <Copy SourceFiles="@(OutputFiles1)" DestinationFolder="DeployOutput" />
    <Copy SourceFiles="@(OutputFiles2)" DestinationFolder="DeployOutput" />
</Target>

The problem I'm experiencing is this: The two ItemGroup elements contain the contents of the directories when the build script starts, not the contents of the directories when the solution build finishes.

So, for example, if I add a reference in Project1 to a new assembly and then run the build, the DeployOutput directory does not contain that new assembly because it didn't existing in the project output directory when the build started. But if I run the build again the file is there and gets copied.

It seems like this behavior is by design, but I'm unsure how to accomplish my task without spawning to a batch file or something like that to do the copy.

Upvotes: 1

Views: 380

Answers (1)

stijn
stijn

Reputation: 35901

Classical msbuild evaluation order problem: items and properties in the project root are basically evaluated upon parsing, before any targets run. Put them inside a target however and they are evaluated when the target runs. Which is better for your case, since you can also make sure one target runs after another and hence make the first see outputs produced by the latter. Illustration:

<ItemGroup>
  <OutputFiles1 Include="Project1\bin\Release\*.*" />
</ItemGroup>

<Target Name="BuildIt">
  <Message Text="OutputFiles1=@(OutputFiles1 )" />
  <MSBuild Projects="Project1.vcxproj" Targets="Build" />
</Target>

<Target Name="Copy" DependsOnTargets="BuildIt">
  <ItemGroup>
    <ActualOutputFiles1 Include="Project1\bin\Release\*.*" />
  </ItemGroup>
  <Message Text="OutputFiles1=@(OutputFiles1)" />
  <Message Text="ActualOutputFiles1=@(ActualOutputFiles1 )" />
</Target>

When running the Copy target (and the output directory still is empty) you'll get output like

OutputFiles1=
.... build output ....
OutputFiles1=
ActualOutputFiles1=Project1\bin\Release\a.dll;......

OutputFiles1 is always empty since at the time it was evaluated the output directory was empty. ActualOutputFiles1 however was evaluated after the build so it contains the output directory's content.

Upvotes: 4

Related Questions