Learning Something New on MSBuild Targets and Conditions
I love learning something new. Especially something that goes against what I would have expected. Such is the case with MSBuild Targets and conditions. A few days ago I posted about Enforcing Build Agents in a Team Build. I challenged others to show me the better way to do what I wanted and Ibrahim Hashimi took up the gauntlet and did just that. Actually, he has challenged that post twice now and I learn something each time. Go Ibrahim! The latest post on MSBuild Conditions in Targets was especially interesting. Though I didn't agree with Ibrahim about not using Conditions on targets when I began reading it he makes an interesting case in his example that I would not have expected to see and didn't realize would be the case. I have since joined him in thought. I know I will now be much more careful about how I use conditions with my targets to ensure I get the expected outcome.
I had planned to write out a post saying that what Ibrahim had failed to point out, though it made his point no less valid, is that this only occurs if you call the target explicitly such as in his example of msbuild /t:Demo or if you did the following calling msbuild /t:InitTarget.
1: <?xml version="1.0" encoding="utf-8"?>
2: <Project ToolsVersion="2.0"
3: xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
4: DefaultTargets="Demo">
5:
6: <PropertyGroup>
7: <AllowTarget>true</AllowTarget>
8: </PropertyGroup>
9:
10: <Target Name="InitTarget">
11:
12: <CallTarget Targets="Demo" />
13:
14: </Target>
15:
16: <Target Name="SupressTarget">
17: <CreateProperty Value="false">
18: <Output PropertyName="AllowTarget" TaskParameter="Value"/>
19: </CreateProperty>
20: <Message Text=" ==== SupressTarget ==== " Importance="high"/>
21: <Message Text="AllowTarget: $(AllowTarget)"/>
22: </Target>
23:
24: <Target Name="Demo" Condition="'$(AllowTarget)'=='true'"
25: DependsOnTargets="SupressTarget">
26: <Message Text=" ===== Demo ===== " Importance="high" />
27:
28: <Message Text="AllowTarget: $(AllowTarget)"/>
29: </Target>
30: </Project>
I had then planned to go on and say (wrongly) that if you had it implicitly called as would be the case below you would get the often expected results of the Demo target not running- msbuild /t:InitTarget:
1: <?xml version="1.0" encoding="utf-8"?>
2: <Project ToolsVersion="2.0"
3: xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
4: DefaultTargets="Demo">
5:
6: <PropertyGroup>
7: <AllowTarget>true</AllowTarget>
8: </PropertyGroup>
9:
10: <Target Name="InitTarget" DependsOnTargets="Demo">
11:
12: <Message Text=" ===== InitTarget ===== " Importance="high" />
13:
14: </Target>
15:
16: <Target Name="SupressTarget">
17: <CreateProperty Value="false">
18: <Output PropertyName="AllowTarget" TaskParameter="Value"/>
19: </CreateProperty>
20: <Message Text=" ==== SupressTarget ==== " Importance="high"/>
21: <Message Text="AllowTarget: $(AllowTarget)"/>
22: </Target>
23:
24: <Target Name="Demo" Condition="'$(AllowTarget)'=='true'"
25: DependsOnTargets="SupressTarget">
26: <Message Text=" ===== Demo ===== " Importance="high" />
27:
28: <Message Text="AllowTarget: $(AllowTarget)"/>
29: </Target>
30: </Project>
I had expected that since it set up my tree for execution by walking backwards up the dependencies the engine would say - Let's see you want to run InitTarget which means I need to include Demo which means I need to include SuppressTarget so here is what I'll run for you and evaluate if each target runs as I get to it:
- SuppressTarget
- Demo
- InitTarget
Imagine my surprise when I ran the above script and got the following output:
DOH!
Not what I expected. I would have bet the farm that as long as the target was not explicitly called but was executed based upon dependencies only that I would have received the results I expected. I would have lost the farm and man oh man would my wife have been upset. I suspect this is because I don't realize what that DependsUponTargets really gets translated into. Which is the point Ibrahim was making not about me specifically but about room for confusion in general. The target has already been chosen to exist and run. Very interesting.
I plan to play around with this a bit more. It may bring to light I have some scripts that don't actually work the way I expect. Hmm... I must digest this.