MSBuild ItemGroup with condition - msbuild

I don't know if ItemGroup is the right type to use. I will get 4 different booleans that will be true or false depending on choice.
I would like to fill up an ItemGroup with this "strings" depending on the true or false. Is that possible or what should I use?
Example
Anders = true
Peter = false
Michael = false
Gustaf = true
My ItemGroup should then have
Anders and Gustaf.
Is that possible or how should I solve that?

Since you have a bunch of items, it would be better to store them in an ItemGroup from the start since after all that is what it is meant for and it also allows transformations etc. For example this achieves what you want:
<ItemGroup>
<Names Include="Anders">
<Value>True</Value>
</Names>
<Names Include="Peter">
<Value>False</Value>
</Names>
<Names Include="Michael">
<Value>False</Value>
</Names>
<Names Include="Gustaf">
<Value>True</Value>
</Names>
</ItemGroup>
<Target Name="GetNames">
<ItemGroup>
<AllNames Include="%(Names.Identity)" Condition="%(Names.Value)==true"/>
</ItemGroup>
<Message Text="#(AllNames)"/> <!--AllNames contains Anders and Gustaf-->
</Target>
However if they must be properties, I do not think there is another way than enumerating them all manually like so:
<PropertyGroup>
<Anders>True</Anders>
<Peter>False</Peter>
<Michael>False</Michael>
<Gustaf>True</Gustaf>
</PropertyGroup>
<Target Name="GetNames">
<ItemGroup>
<AllNames Include="Anders" Condition="$(Anders)==true"/>
<AllNames Include="Peter" Condition="$(Peter)==true"/>
<AllNames Include="Michael" Condition="$(Michael)==true"/>
<AllNames Include="Gustaf" Condition="$(Gustaf)==true"/>
</ItemGroup>
<Message Text="#(AllNames)"/>
</Target>

Related

msbuild - how to remove a string within a property

If I have
<PropertyGroup>
<Prop1>C:\asdfsa\abc;C:\sadf\def;C:\asfddsa\abc;</Prop1>
</PropertyGroup>
How do I remove all entries that contain \abc?
I want final value of $(Prop1) to be C:\sadf\def.
A property doesn't have 'entries', it's merely a string. You could fiddle with string splitting and/or regexes to erase some parts from it. On the other hand MSBuild also has Items which are more like proper lists. Going round via them is probably easier:
<Target Name="RemoveItemsFromProperty">
<PropertyGroup>
<Prop1>C:\asdfsa\abc;C:\sadf\def;C:\asfddsa\abc;</Prop1>
</PropertyGroup>
<ItemGroup>
<Items Include="$(Prop1)"/>
<FilteredItems Include="#(Items)" Condition="! $([System.String]::Copy('%(Identity)').Contains('\abc'))"/>
</ItemGroup>
<PropertyGroup>
<Prop1>#(FilteredItems)</Prop1>
</PropertyGroup>
<Message Text="$(Prop1)" />
</Target>
edit ok the regex way is easier though I'm not 100% sure my pattern covers all cases:
<Target Name="RemoveItemsFromProperty">
<PropertyGroup>
<Prop1>C:\asdfsa\abc;C:\sadf\def;C:\asfddsa\abc;</Prop1>
</PropertyGroup>
<Message Text="$([System.Text.RegularExpressions.Regex]::Replace('$(Prop1)', ';[.^;]\\abc', ''))" />
</Target>

Parse key/value pairs from MSBuild property

