Reputation: 1011
I'm a bit confused about property scope within an MSBuild project. My understanding was that a property declared outside of a target would be global within the project file. For example:
<PropertyGroup>
<TestProp>Unassigned</TestProp>
</PropertyGroup>
<ItemGroup>
<TestEnvironments Include="Development;UAT;Production" />
</ItemGroup>
<Target Name="TestScope">
<PropertyGroup>
<TestProp>Some test data</TestProp>
</PropertyGroup>
<Message Text="Test property scope $(TestProp)" />
<CallTarget Targets="ForEachTestScope" />
</Target>
<Target Name="ForEachTestScope" Inputs="@(TestEnvironments)" Outputs="%(Identity).done">
<Message Text="Test property scope $(TestProp)" />
<Message Text="Selected environment: @(TestEnvironments)" />
</Target>
When executing the TestScope target the output is:
TestScope:
Test property scope Some test data
ForEachTestScope:
Test property scope Unassigned
Selected environment: Development
ForEachTestScope:
Test property scope Unassigned
Selected environment: UAT
ForEachTestScope:
Test property scope Unassigned
Selected environment: Production
I would have expected the value of $(TestProp) in the called target ForEachTestScope to be that which was set in the calling target, i.e. Some TestData
Is the scope of a "locally" declared PropertyGroup (or ItemGroup for that matter) always the scope of the target the declaration is contained in?
Upvotes: 0
Views: 189
Reputation: 5008
First, MSBuild is a declarative language. Don't use CallTarget
to try to write procedural style code in MSBuild. Targets are not subroutines and CallTarget
is not a subroutine call. There is no stack frame providing a scope to the called target.
Given the following test.proj file, which only ever defines TestProp
within a target:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Test0">
<PropertyGroup>
<TestProp Condition="'$(TestProp)' != ''">$(TestProp);Some test data</TestProp>
<TestProp Condition="'$(TestProp)' == ''">Some test data</TestProp>
</PropertyGroup>
<Message Text="$(TestProp)" />
</Target>
<Target Name="Test1">
<PropertyGroup>
<TestProp Condition="'$(TestProp)' != ''">$(TestProp);Some other test data</TestProp>
<TestProp Condition="'$(TestProp)' == ''">Some other test data</TestProp>
</PropertyGroup>
<Message Text="$(TestProp)" />
</Target>
</Project>
The commands msbuild test.proj /t:"test0;test1"
and msbuild test.proj /t:"test1;test0"
will produce different outputs.
The outputs will be
Test0:
Some test data
Test1:
Some test data;Some other test data
and
Test1:
Some other test data
Test0:
Some other test data;Some test data
respectively.
Coming back to CallTarget
, the task documentation explains that
When using CallTarget, MSBuild evaluates the called target in a new scope, as opposed to the same scope it's called from. This means that any item and property changes in the called target are not visible to the calling target. To pass information to the calling target, use the TargetOutputs output parameter.
Your testing with ForEachTestScope2
demonstrates this.
Rewriting your code to not use CallTarget
might look like the following (but I'm guessing at your intents).
<ItemDefinitionGroup>
<TestEnvironments>
<TestProp>Unassigned</TestProp>
</TestEnvironments>
</ItemDefinitionGroup>
<ItemGroup>
<TestEnvironments Include="Development;UAT;Production" />
</ItemGroup>
<Target Name="TestEnvironment" DependsOnTargets="SetTestData" Inputs="@(TestEnvironments)" Outputs="%(Identity).done">
<Message Text="Selected environment: @(TestEnvironments->'Test %(identity) with %(TestProp)')" />
</Target>
<Target Name="SetTestData">
<Message Text="in SetTestData"/>
<ItemGroup>
<TestEnvironments Condition="'%(identity)' == 'Development'">
<TestProp>Some development test data</TestProp>
</TestEnvironments>
<TestEnvironments Condition="'%(identity)' != 'Development'">
<TestProp>Some test data</TestProp>
</TestEnvironments>
</ItemGroup>
</Target>
Running the TestEnvironment
target produces:
SetTestData:
in SetTestData
TestEnvironment:
Selected environment: Test Development with Some development test data
TestEnvironment:
Selected environment: Test UAT with Some test data
TestEnvironment:
Selected environment: Test Production with Some test data
Note that a default of Unassigned
is created for TestProp
. The SetTestData
target runs only once and is changing the value of TestProp
. SetTestData
may set different values for different environments.
Upvotes: 1