can msbuild resolve wildcard expressions resulting from a transform? - msbuild

I expected the code below to result in identical items for List and List2 (I have a single cpp1 project in the searched path).
<ItemGroup>
<src Include="cpp1"/>
<List Include="#(src -> '..\..\..\projects\**\%(identity).vcxproj')" />
<List2 Include="..\..\..\projects\**\cpp1.vcxproj" />
</ItemGroup>
But what I get is:
List: ..\..\..\projects\**\cpp1.vcxproj
List2: ..\..\..\projects\common\cpp1\cpp1.vcxproj
So it looks like the wildcard expression is not being expanded when its the result of a transform. What am I missing here?

You can get the extra transformation, but you need to add an additional item array, and it needs to have each potential element added one at a time, with a dependent target. You also need to pass the intermediate item specification through a property,
<ItemGroup>
<src Include="cpp1"/>
<List1a Include="#(src -> '..\..\..\projects\**\%(Identity).vcxproj')" />
<List2 Include="..\..\..\projects\**\cpp1.vcxproj" />
</ItemGroup>
<Target Name="TransformWithWildcards"
Outputs="%(List1a.Identity)">
<PropertyGroup>
<_ThisList1a>#(List1a)</_ThisList1a>
</PropertyGroup>
<ItemGroup>
<List1b Include="$(_ThisList1a)" />
</ItemGroup>
</Target>
<Target Name="Transform"
DependsOnTargets="TransformWithWildcards">
<Message Text="1a. %(List1a.Identity)" />
<Message Text="1b. %(List1b.Identity)" />
<Message Text="2. %(List2.Identity)" />
</Target>
#(List1a) is the same as your original #(List), and #(List1b) contains the results you expected, after TransformWithWildcards completes.

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>

How to filter an ItemGroup if the items not exist in another ItemGroup

I have a ItemGroup:
<ItemGroup>
<MainItem Include="A;B;C;D;E;F" />
</ItemGroup>
I would like to filter from another ItemGroup if the items not exist in the above ItemGroup:
<ItemGroup>
<MyItem Include="A;C;G;H" />
</ItemGroup>
<ItemGroup>
<Filtered Include="#(MyItem)" Condition="If %(MyItem.Identity) not exists in #(MainItem)" />
</ItemGroup>
I expect #(Filtered) = 'G;H'.
Possibly there is a way to do this using batching as you tried, but I didn't find it immediately and even if it's possible it won't be as simple and elegant as this one:
<ItemGroup>
<Filtered Include="#(MyItem)" Exclude="#(MainItem)" />
</ItemGroup>

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.

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

Can't get MSBuild Community Task RegexReplace to work

I'm trying to copy a bunch of files whose names begin with the prefix DR__, but the copies must have that prefix removed. That is, DR__foo must be copied as foo. I'm trying this, which is based in the example provided in the documentation (the .chm):
<Target Name="CopyAuxiliaryFiles">
<MakeDir Directories="$(TargetDir)Parameters" Condition="!Exists('$(TargetDir)Parameters')" />
<ItemGroup>
<ContextVisionParameterFiles Include="$(SolutionDir)CVParameters\DR__*" />
</ItemGroup>
<Message Text="Files to copy and rename: #(ContextVisionParameterFiles)"/>
<RegexReplace Input="#(ContextVisionParametersFiles)" Expression="DR__" Replacement="">
<Output ItemName ="DestinationFullPath" TaskParameter="Output" />
</RegexReplace>
<Message Text="Renamed Files: #(DestinationFullPath)"/>
<Copy SourceFiles="#(ContextVisionParameterFiles)" DestinationFiles="#(DestinationFullPath)" />
</Target>
DestinationFullPath comes out empty (or that's what I see when I display it with Message). Thus, Copy fails because no DestinationFiles are specified. What's wrong here?
Edit: ContextVisionParameterFiles is not empty, it contains this:
D:\SVN.DRA.WorkingCopy\CVParameters\DR__big_bone.alut;D:\SVN.DRA.WorkingCopy\CVParameters\DR__big_medium.gop
They're actually 40 files, but I trimmed it for the sake of clarity
Got it! It seems to have been the combination of a stupid error and a seemingly compulsory parameter. As for the first one, there were two Targets called CopyAuxiliaryFiles. As for the second one, it seems the Count parameter is needed.
The final, working version:
<Target Name="CopyCvParameters">
<ItemGroup>
<CvParamFiles Include="$(SolutionDir)CVParameters\DR__*" />
</ItemGroup>
<Message Text="Input:
#(CvParamFiles, '
')"/>
<!-- Replaces first occurance of "foo." with empty string-->
<RegexReplace Input="#(CvParamFiles)" Expression="^.*DR__" Replacement="$(TargetDir)Parameters\" Count="1">
<Output ItemName ="RenamedCvParamFiles" TaskParameter="Output" />
</RegexReplace>
<Message Text="
Output RenamedCvParamFiles:
#(RenamedCvParamFiles, '
')" />
<Copy SourceFiles="#(CvParamFiles)" DestinationFiles="#(RenamedCvParamFiles)" SkipUnchangedFiles="True" />
</Target>
Notice that:
I renamed the Target to solve the name collision (Why doesn't Visual Studio detect this as an error?)
I pretty-printed the ItemGroups with the #(CvParamFiles, '
') syntax, which seems to replace ; with line breaks
My regex replaces the absolute path and the prefix
Count="1" is now passed to RegexReplace