MSBuild script default property best practices explanation - msbuild

In building an MSBuild script, I need to define a series of properties that are default but can be overridden when running the script. According to the following article, you should use a Conditional to default the property:
http://msdn.microsoft.com/en-us/library/ee240983.aspx
How Microsoft recommends:
<PropertyGroup>
<MyProperty Condition="'$(MyProperty)' == '' ">Default Value</MyProperty>
</PropertyGroup>
However, this behaves exactly the same way:
<PropertyGroup>
<MyProperty>Default Value Without Conditional</MyProperty>
</PropertyGroup>
So, if I have this Target and invoke it with either of the above, it has the same behavior:
<Target Name="DefaultsTest">
<Message Text="$(MyProperty)"></Message>
</Target>
Invocation:
msbuild build.xml /t:DefaultsTest /p:MyProperty="Overridden value"
Please explain the benefits of using the Condition attribute if you are only defaulting the same property that can be overridden from the invocation?
Update:
Here is a full simple config file to demonstrate: defaults.xml
<?xml version="1.0" encoding="utf-8" ?>
<Project DefaultTargets="DefaultsTest" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MyProperty Condition=" '$(MyProperty)' == '' ">MyProperty with Conditional</MyProperty>
<MyOtherProperty>MyOtherProperty without Conditional</MyOtherProperty>
</PropertyGroup>
<Target Name="DefaultsTest">
<Message Text="$(MyProperty)"></Message>
<Message Text="$(MyOtherProperty)"></Message>
</Target>
</Project>
This can be run simply as msbuild defaults.xml
or
msbuild defaults.xml /p:MyProperty="Changed Value" /p:MyOtherProperty="Changed as well"

You correctly noticed, that you can achieve behavior you want with unconditional assignment of property. The following project when built without /p: override on command line will produce Default Value. And when built using command msbuild myproj.proj /t:DefaultsTest /p:MyProperty=NewValue will produce NewValue.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MyProperty>Default Value</MyProperty>
</PropertyGroup>
<Target Name="DefaultsTest">
<Message Text="MyProperty=$(MyProperty)"></Message>
</Target>
</Project>
This is because any properties specified on MSBuild command line or provided as a parameter of task will be treated as Global Properties. For global properties, any assignment or modifications, either conditional or unconditional, are simply ignored -- global properties will remain constant throughout lifetime of the project execution.
The only difference in behavior between conditional assignment and unconditional will be if you use TreatAsLocalProperty attribute.
For example, consider the following project:
<Project TreatAsLocalProperty="Prop1;Prop2" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Prop1>Prop1 Default Value</Prop1>
<Prop2 Condition="$(Prop2) == ''">Prop2 Default Value</Prop2>
</PropertyGroup>
<Target Name="DefaultsTest">
<Message Text="Prop1=$(Prop1)"></Message>
<Message Text="Prop2=$(Prop2)"></Message>
</Target>
</Project>
Two properties -- Prop1 and Prop2 are both declared as local in the Project element. Prop1 is assigned unconditionally, while Prop2 is assigned using non-empty condition. Executing build command:
msbuild b.proj /t:DefaultsTest /p:Prop1=NewValue1 /p:Prop2=NewValue2
will produce output:
Prop1=Prop1 Default Value
Prop2=NewValue2
This means that in general case (if you are not absoluterly sure if property will be global or local), it is safer to use conditional assignment of the default value, because it works always.

Related

How to check for presence of a string in a property

If I have a property
<PropertyGroup>
<MyProp>abd;efg;hij;klm</MyProp>
</PropertyGroup>
How do I parse $(MyProp) to check the presence of klm?
You can use property functions to invoke the string Contains() method to check for string occurrences. While some other options using the Items (through an Include="$(MyProp)" and checking if an item with the expected identity exists) is also possible, conditions using property functions can be used on any msbuild element, both inside and outside of targets.
Example:
<Project>
<PropertyGroup>
<MyProp>abd;efg;hij;klm</MyProp>
</PropertyGroup>
<Target Name="Build">
<Message Importance="high" Text="klm is included!" Condition="$(MyProp.Contains('klm'))" />
<PropertyGroup>
<MyProp>;$(MyProp);</MyProp>
</PropertyGroup>
<Message Importance="high" Text="exactly klm is included!" Condition="$(MyProp.Contains(';klm;'))" />
</Target>
</Project>
The second approach - pre- and appending ; and checking for ;klm; - makes sure that the string is matched as a whole in the list can deal with ;aklm.

