Scope of properties and item in an MSBuild script 4

Scope of properties and item in an MSBuild script

Some time ago I asked myself what was the scope of properties and items in an MSBuild script specifically when using CallTarget and MSBuild tasks and dynamically modifying those variables. Here are the results of my (long) researches:

What we must know first:

  • Using the CallTarget task is the same as using the MSBuild task with the project $(MSBuildProjectFile). I’ll use the MSBuild task in this post.
  • Internally MSBuild uses an instance of the Project class to represents a script project.

Here is the test script I’m using:

<PropertyGroup>
<MyProp>Static</MyProp>
</PropertyGroup>

<Target Name=”Print”>
<Message Text=”[Print] $(MyProp)” />
</Target>

<Target Name=”Update”>
<PropertyGroup>
<MyProp>Dynamic</MyProp>
</PropertyGroup>
</Target>

<Target Name=”Test1″>
<Message Text=”[Test1] [BeforeUpdate] $(MyProp)” />
<MSBuild Projects=”$(MSBuildProjectFile)” Targets=”Update” />
<Message Text=”[Test1] [AfterUpdate] $(MyProp)” />
</Target>

<Target Name=”Test2″>
<Message Text=”[Test2] [BeforeUpdate] $(MyProp)” />
<MSBuild Projects=”$(MSBuildProjectFile)” Targets=”Update” />
<MSBuild Projects=”$(MSBuildProjectFile)” Targets=”Print” />
<Message Text=”[Test2] [AfterUpdate] $(MyProp)” />
</Target>

If we run this script calling the targets “Update” and “Print” we have the following result:

image

As expected the value of “MyProp” is the updated one (“Dynamic”). Now if we call the targets “Test1” and “Print” we have the following result:

image1

Although we the target “Test1” calls the “Update” target using the MSBuild task, the value of “MyProp” is still “Static” but it is the updated value in the “Print” target.

Calling the script with the “Test2” target helps understand what’s going on:

image2

Calling the “Print” task in the target “Test2” you have the updated value. Based on these examples here is what I deduced:

  • One instance of the Project class is created for the script and contains all the values of the properties and items in a global context.
  • When a target is executed, the global context is copied in a local context which will be used by the target.
  • A the target execution end, the local context updates are merged back to the global context.

Until a target execution is finished the local updates are not accessible to targets called using CallTarget or MSBuild tasks. You must be careful with this mechanism as shown with the following example:

<Target Name=”Warning”>
<PropertyGroup>
<MyProp>Warning</MyProp>
</PropertyGroup>
<MSBuild Projects=”$(MSBuildProjectFile)” Targets=”Update” />
</Target>

If we call the “Warning” and “Print” target we would expect to see the value “Dynamic” since the call to “Update” is done after the update in “Warning” but here’s the result:

image3

Why this results ? When calling the “Update” target, the global context is updated with the “Dynamic” value but at the end of the “Warning” target execution, the global context is again updated with the “Warning” value from the local context which has no information the another updated occurred and so overwrite the “Dynamic” value.

But wait, there is more :-)  Look at the following example:

<Target Name=”Test3″>
<MSBuild Projects=”$(MSBuildProjectFile)” Targets=”Update” Properties=”a=a” />
</Target>

If we call the targets “Test3” and “Print” we could think we’ll have the “Dynamic” value but her is the result:

image4

Why does the value of “MyProp” is not updated? The answer is that the MSBuild engine creates a instance of the Project class for each unique pair (<file name>, <passed parameter properties dictionary>). In this case, when calling the “Update” target we pass as parameters the different property set (“a=”a”). A new instance of Project is created and it is this instance global context which is updated. This can be confirmed by the following example:

<Target Name=”Test4″>
<MSBuild Projects=”$(MSBuildProjectFile)” Targets=”Update” Properties=”a=a” />
<MSBuild Projects=”$(MSBuildProjectFile)” Targets=”Print” Properties=”a=a” />
</Target>

If we call the “Test4” target we get the following result:

image5

The two calls to MSBuild task uses the same file and property set, they share the same instance of a Project class and thus the same global context.

Here’s a summary what I found about MSBuild properties and items scope:

  • MSBuild creates a instance of the Project class for each unique pair (<file name>, <passed parameter properties dictionary>).
  • MSBuild stores for each instance of Project a global context containing the properties and items values.
  • When executing a target for an instance of Project, a local context is created from a copy of the global context and is used by the target.
  • When a target execution ends, the updated values in the local context are merged back to the global context.

Carpe Diem.

4 thoughts on “Scope of properties and item in an MSBuild script

  1. Reply Sayed Ibrahim Hashimi Oct 30,2009 21:13

    Hi,
    Using CallTarget and the MSBuild task are not the same. Using CallTarget will have all the properties and items available, and MSBuild task not necessarily. Also there is a bug related to CallTarget which doesn’t correctly expose dynamic properties and items when the target is called. See my blog posts for more details at:
    http://sedodream.com/2005/12/28/MSBuildCreatePropertyItemAndCallTargetBug.aspx
    http://sedodream.com/2006/01/08/MSBuildCreatePropertyBugWorkaround.aspx

    Instead of using CallTarget or the MSBuild task in this manner, better is to use the DependsOnTargets on the Targets node to build a list of targets which should be executed.

    Sayed Ibrahim Hashimi

  2. Reply Guillaume Rouchon Nov 2,2009 13:54

    Hi,
    Thanks for the input. You’re right about the CallTarget and MSBuild task which are not exactly the same, internally the same code is used (MSBuild.ExecuteTarget) but CallTarget provides all the current context automatically which MSBuild doesn’t.
    As for the bug about dynamic properties, that’s what i was trying to explain: the dynamic properties are created/updated inside the current target local scope and are only available to other targets after the target ends which triggers a global scope update. That’s why DependsOnTarget works as calling multiple targets one after another.

    As a side note: your book is great and is in my must have list when talking about MSBuild :)

    Guillaume Rouchon

  3. Reply David Keaveny Dec 10,2009 02:46

    So is it possible to have an itemgroup (whether created by or in MSBuild 3.5) that is locally-scoped to a target?

  4. Reply Guillaume Rouchon Dec 10,2009 09:22

    Hi David,
    Unfortunately no, MSBuild doesn’t have a true local scope mechanism; Every property or itemgroup created locally will, at the end of the target, be copied to the global scope. If you don’t want your itemgroup values to be accessed from other targets the best to do is create it at the start of your target and clean it at the end using :

    <ItemName Remove="@(ItemName)"/>

    Guillaume

Leave a Reply to Guillaume Rouchon Cancel Reply

  

  

  

CAPTCHA *