I needed a reference on MSBuild property evaluation to link to from another post I've been working on, and discovered that such a thing doesn't really exist! (Or, alternatively, that my web search skills are not what they once were) Unfortunately, this is a rather confusing topic. If you don't believe me, try out the following quiz without looking at the answers:
Given the following MSBuild script, predict the output of each Message task invocation given the following command lines:
Here are the contents of sample.proj (note that $(MSBuildProjectFile) is the currently executing project file - namely sample.proj - so the MSBuild task invocations call back into the same project):
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <MyProperty Condition=" '$(MyProperty)'=='' ">Declared Value</MyProperty> </PropertyGroup> <Target Name="Build"> <Message Text="Build 1, MyProperty = $(MyProperty)" /> <CreateProperty Value="Programmatic Value"> <Output TaskParameter="Value" PropertyName="MyProperty"/> </CreateProperty> <Message Text="Build 2, MyProperty = $(MyProperty)" /> <CallTarget Targets="Internal" /> <MSBuild Projects="$(MSBuildProjectFile)" Targets="Internal" /> <MSBuild Projects="$(MSBuildProjectFile)" Targets="Internal" Properties="Foo=Bar" /> <MSBuild Projects="$(MSBuildProjectFile)" Targets="Internal" Properties="MyProperty=MSBuild Task Value" /> </Target> <Target Name="Internal"> <Message Text="Internal, MyProperty = $(MyProperty)" /> </Target> </Project>
Before getting to the answers, here are (roughly speaking) the property evaluation rules for MSBuild:
So - what are the outputs of the above project file? Here they are, at normal verbosity...
Case 1 (> msbuild sample.proj):
Project "c:\sample.proj" (default targets): Target Build: Build 1, MyProperty = Declared Value Build 2, MyProperty = Programmatic Value Target Internal: Internal, MyProperty = Declared Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)): __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)): Target Internal: Internal, MyProperty = Declared Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)): Target Internal: Internal, MyProperty = MSBuild Task Value
Project "c:\sample.proj" (default targets):
Target Build: Build 1, MyProperty = Declared Value Build 2, MyProperty = Programmatic Value Target Internal: Internal, MyProperty = Declared Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)):
__________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)):
Target Internal: Internal, MyProperty = Declared Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)):
Target Internal: Internal, MyProperty = MSBuild Task Value
In this case, MyProperty takes on its declarative value (as per rule 3) and is then overridden programmatically (as per rule 4). The CallTarget invocation repeats the process, and in this new scope MyProperty takes on its declarative value again. The first MSBuild invocation doesn't even execute, since MSBuild has already successfully executed the Internal target with the exact set of global properties (see this forum thread for details). The second MSBuild invocation has the same behavior as the CallTarget invocation. The final MSBuild invocation explicitly specifies a value for MyProperty, and this value overrides the declarative value.
Case 2 (> set MyProperty=Environment Variable Value, > msbuild sample.proj):
Project "c:\sample.proj" (default targets): Target Build: Build 1, MyProperty = Environment Variable Value Build 2, MyProperty = Programmatic Value Target Internal: Internal, MyProperty = Environment Variable Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)): __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)): Target Internal: Internal, MyProperty = Environment Variable Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)): Target Internal: Internal, MyProperty = MSBuild Task Value
Target Build: Build 1, MyProperty = Environment Variable Value Build 2, MyProperty = Programmatic Value Target Internal: Internal, MyProperty = Environment Variable Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)):
Target Internal: Internal, MyProperty = Environment Variable Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)):
In this case, MyProperty takes on a value from the environment (as per rule 2). Because it already has a value, the condition on the declarative value evaluates to false and the environment variable value is preserved.
Case 3 (> msbuild sample.proj /p:MyProperty="Command Line Value"):
Project "c:\sample.proj" (default targets): Target Build: Build 1, MyProperty = Command Line Value Build 2, MyProperty = Programmatic Value Target Internal: Internal, MyProperty = Command Line Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)): __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)): Target Internal: Internal, MyProperty = Command Line Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)): Target Internal: Internal, MyProperty = MSBuild Task Value
Target Build: Build 1, MyProperty = Command Line Value Build 2, MyProperty = Programmatic Value Target Internal: Internal, MyProperty = Command Line Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)):
Target Internal: Internal, MyProperty = Command Line Value __________________________________________________ Project "c:\sample.proj" is building "c:\sample.proj" (Internal target(s)):
In this case, MyProperty is passed in via the command-line and becomes a global property (as per rule 1). As such, it can only be overwritten programmatically (as per rule 4) or by being specified again as a global property via the MSBuild task (as per rule 5).
Did you get them all right? If so, you are an official MSBuild master... If not, you are not alone - this stuff is pretty confusing. Please let me know if you have advice on how I can make the explanations here easier to understand, etc.