How to access ItemGroup Metadata as properties within MSBuild script

Is it possible using default MSBuild technology to access a listing within an item group as a property in msbuild? I know I can do this in a custom task in C#, but I am trying to use built-in capabilities if possible.
Example:
I have an item group:
<ItemGroup>
<SolutionToBuild Include="$(SolutionRoot)\Solutions\ClassLib\ClassLib.sln">
<Properties>
AssemblySigningKey=MySigningKey;
OutDir=$(BinariesRoot)\SomeLocation\;
LibraryName=ClassLib;
PlatformTarget=x86;
</Properties>
</SolutionToBuild>
<SolutionToBuild Include="$(SolutionRoot)\Solutions\BLAH\BLAH.sln">
<Properties>
ProjectType=Web;
</Properties>
</SolutionToBuild>
</ItemGroup>
I would like to extract the value of AssemblySigningKey, if it exists, and place this value into an MSBuild variable.
I have tried a few methods and the closest example I could find is using a tranformation within a separate target, but even this looks to be a bit of a hack, even if I could get the Condition to work I would then have to parse out the value splitting on the =. Is there no standard method to access this metadata within the item group?
<Target Name="TransformProps"
Inputs="%(SolutionToBuild.Identity)"
Outputs="_Non_Existent_Item_To_Batch_">
<PropertyGroup>
<IncludeProps>%(SolutionToBuild.Properties)</IncludeProps>
</PropertyGroup>
<ItemGroup>
<IncludeProps Include="$(IncludeProps)" />
<Solution Include="#(SolutionToBuild)">
<IncludeProps Condition="'True'=='True' ">#(IncludeProps ->'-PROP %(Identity)', ' ')</IncludeProps>
</Solution>
</ItemGroup>
</Target>
My main target would call into the tranform in the following manner:
<Target Name="Main" DependsOnTargets="TransformProps">
<Message Text="Solution info: %(Solution.Identity) %(Solution.IncludeProps)" />
</Target>
Items Metadata are declared and transformed using xml tags. It seems like you're using the MSBuild Task to build some solutions - the properties tag is a parameter specific to this task.
The conversion from comma separated list and items as you tried won´t help because, as you mentioned, you still have the equal sign as the link from the keys to the values. I think there´s no way of obtaining the signing key value without parsing. After all msbuild do not consider the list of properties as metadata, it is just a list of strings.
I did the script below to exemplify how msbuild declare and read metadata. It is not an option for you because your ItemGroup structure cannot be changed.
IMHO in this case you have no option but use a custom task and do the parsing. Use Inline Tasks if you´re building with msbuild 4.0.
<?xml version="1.0" encoding="UTF-8" ?>
<Project DefaultTargets="Main" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<SolutionToBuild Include="$(SolutionRoot)\Solutions\ClassLib\ClassLib.sln">
<AssemblySigningKey>MySigningKey123</AssemblySigningKey>
<Properties>
AssemblySigningKey=MySigningKey456;
OutDir=$(BinariesRoot)\SomeLocation\;
LibraryName=ClassLib;
PlatformTarget=x86;
</Properties>
</SolutionToBuild>
</ItemGroup>
<Target Name="TransformProps">
<PropertyGroup>
<MySigningKey>#(SolutionToBuild->'%(AssemblySigningKey)')</MySigningKey>
</PropertyGroup>
</Target>
<Target Name="Main" DependsOnTargets="TransformProps">
<Message Text="My desired Property Value: $(MySigningKey)" />
</Target>

msbuild, overwriting property value in different file

