Is there a neat way to create "boolean" properties to use in MSBuild? I can evaluate the expression inside a Condition attribute, but not inside the Value attribute of a CreateProperty task.
I'd like to do something like this:
<CreateProperty Value="'$(IncludeInBuild)'=='' OR
'$([System.Text.RegularExpressions.Regex]::IsMatch($(MSBuildProjectFullPath),
$(IncludeInBuild)'=='True'">
<Output TaskParameter="Value" PropertyName="MatchesInclude" />
</CreateProperty>
What that gives me is not True or False, but
''=='' OR '$([System.Text...
Can I evaluate a boolean expression and set a property with the result? My workaround now is just to repeat the expression in Condition attributes wherever I need it.
How about creating a default property 'false' with a condition to assign true if the condition passes?
<PropertyGroup>
<MatchesInclude>false</MatchesInclude>
<MatchesInclude Condition="'$(IncludeInBuild)'=='' OR
'$([System.Text.RegularExpressions.Regex]::IsMatch($(MSBuildProjectFullPath),
$(IncludeInBuild)'=='True'">true</MatchesInclude>
</PropertyGroup>
Related
This feels like it's so simple, but I cannot get it to work.
All I'm trying to achieve is a filtered list of the embedded resources. I've tried various approaches but I can't seem to get it right.
Here's what I thought was the right solution:
<ItemGroup>
<AllEmbeddedResources Include="#(EmbeddedResource)" Condition="$(FullPath.Contains('Change')"/>
</ItemGroup>
Edit...
To clarify, the results are without the condition, the list is all embedded resources, with the condition, the group is empty.
I've tried this inside and outside of target's, and I've tried getting the full list in one group, and then filtering in a separate group. I know I'm just misunderstanding some fundamental part of msbuild syntax, I just can't seem to work it out. Looking forward to being shown my stupid mistake!
Inside a target, this can be done using the batching syntax for items and using the System.String.Copy method to be able to call instance functions on the string:
<Target Name="ListAllEmbeddedResources">
<ItemGroup>
<AllEmbeddedResources Include="#(EmbeddedResource)" Condition="$([System.String]::Copy(%(FullPath)).Contains('Change'))" />
</ItemGroup>
<Message Importance="high" Text="AllEmbeddedResources: %(AllEmbeddedResources.Identity)" />
</Target>
Note that this syntax only works inside a target and not during static evaluation (item group directly under the <Project> node).
The Condition Attribute must return a boolean, and it operates on each element of the itemgroup.
You can access each element using %(Identity).
Say you have some unfiltered itemgroup called UnfilteredItems, and you want to filter those into a group called MyFilteredItems, using some regex pattern.
<ItemGroup>
<MyFilteredItems Include="#(UnfilteredItems)" Condition="$([System.Text.RegularExpressions.Regex]::Match(%(Identity),'.*\\bin\\.*').Success)"/>
</ItemGroup>
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.
I have not found a correct syntax that would allow a transform modifiers to use property functions. For example, if we want to trim each filename in a list starting with the "lib" string:
<ListWithoutLib>#(MyOriginalList->%(Filename.TrimStart("lib"))</ListWithoutLib>
Is there any msbuild voodoo that could be written to accomplish this?
There is more than one issue in the question.
First, TrimStart doesn't have an overload that accepts a string and it doesn't remove a substring from the beginning of a string. Instead TrimStart accepts a set of chars and remove all instances of each char from the beginning of string.
For example in C#
"libFoo.dll".TrimStart('l', 'i', 'b')
will produce 'Foo.dll' and
"bilbobaggins.dll".TrimStart('l', 'i', 'b')
will produce 'obaggins.dll'.
But that issue is secondary to the question being asked.
You cannot use a property function inside of a metadata transform but you can apply a method of the string class as an item function.
<ItemGroup>
<MyOriginalList Include="libapple;libboat;cat;dog" />
</ItemGroup>
<PropertyGroup>
<ListWithoutLib>#(MyOriginalList->TrimStart('l'))</ListWithoutLib>
</PropertyGroup>
<Target Name="Default">
<Message Text="ListWithoutLib = $(ListWithoutLib)" />
</Target>
<!--
Output:
Default:
ListWithoutLib = ibapple;ibboat;cat;dog
-->
See String item functions.
This example shows how TrimStart can be called on an item collection but it doesn't do what you indicated that you need.
A solution to what you seem to need might be as follows:
<ItemGroup>
<SourceList Include="libapple;libboat;cat;dog"/>
</ItemGroup>
<PropertyGroup>
<libPrefix>lib</libPrefix>
</PropertyGroup>
<ItemGroup>
<MyOriginalList Include="#(SourceList)">
<IsStartsWithLibPrefix>$([System.String]::Copy('%(Filename)').StartsWith('$(libPrefix)'))</IsStartsWithLibPrefix>
<NameWithoutPrefix Condition="!'%(IsStartsWithLibPrefix)'">%(Filename)</NameWithoutPrefix>
<NameWithoutPrefix Condition="'%(IsStartsWithLibPrefix)'">$([System.String]::Copy('%(Filename)').Substring($(libPrefix.Length)))</NameWithoutPrefix>
</MyOriginalList>
</ItemGroup>
<Target Name="Default">
<Message Text="List of Filename:" />
<Message Text="#(MyOriginalList->' %(Filename)','%0d%0a')" />
<Message Text="List of NameWithoutPrefix:" />
<Message Text="#(MyOriginalList->' %(NameWithoutPrefix)','%0d%0a')" />
</Target>
<!--
Output:
Default:
List of Filename:
libapple
libboat
cat
dog
List of NameWithoutPrefix:
apple
boat
cat
dog
-->
There is a difference between item functions and property functions. Item functions are limited to being called on a collection reference, e.g. #(MyOriginalList). Item batching could be used to split the collection into references to the items with and without the lib prefix. Batching requires a target or task to batch over. The code that is shown forgoes using item functions and instead uses property functions. This allows all the 'work' to be done in the ItemGroup definition.
Assuming the inputs are not static, we need to determine which filenames have the prefix.
Although metadata values are strings, string methods can't be called on metadata values. A well known work-around is to create a new string object from the metadata value and then call a string method. [System.String]::Copy('%(Filename)') is creating a string object that is a copy of the value of the Filename metadata. The code $([System.String]::Copy('%(Filename)').StartsWith('$(libPrefix)')) is a property function call and the IsStartsWithLibPrefix metadata will be set to either True or False.
Because ItemGroups can be self-referential, we can use the IsStartsWithLibPrefix metadata to define the NameWithoutPrefix metadata. The NameWithoutPrefix definition has a condition. For a given item in the MyOriginalList ItemGroup only one variant of the NameWithoutPrefix definition will be used. For Items that have the prefix another property function call is used. Substring is called to create a new string instance that doesn't have the prefix.
I'm executing a task in msbuild:
<MyTask ContinueOnError="true" OtherParam="Cheese"/>
<PropertyGroup>
<MyTaskResult>????how do I set this????</MyTaskResult>
<PropertyGroup>
I want to get the result of MyTask into a property (it's Execute method returns a bool) so I can test it and conditionally do stuff. Is this possible?
Thanks.
You could modify your task to put its results in an output parameter.
In mytask.targets, I have something like:
<UsingTask TaskName="DoStuff" AssemblyFile="....etc....."/>
<PropertyGroup>
<RequiredParamDefault>hello</RequiredParamDefault>
</PropertyGroup>
This task currently has a required parameter (which could be changed from required if necessary).
When the task is used:
<DoStuff RequiredParam="$(RequiredParamDefault)" OtherParam="wobble"/>
Currently, RequiredParam has to be specified everytime. Is there anyway that when UsingTask is defined, the default can be set up so it doesn't have to be specified on every use of DoStuff?
I know the default could be hardcoded in the assembly, but I'd like to be able to define different defaults with different UsingTask statements.
Thanks.
You can't do this at the UsingTask or Task but instead you can using properties that you pass into the task. For example.
<Target>
<PropertyGroup>
<ReqParam Condition=" '$(ReqParam)'=='' ">Param-Default-Value</ReqParam>
</PropertyGroup>
<DoStuff RequiredParam="$(ReqParam)" OtherParam="wobble"/>
</Target>
In this case I define the property ReqParam to be Param-Default-Value only if the property doesn't already have a value. This is not exactly what you are looking for, but it may be your best option unless you can change the task itself.