I have an ItemGroup, and I use its metadata as identifiers in my MSBuild project for batch processing. For example:
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="RunUnitTestsStep-%(TestSuite.Filename)-%(TestSuite.Extension)"
Message=" - Unit Tests: %(TestSuite.Filename): %(TestSuite.Extension)">
<Output
TaskParameter="Id"
PropertyName="RunUnitTestsStepId-%(TestSuite.Filename)-%(TestSuite.Extension)" />
</BuildStep>
However, this will not work, because there is a dot in the Extension, which is invalid character for an Id (in the BuildStep task). Thus, the MSBuild always fails on the BuildStep task.
I've been trying to remove the dot, but with no luck. Maybe there is a way to add some metadata to en existing ItemGroup? Ideally, I would like to have something like %(TestSuite.ExtensionWithoutDot). How can I achieve that?
I think you are slightly confused about what the <Output> element is doing here - it will create a property named with the value in the PropertyName attribute, and will set the value of that property to be value of the Id output from the BuildStep task. You have no influence on the value of Id - you just store it in a property for later reference in order to set the status of the build step
With that in mind, I can't see why you are concerned that the Property created would have a name that would include the concatenation of the extension. As long as the property name is unique, you can reference it later in a subsequent BuildStep task, and I presume your testsuite filename is enough to indicate uniqueness.
In fact, you could avoid having to create unique properties that track each testsuite/buildstep pair if you did Target batching:
<Target Name="Build"
Inputs="#(TestSuite)"
Outputs="%(Identity).Dummy">
<!--
Note that even though it looks like we have the entire TestSuite itemgroup here,
We will only have ONE - ie we will execute this target *foreach* item in the group
See http://beaucrawford.net/post/MSBuild-Batching.aspx
-->
<BuildStep
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Name="RunUnitTestsStep-%(TestSuite.Filename)-%(TestSuite.Extension)"
Message=" - Unit Tests: %(TestSuite.Filename): %(TestSuite.Extension)">
<Output
TaskParameter="Id"
PropertyName="TestStepId" />
</BuildStep>
<!--
..Do some stuff here..
-->
<BuildStep Condition=" Evaluate Success Condition Here "
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(TestStepId)"
Status="Succeeded" />
<BuildStep Condition=" Evaluate Failed Condition Here "
TeamFoundationServerUrl="$(TeamFoundationServerUrl)"
BuildUri="$(BuildUri)"
Id="$(TestStepId)"
Status="Failed" />
</Target>
Related
I tried to have a condition on a Target tag, but resulted with the error:
target has a reference to item metadata. References
to item metadata are not allowed in target conditions unless they are part of an item transform.
So i found this work around:
How to add item transform to VS2012 .proj msbuild file
and tried to implement it, but i can't figure up what i am doing wrong because it's not working as expected.
<CallTarget Targets="CopyOldWebConfigJs" />
<Target Name="CopyOldWebConfigJs"
Inputs="#(ContentFiltered)"
Outputs="%(Identity).Dummy"
DependsOnTargets="webConfigJsCase">
<Message Text="web.config.js Case" />
</Target>
<!-- New target to pre-filter list -->
<Target Name="webConfigJsCase"
Inputs="#(FileToPublish)"
Outputs="%(Identity).Dummy">
<ItemGroup>
<ContentFiltered Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('%(FileToPublish.Filename)%(FileToPublish.Extension)', 'web.config.js'))" />
</ItemGroup>
</Target>
I thought that Inputs="#(ContentFiltered)" will contain the lines that DependsOnTargets="webConfigJsCase" find.
But when i run it , i am getting this message: Skipping target "CopyOldWebConfigJs" because it has no inputs.
I know for a fact that the regex work, and it do find a filename_ext that equals web.config.js so it return True
What do i do or understand wrong?
In <ItemGroup><Item/></ItemGroup>, no change will be made to the Item item because no action was specified. If you want to add entries to the item, you must specify Include="".
The <Item/> documentation describes the various attributes for item elements inside of an <ItemGroup/>. Note that at the top-level of an MSBuild file, directly under the <Project/> element, you would use the attributes Include and Exclude while in a <Target/> you would use the attributes Include and Remove. Not including any attributes at all is nonsensical and—as far as I know—no different than simply deleting the entire line. I am surprised MSBuild doesn’t throw an error or warning this is almost certainly a mistake and not intentional.
The Inputs and Outputs attributes on your <Target Name="webConfigJsCase"/> are unnecessary. In fact, they slow MSBuild down by making it loop over the target unnecessarily. You can filter just in the <Item/> like this:
<Target Name="webConfigJsCase">
<ItemGroup>
<ContentFiltered Condition="'%(FileToPublish.Filename)%(FileToPublish.Extension)' == 'web.config.js'" Include="#(FileToPublish)" />
</ItemGroup>
</Target>
Additionally, I assume that you intended your regular expression to match web.config.js but not match webaconfigbjs. You don’t need to use an advanced feature like Regular Expressions here because MSBuild’s built-in condition operators already support simple string comparison. If fixed the condition above to be more readable.
<SqlToMetadataMultiTask ConnectionString="$(ConnectionString)">
<Output TaskParameter="Items" ItemName="MultiStats" />
<Output TaskParameter="Columns" ItemName="MultiColumns" />
</SqlToMetadataMultiTask>
<PropertyGroup>
<OutputFormat>#(MultiColumns,',')</OutputFormat>
</PropertyGroup>
<Message Text="Columns=#(MultiColumns,',')"/>
<WriteLinesToFile File="SqlMetricsMulti.csv" Overwrite="true" Lines="#(MultiColumns,',')" />
<WriteLinesToFile File="SqlMetricsMulti.csv" Overwrite="false"
Lines="#(MultiStats->'%(db),%(num_procs),%(len_procs),%(cursors_refs),%(tt_refs),%(ifs),%(cases),%(where),%(join),%(ands),%(ors)')" />
I have a row for each database, and write out the column headers, then the metadata-stored metrics for each database.
Can I make this task more generic so that the data output columns are generated dynamically just like the Column headers are being done? In some ways this would be custom metadata whitelisting by another item group dynamically based on the input.
See this answer in the link below, which is somewhat similar, writing out item meta data to a file. You'll need to check out the docs for ITaskItem, specifically ITaskItem.CloneCustomMetadata to get the generic behavior you're looking for.
Passing Items to MSBuild Task
So I'm kinda getting the hang of writing in MSBuild.
I like the idea storing things in ItemGroups because its easy to iterate, and you can have multple fields.
That makes them sort of like a class (more like a struct) but they respond to the iteration syntax of targets and tasks in a way that feels like a lambda expression.
However I continue to run into situation where I want to access a value of a particular item in an item group, that is, to access it like a property.
In that case I run into the issue of how to isolate a single item within the group.
When the item group is batching in a target or task, because of addressing using a metadata name or because of addressing with the common itemgroup name, you can utilize the value of the 'current' item i.e. #(ItemsName) or %(MetaDataName).
But I want to do things like: use an Item group as a System.Configuration class that contains the values of the entries in a section of a config file. Therefore the normal thing would be to name the ItemGroup itself to match the section name in the config file. however, the ItemGroup is not an addressable element that is accessible through the build engine interface, only the items themselves are addressable.
It might be nice to individually name the items in an ItemGroup rather than name them all the same and use the Include or a metadata field like to distinguish among them. This makes them behave like properties in that they are individually addressable as distinct items. so you could easily use their values in Conditions this way: '#(UniqueItemName->'%(Value)').
However, then the iterable features are essentially lost.
To narrow this down, presume i have a config file that gets read into an Item group by and xml task so that the element names in a section become the name of the items in the item group and attributes of each config file element are attributes that become metadata:
<configItemFlag name="displayDebugMessages" value="true" note="use with abandon" />
<configItemFlag name="displaySecurityValueMessages" value="false" note="use with caution" />
When I want to test this in a Condition, I need to narrow it down to something like this:
<Messge Text="Debug Message: you are debugging!" Condition="'#(configItemFlag->'%(Name)')' == 'displayDebugMessages' AND '#(configItemFlag->'%(Value)')' == 'true'/>
But this only evaluates the comparison and frequently does not evaluate to a single boolean.
So is there any way to syntacticly get this down to a dependable test?
Does this work for what you are trying to do?
<ItemGroup>
<ConfigItemFlag Include="displayDebugMessages">
<Value>true</Value>
<Note>use with abandon</Note>
</ConfigItemFlag>
<ConfigItemFlag Include="displaySecurityValueMessages">
<Value>false</Value>
<Note>use with caution</Note>
</ConfigItemFlag>
</ItemGroup>
<Target Name="Build">
<Message
Condition="
'%(ConfigItemFlag.Identity)' == 'displayDebugMessages' AND
'%(Value)' == 'true'"
Text="Debug Message: you are debugging, %(Note)!"
/>
</Target>
Output:
Build:
Debug Message: you are debugging, use with abandon!
(response to comment)
...the only thing I can offer to be able to use meta as properties isn't all that great, unless the target will make heavy use of them throughout. Basically it involves flattening each item to properties by batching on the item and creating local properties with each batch.
<Target Name="BuildOne"
Outputs="%(ConfigItemFlag.Identity)">
<!-- flatten this batch to properties -->
<PropertyGroup>
<_Identity>%(ConfigItemFlag.Identity)</_Identity>
<_Value>%(ConfigItemFlag.Value)</_Value>
<_Note>%(ConfigItemFlag.Note)</_Note>
</PropertyGroup>
<!-- use meta as properties -->
<Message
Condition="
'$(_Identity)' == 'displayDebugMessages' AND
'$(_Value)' == 'true'"
Text="Debug Message: you are debugging, $(_Note)!"
/>
</Target>
<Target Name="Build" DependsOnTargets="BuildOne"
/>
It seems like you are running into some of the limitations of the msbuild scripting language. Have you thought about writing a custom task to perform what you are looking for? That way you would be able to bring the full power of a full programming language to bear against the simple conditional check you want to perform.
I wonder why in the following code, MsBuild refuses to set the Suffix Metadata. It does work with a CreateItem task instead of the ItemGroup Declaration (because CreateItem is computed at build time) but I can't do this here because this code is in a "property file" : the project has no target, it's just a bunch of properties/items I include in real projects.
<ItemGroup>
<Layout Include="Bla">
<PartnerCode>bla</PartnerCode>
</Layout>
<Layout Include="Bli">
<PartnerCode>bli</PartnerCode>
</Layout>
</ItemGroup>
<ItemGroup Condition="'$(LayoutENV)'=='Preprod'">
<LayoutFolder Include="Preprod">
<Destination>..\Compil\layout\pre\</Destination>
</LayoutFolder>
</ItemGroup>
<ItemGroup>
<Destinations Include="#(LayoutFolder)" >
<Suffix>%(Layout.PartnerCode)</Suffix>
</Destinations>
</ItemGroup>
Destinations is well built but the Suffix Metadata is not set.
As for now, I have duplicated the Destinations Definition in every project I needed it but it's not very clean. If someone has a better solution, I'm interested!
With MSBuild 4 you can use metadata from previous items in item declaration like this :
<ItemGroup>
<Layout Include="Bla">
<PartnerCode>bla</PartnerCode>
</Layout>
<Layout Include="Bli">
<PartnerCode>bli</PartnerCode>
</Layout>
</ItemGroup>
<ItemGroup>
<Destinations Include="#(Layout)" >
<Suffix>%(PartnerCode)</Suffix>
</Destinations>
</ItemGroup>
(It's strange that you batch on LayoutFolder and try to get Layout metadata. What value do you want as Suffix bla or bli?)
It appears that I try to set Metadata dynamically outside a target which is impossible.
I try to set the Suffix Metadata by batching over Layout items but Layout items are not properly set when the batching is done. The batching is done when msbuild parse my property files, it does not wait for Layout to be declared.
Nevertheless, like MadGnome pointed out, I can batch over LayoutFolder (which is the source items for my includes) because MSBuild does wait for it to be declared.
The issue you're encountering is that you're referring to metadata in a list. The %(Layout.PartnerCode) iterates through the ItemGroup of "Layout", which in this case returns 2 items. Even with 1 it causes undesired, unexpected results, as you're pointing to a list. MSBuild returns two meta tags and doesn't know which one you would want to have. The result being that it chooses none instead... or.. well, MSBuild ends up setting it to nothing.
I'd suggest setting a default ItemDefinition, like this (MSBuild 3.5)
<ItemDefinitionGroup>
<Layout>
<PartnerCode>%(Identity)</PartnerCode>
<Suffix>%(PartnerCode)</Suffix>
<Destination Condition="'$(LayoutENV)'=='Preprod'">..\Compile\layout\pre\</Destination>
</Layout>
</ItemDefinitionGroup>
And then define them as you would have.
<ItemGroup>
<Layout Include="Bla" />
<Layout Include="Bli" />
<Layout Include="Bloop">
<PartnerCode>B2</PartnerCode>
<Suffix>%(PartnerCode)</Suffix>
</Layout>
</ItemGroup>
Sidenotes
Note. Metadata seems to be only parsed once per definition group / itemgroup, so if you're setting PartnerCode, you'd also have to reset Suffix, as seen in the second example. I am not familiar with the behaviour in MSBuild 3.5, but it is the case in MSBuild 4.0.
Note. I'm assuming that you want your filename as a suffix, Identity does the trick, see here "MSBuild Well-known Item Metadata": (https://msdn.microsoft.com/en-us/library/ms164313.aspx), if this is not the case, you can always follow the custom override example or write your own function based on it. Read more on stuff like that here "MSBuild Property Functions": (https://msdn.microsoft.com/en-us/library/dd633440.aspx)
I need to create multiple /testcontainer: parameters to feed into a task that exec's MsTest.
I have the following :
<ItemGroup>
<TestFiles Include="$(ProjectPath)\**\UnitTest.*.dll" />
</ItemGroup>
for each match in TestFiles I would like to build a string like so:
"/testcontainer:UnitTest.SomeLibrary1.dll"
"/testcontainer:UnitTest.SomeLibrary2.dll"
"/testcontainer:UnitTest.SomeLibrary3.dll"
I am trying to use the internals of MSBuild without having to create a custom task, is this possible ?
TIA
It really depends on the usage of this afterwards. For example the task that you are sending it to, does it accept in an item list and do you want to invoke it once or multiple times?
If you want to invoke it once then you use the #(...) syntax, and if you want to invoke it many times then you do batching with the %(...) syntax.
To invoke once
<Message Text="Test Files: #(TestFiles->'/testcontainer:%(RecursiveDir)%(Filename)%(Extension)')"/>
To invoke many times
<Message Text="Test Files: /testcontainer:%(TestFiles.RecursiveDir)%(TestFiles.Filename)%(TestFiles.Extension)"/>
More info on batching at http://sedotech.com/Resources#batching
Try this:?
<Message Text="TestFiles= #(TestFiles-> '"%(Fullpath)"', ' ')" />
References:
MSBuild transforms
MSBuild: How to display an item list, separated by a comma.