I am trying to modify property value depending on certain condition in another file.
For ex.
I have one file that calls target file.
<Import Project="sample.vcxproj"/>
<PropertyGroup>
<Gender>Boy</Gender>
<Search>UNIQUE_NAME</Search>
</PropertyGroup>
<Target Name="Build">
<callTarget Targets="SetName"/>
<Message Text="$(Person)"/>
</Target>
I have one file that includes item group to decide and target that modifies
<ItemGroup>
<Name Include="UNIQUE_NAME">
<Boy>DAVID</Boy>
<Girl>REBECCA</Girl>
</NAME>
</ItemGroup>
<Target Name="SetName">
<PropertyGroup Condition="'$(Search)'=='#(Name)'">
<Person>#(Name->'%($(Gender))')</Person>
</PropertyGroup>
</target>
But when I print 'Person' I get empty string. And I checked that 'SetName' is called and correct name is set.
What am I missing here?
This has to do with the accessibility of MSBuild properties, depending on whether you are using DependsOnTargets or CallTarget. When using DependsOnTargets you will have greater access to properties. That is why your example works when using that method.
There is an existing stackoverflow article that speaks to this issue.
It works fine using 'DependsOnTarget' attrib instead of callTarget task

Can I be sure that i receive correct item if I defined item in one msbuild target and get it in other one?

I have two different targets which are invoked with one collection of properties via msbuild task. In one target I define ItemGroup and in other one I recieve it. I invoke targets in the next way:
<MsBuild Projects="deploypkg.project" Properties="CurrentSite=%(SitesName.Identity)" Targets="TargetA"/>
<MsBuild Projects="deploypkg.project" Properties="CurrentSite=%(SitesName.Identity)" Targets="TargetB"/>
When in TargetB I refer defined in TargetA ItemGroup i get items defined only for current site(input property). It's exactly what i need but I'm not sure I can rely on it, because I found nothing about this possibility.
You are not sure, you can create test.project to test it. Something like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<Target Name="TargetA">
<Message Text="TargetA: CurrentSite = $(CurrentSite)"/>
</Target>
<Target Name="TargetB">
<Message Text="TargetB: CurrentSite = $(CurrentSite)"/>
</Target>
</Project>
Use test.project in your script:
<MsBuild Projects="test.project" Properties="CurrentSite=%(SitesName.Identity)" Targets="TargetA"/>
<MsBuild Projects="test.project" Properties="CurrentSite=%(SitesName.Identity)" Targets="TargetB"/>

MSBuild getting property substring before underscore symbol

In MSBuild I have a property which value is Name_Something. How can I get name part of this property.
With MSBuild 4
If you use MSBuild 4, you could use the new and shiny property functions.
<PropertyGroup>
<MyProperty>Name_Something</MyProperty>
</PropertyGroup>
<Target Name="SubString">
<PropertyGroup>
<PropertyName>$(MyProperty.Substring(0, $(MyProperty.IndexOf('_'))))</PropertyName>
</PropertyGroup>
<Message Text="PropertyName: $(PropertyName)"/>
</Target>
With MSBuild < 4
You could use the RegexReplace task of MSBuild Community Task
<PropertyGroup>
<MyProperty>Name_Something</MyProperty>
</PropertyGroup>
<Target Name="RegexReplace">
<RegexReplace Input="$(MyProperty)" Expression="_.*" Replacement="" Count="1">
<Output ItemName ="PropertyNameRegex" TaskParameter="Output" />
</RegexReplace>
<Message Text="PropertyNameRegex: #(PropertyNameRegex)"/>
</Target>
If I understand your question correctly you are trying to get the substring of a MSBuild property. There is no direct way to do string manipulation in MSBuild, like in NAnt. So you have two options:
1). Create separate variables for each part and combine them:
<PropertyGroup>
<Name>Name</Name>
<Something>Something</Something>
<Combined>$(Name)_$(Something)</Combined>
</PropertyGroup>
This works fine if the parts are known before hand, but not if you need to do this dynamically.
2). Write a customer MSBuild task that does the string manipulation. This would be your only option if it needed to done at runtime.
It looks like you could use Item MetaData instead of a Property:
<ItemGroup>
<Something Include="SomeValue">
<Name>YourName</Name>
<SecondName>Foo</SecondName>
</Something>
</ItemGroup>