Say I have a property like:
<MyProp>Foo=Bar;Hello=World</MyProp>
This seems like a reasonably common property pattern in MSBuild. How would I go about fetching the value "World"? In an ideal world this might look something like:
$(MyProp).(Hello)
Edit: To be clear, the property is not of my own creation, it is the output from another target that is out of my control, so I cannot change the way the property is declared.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MyProp>Foo=Bar;Hello=World</MyProp>
</PropertyGroup>
<Target Name="Foo">
<CreateItem Include="MyProp" AdditionalMetadata="$(MyProp)">
<Output TaskParameter="Include" ItemName="MyProp" />
</CreateItem>
<Message Text="Foo %(MyProp.Foo)" />
<Message Text="Hello %(MyProp.Hello)" />
</Target>
</Project>
You have two routes to follow as far as I am concerned
Declare a Property Group just like the following:
<PropertyGroup>
<Foo>Bar</Foo>
<Hello>World</Hello>
</PropertyGroup>
and then use the following method to access your properties
<Target Name="DoSomething">
<Message Text="Print this : $(Foo)" />
</Target>
or you might want to take ItemGroup Element approach like the following
<ItemGroup>
<MySolutionFiles Include="..\mySolution.sln" />
</ItemGroup>
<Target Name="PrintItems">
<Message Text="My Files: #(MySolutionFiles)" />
</Target>
You can have the following as well
<ItemGroup>
<MyProp
Include="Foo;Hello" />
</ItemGroup>
<Target Name="PrintMyItems">
<Message Text="MyProp: #(MyProp)" />
</Target>
If there is no choice over the input then one possible solution is parsing the input into an array and then taking it from there like the following:
<PropertyGroup>
<MyProp>Foo=Bar;Hello=World</MyProp>
<Split>$(MyProp.Split(';'))</Split>
</PropertyGroup>
and then play with the array items like the following:
<Target Name="DoPrint">
<Message text="$(Split[0])" />
</Target>
Split[0] item contains your Foo=Bar which can be split into two more strings just like above. This should keep you going for now.
You don't need a property group, it's just nice to have default values in case the user doesn't pass them.
For each property you pass, the syntax to access that property is $(PropertyName).
So if you pass:
msbuild.exe /p:P1=V1 /p:P2=V2;P3=V3
You would use the property name $(P1), $(P2), $(P3).
More on MsBuild properties here.

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>

Evaluate item multiple times

Not sure if that the right title for the question, but what I'm trying to do is this:
<ItemGroup>
<item1 Include="a;b;c;"/>
<item2 Include="x;y;z;"/>
<itemNames Include="item1;item2"/>
</ItemGroup>
<Target Name="DefaultName">
<Message Text="%(%(itemNames.Identity))"/>
</Target>
I'm expecting output to be:
a;b;c;
x;y;z;
Instead, the output is:
%(item1)
%(item2)
So my guess is that the Text property is parsed only once and the resulting string is not. Any workarounds arround this?
The following will produce the output you are looking for:
<ItemGroup>
<item1 Include="a;b;c;"/>
<item2 Include="x;y;z;"/>
<itemNames Include="item1;item2"/>
</ItemGroup>
<Target Name="DefaultName"
Outputs="%(itemNames.Identity)">
<PropertyGroup>
<ThisItem>%(itemNames.Identity)</ThisItem>
</PropertyGroup>
<ItemGroup>
<ThisItem Include="#($(ThisItem))" />
</ItemGroup>
<Message Text="#(ThisItem)" />
</Target>
...shows the following output...
DefaultName:
a;b;c
DefaultName:
x;y;z
Excerpted from MSBuild Trickery tricks #68 and 69

Referencing an MSBuild property using the contents of another property

I want to be able to reference an MSBuild (3) property using the contents of another property. For example:
<PropertyGroup>
<SelectVariable>Test</SelectVariable>
<TestVariable>1</TestVariable>
<FullVariable>2</FullVariable>
</PropertyGroup>
<Message Text="Value $($(SelectVariable)Variable)"/>
In this scenario, I want the contents of TestVariable outputted (1). Is this possible?
I don't believe that is possible. However, you could achieve a similar effect with ItemGroups:
<PropertyGroup>
<SelectVariable>Test</SelectVariable>
</PropertyGroup>
<ItemGroup>
<Variable Include="1">
<Select>Test</Select>
</Variable>
<Variable Include="2">
<Select>Full</Select>
</Variable>
</ItemGroup>
<Message Text="#(Variable)"
Condition=" '%(Select)' == '$(SelectVariable)' " />
It's a little clunky tho...
Sure this is possible. Just do:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SelectVariable>Test</SelectVariable>
<TestVariable>1</TestVariable>
<FullVariable>2</FullVariable>
</PropertyGroup>
<Target Name="Demo01">
<PropertyGroup>
<Value>$(SelectVariable)Variable</Value>
</PropertyGroup>
<Message Text="Value $(Value)"/>
</Target>
</Project>
The result is shown in the image below.
You could use the <Choose> task to achieve something similar, but (as Peter said) that's likely to be some distance from your desire to have something short and pithy.
Perhaps psake is the answer - it has no such arbitrary and puny limits when nesting expressions and parentheses :P