Is it possible to dependency-check multiple inputs for each output when using MSBuild batching?
I thought I'd found a solution to this by constructing my inputs list in the metadata of the output file, as follows:
<ItemGroup>
<Foo Include="output1">
<Inputs>input1a;input1b</Inputs>
</Foo>
<Foo Include="output2">
<Inputs>input2a;input2b</Inputs>
</Foo>
</ItemGroup>
<Target Name="_CompileFoo" Outputs="#(Foo)" Inputs="%(Foo.Inputs)">
<FooCompiler Src="%(Foo.Inputs)" Out="#(Foo)" />
</Target>
However, MSBuild complains that the file "input1a;input1b" does not exist. It seems that the string->items conversion takes place before the expression evaluation.
Is there any solution to this other than writing my own dependency checking?
Checking multiple dependencies works if the item group is set up the other way round with the compilation result as metadata.
<ItemGroup>
<Foo Include="input1a">
<Result>output1</Result>
</Foo>
<Foo Include="input1b">
<Result>output1</Result>
</Foo>
<Foo Include="input2a">
<Result>output2</Result>
</Foo>
<Foo Include="input2b">
<Result>output2</Result>
</Foo>
</ItemGroup>
<Target Name="_CompileFoo" Inputs="#(Foo)" Outputs="%(Result)">
<FooCompiler Overwrite="true" Src="#(Foo)" Out="%(Foo.Result)"/>
</Target>
And instead of manually converting the Foo item group, you can transform this in a prerequisite target building a new item group _Foo as follows.
<ItemGroup>
<Foo Include="output1">
<Inputs>input1a;input1b</Inputs>
</Foo>
<Foo Include="output2">
<Inputs>input2a;input2b</Inputs>
</Foo>
</ItemGroup>
<Target Name="_PrepareItemsForCompileFoo">
<ItemGroup>
<_Foo Include="%(Foo.Inputs)">
<Result>%(Foo.Identity)</Result>
</_Foo>
</ItemGroup>
</Target>
<Target Name="_CompileFoo" DependsOnTargets="_PrepareItemsForCompileFoo" Inputs="#(_Foo)" Outputs="%(Result)">
<FooCompiler Overwrite="true" Src="#(_Foo)" Out="%(_Foo.Result)"/>
</Target>
Related
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>
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.
I know I can do the following
<PropertyGroup>
<Foo>Bar</Foo>
<Foo1>Bar1</Foo1>
<Foos>$(Foo) $(foo1)</Foos>
</PropertyGroup>
<Target Name="Def">
<Message Text="$(Foos)"/>
</Target>
and get Bar Bar1
But this doesn't scale very well if you have many properties in the PropertyGroup.
Is there any way to reference a PropertyGroup or some other node and have MSBuild do the hard work for you?
I know the PropertyGroup element doesn't support it but imagine being able to do
<PropertyGroup Name="Bob">
<Foo>Bar</Foo>
<Foo1>Bar 1</Foo1>
</PropertyGroup>
<Target Name="Def">
<Message Text="$(Bob)"/>
</Target>
and get Bar Bar 1
This can be achieved using an ItemGroup and #() notation.
Example
<ItemGroup>
<Foo Include="Bar"/>
<Foo Include="Bar1"/>
</ItemGroup>
<Message Text="#(Foo)"/>
prints Bar;Bar1
notice the # symbol joins the items wt a semicolon by default. We can change this with a second paramter to #(..).
<ItemGroup>
<Foo Include="Bar"/>
<Foo Include="Bar1"/>
</ItemGroup>
<Message Text="#(Foo, ' ')"/>
prints Bar Bar1
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
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.