MSBuild Properties Don’t Always Evaluate Properly

Filed under MSBuild, Troubleshooting

image In my travails with MSBuild lately (the latest 3.5 version), I recently ran into some rather puzzling behavior. Essentially, it boils down to this.

If you set a property value via a PropertyGroup element within a target, and then use CallTarget to call another target, the property values you set won’t be visible to conditions on that called target.

If, on the other hand, you set those same property values from a PropertyGroup within a different target that your main target refers to via the DependsOnTargets attribute, then those properties will be visible to conditions on the called target.

Yeah, you read that right <g>

Here’s a test MSProj project file that illustrates the problem.

<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
    <!-- Define various properties to use during the build here -->
    <PropertyGroup>
      <TestValue>false</TestValue>
    </PropertyGroup>

    
    <!-- A DependsOnTargets target -->
    <Target Name="DetermineTestValue">
        <PropertyGroup>
            <TestValue>true</TestValue>
        </PropertyGroup>
        <Message Text="TestValue=$(TestValue)" />
    </Target>

    
    <!-- The test condition fails here and this target is skipped -->
    <Target Name="DoTheBuildSkips" 
            Condition="'$(TestValue)' == 'true'">
      <Message Text="Did the Build (skipped)" />
    </Target>

    
    <!-- The test condition DOES NOT fail here and this target is executed -->
    <Target Name="DoTheBuildWorks" 
            Condition="'$(TestValue)' == 'true'">
      <Message Text="Did the Build (works)" />
    </Target>


    <!-- Root Target just Executes two different test targets -->
    <Target Name="Build" DependsOnTargets="PropertyIsNotSeenProperly;PropertyWorks" />
    
    <!-- First test target, set the TestValue to true and use calltarget, this fails -->
    <Target Name="PropertyIsNotSeenProperly">
        <PropertyGroup>
            <TestValue>true</TestValue>
        </PropertyGroup>
        <!-- the TestValue is true at this point but false when evaluated from DoTheBuildSkips -->
        <Message Text="TestValue (place1)=$(TestValue)" />
        <CallTarget Targets="DoTheBuildSkips" />
    </Target>    

    <!-- Second test target, set the TestValue to true from the DependsOnTarget
         and the "DoTheBuildWorks" target is executed because the condition is true -->
    <Target Name="PropertyWorks" DependsOnTargets="DetermineTestValue">
        <!-- the TestValue is set to true from within DetermineTestValue and is ALSO true
             when evaluated from DoTheBuildWorks -->
        <CallTarget Targets="DoTheBuildWorks" />
    </Target>    

</Project>

Just drop it into a folder and run MSBuild. Be sure to use the /v:diag command line switch so you see the full output.

I piped the results to a file and (with some bits trimmed out) this is the result:

Microsoft (R) Build Engine Version 3.5.30729.1
[Microsoft .NET Framework, Version 2.0.50727.3074]
Copyright (C) Microsoft Corporation 2007. All rights reserved.

Build started 7/23/2009 4:25:13 PM.
Project "O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj" on node 0 (default targets).
Initial Properties:
ALLUSERSPROFILE = C:\ProgramData
...Many env vars cut out here
TestValue = false

Building with tools version "3.5".
Target "PropertyIsNotSeenProperly: (TargetId:4)" in file "O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj" from project "O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj":
Using "Message" task from assembly "Microsoft.Build.Tasks.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "Message" (TaskId:0)
  TestValue (place1)=true (TaskId:0)
Done executing task "Message". (TaskId:0)
Using "CallTarget" task from assembly "Microsoft.Build.Tasks.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "CallTarget" (TaskId:1)
Target "DoTheBuildSkips" skipped, due to false condition; ('$(TestValue)' == 'true') was evaluated as ('false' == 'true').
Done executing task "CallTarget". (TaskId:1)
Done building target "PropertyIsNotSeenProperly" in project "Test.proj".: (TargetId:4)
Target "DetermineTestValue: (TargetId:0)" in file "O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj" from project "O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj":
Task "Message" (TaskId:2)
  TestValue=true (TaskId:2)
Done executing task "Message". (TaskId:2)
Done building target "DetermineTestValue" in project "Test.proj".: (TargetId:0)
Target "PropertyWorks: (TargetId:5)" in file "O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj" from project "O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj":
Task "CallTarget" (TaskId:3)
Target "DoTheBuildWorks: (TargetId:2)" in file "O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj" from project "O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj":
Task "Message" (TaskId:4)
  Did the Build (works) (TaskId:4)
Done executing task "Message". (TaskId:4)
Done building target "DoTheBuildWorks" in project "Test.proj".: (TargetId:2)
Done executing task "CallTarget". (TaskId:3)
Done building target "PropertyWorks" in project "Test.proj".: (TargetId:5)
Done Building Project "O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj" (default targets).

Project Performance Summary:
      107 ms  O:\Dev\Darin\Articles\MSBuild Property Failure\Test.proj   1 calls

Target Performance Summary:
        0 ms  Build                                      1 calls
        5 ms  DoTheBuildWorks                            1 calls
        5 ms  DetermineTestValue                         1 calls
       11 ms  PropertyWorks                              1 calls
       21 ms  PropertyIsNotSeenProperly                  1 calls

Task Performance Summary:
       12 ms  Message                                    3 calls
       13 ms  CallTarget                                 2 calls

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:00.11

(I’ve highlighted the key lines)

This file essentially has a Build target that is just used to kick off two other targets, that are the actual tests.

The first, PropertyIsNotSeenProperly illustrates the problem. The second, PropertyWorks, works just fine.

Notice that the TestValue property starts out false, but in both cases, it does appear to get set to true.

However, at the point that the condition for the DoTheBuildSkips target is evaluated, the value of TestValue must be false, which should be impossible.

I’ve read that the CallTarget is a bit of a deprecated method anyway, so maybe this is all just a problem of old code that hasn’t been properly updated along with the rest of MSBuild.

But, I’ve found that using CallTarget on occasion can simplify some aspects of debugging larger MSBuild scripts, so that’s why I’ve used it. Further, for me anyway, it makes the script a lot easier to read when the steps that it entails are laid out in a list, one CallTarget after another, instead of one target being dependent on another, which depends on another, etc, etc.

Something to be aware of.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*