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.
Related
I have the following definitions in my working msbuild project...
<MSBuild
Projects="$(MSBuildProjectFile)"
Condition="'#(FilesToCompile)' != ''"
Targets="buildcpp"
Properties="CPPFILE=%(FilesToCompile.FullPath);OBJFILE=$(ObjectFolder)\%(FilesToCompile.Filename).doj;IncludeDirs=$(IncludeDirs)"
/>
...followed by the definition of the target.
Notice how the definition of the target contains a call to another target compilecpp...
<Target Name="buildcpp">
<PropertyGroup>
<CompileDefines Condition="'$(PreprocessorDefinitions)' != ''">-D$(PreprocessorDefinitions.Replace(";"," -D"))</CompileDefines>
</PropertyGroup>
<Exec
EchoOff="true"
StandardOutputImportance="low"
StandardErrorImportance="low"
IgnoreExitCode="true"
ConsoleToMSBuild="true"
Command='
"$(CompilerExe)" ^
$(HWProcessor) ^
$(IncludeDirs) ^
$(CompilerOptions) ^
$(CompileDefines) ^
"$(CPPFILE)" ^
-MM
'>
<Output TaskParameter="ConsoleOutput" PropertyName="output_cppdeps"/>
<Output TaskParameter="ExitCode" PropertyName="exitcode_cppdeps"/>
</Exec>
<MSBuild
Projects="$(MSBuildProjectFile)"
Condition="'$(exitcode_cppdeps)' == '0'"
Targets="compilecpp"
Properties="INPUTFILES=$(BuildCppDeps)"
/>
</Target>
...which uses the property $(OBJFILE) even though it was never passed in by the caller
<Target Name="compilecpp" Inputs="$(INPUTFILES)" Outputs="$(OBJFILE)">
<Message Importance="high" Text="$(CPPFILE): Compiling..."/>
...
QUESTION
Since this msbuild works, I can infer that $(OBJFILE) is accessible; why is it accessible? What are the scope rules for properties?
When using the <MSBuild> task, this performs a new msbuild run similar to running msbuild.exe with arguments. In particular, passing in properties is similar to passing /p:PropName=Value arguments - it defines new "global properties" for this run.
During this inner build, the property is still there and accessible by additional inner builds (buildcpp -> compilecpp) unless overwritten. So OBJFILE is only accessible in compilecpp because it was defined as global property for a parent msbuild run. If compilecpp was somehow invoked directly, the property would not be defined (assuming it not set somewhere else). When you want to stop forwarding a global property, you'd need to use the MSBuild task's RemoveProperties parameter. So if you set RemoveProperties="OBJFILE", then it won't be pased on.
Fyi, in .NET Core projects, RemoveProperties is used to not forward a RuntimeIdentifier from a self-contained apps to referenced projects, which may not be able to build with this property set (due to missing restore information).
For more information, read the properties documentation - especially the section about global properties - and the MSBuild Task documentation (important part is the description for the Properties parameter). However, the fact that global properties are passed on isn't explicitly documented (though implied by the RemoveProperties).
Update: the documentation for global properties was updated to describe this behavior:
Global properties are also forwarded to child projects unless the
RemoveProperties attribute of the MSBuild task is used to specify the
list of proerties not to forward.
I read a build number from my TFS Team build which looks like "AB-1.2.3.4-CDE-REV.1". I want to edit this number and remove the last decimal point and make it look like "AB-1.2.3.4-CDE-REV1".
Usually when you want to manipulate strings in msbuild you're looking to use Property Functions. In the documentation of those you'll read you can use String functions so next up is figuring out which methods of System.String you need. In this case: LastIndexOf and Remove should do the trick:
<!-- BuildNumber property is fetched elsewhere -->
<PropertyGroup>
<BuildNumber>AB-1.2.3.4-CDE-REV.1</BuildNumber>
</BuildNumber>
<Target Name="ManipulateBuildNumber">
<PropertyGroup>
<BuildNumber>$(BuildNumber.Remove($(BuildNumber.LastIndexOf('.')),1))</BuildNumber>
</PropertyGroup>
<Message Text="New build number is $(BuildNumber)" />
</Target>
Thanks for the solution stijn. It works. I had figured out another lame and crude way of doing it.
<BuildNumber>AB-1.2.3.4-CDE-REV.1</BuildNumber>
<Part1>$(BuildNumber.Split('.')[0])</Part1>
<Part2>$(BuildNumber.Split('.')[1])</Part2>
<Part3>$(BuildNumber.Split('.')[2])</Part3>
<Part4>$(BuildNumber.Split('.')[3])</Part4>
<Part5>$(BuildNumber.Split('.')[4])</Part5>
<BuildNumber>$(Part1).$(Part2).$(Part3).$(Part4)$(Part5)</BuildNumber>
The intention in the title sounds more complex as it is
I have a script which looks like that:
<Import Project="MSBuild/Backend.msbuild.xml" />
<PropertyGroup>
<Data>SomeData</Data>
</PropertyGroup>
and I simply want to detect whether the Import statement is before or after the PropertyGroup because the Backend script looks like
<PropertyGroup>
<BasedOnData>$(Data)/magic.exe</BasedOnData>
</PropertyGroup>
If the Import statement comes first, BasedOnData just looks like /magic.exe which is wrong. If the Import comes last, everything is fine.
I tried to check the Data Property in a Target in Backend but at target-calling-time Data is already defined.
Resulting in
Data=SomeData
but
BasedOnData=/magic.exe
I could create an Error condition based whether $(BasedOnData)== '/magic.exe' but this is error prone as it relies on Data and BasedOnDatas value.
Having an Error condition in PropertyGroup does not work.
Any smarter solution to this known?
Should I fallback to CreateProperty in the first target so I am agnostic of the position of the Import statement? (This gets much more verbose and is not that easy to read than a plain nice PropertyGroup.)
Execution of MSBuild targets happens in separate pass, after all properties in property groups have been evaluated. So what you are observing is designed behavior.
To make import order checking in your case, the following should do the trick. Inside your Backend.msbuild.xml file add this:
<PropertyGroup>
<Data_Copy>$(Data)</Data_Copy>
</PropertyGroup>
This just makes a copy of whatever $(Data) value was at the time. Then in either same imported file or in any other target, add this <Error> task:
<Error Text="Import has to be specified after `Data` is defined" Condition="'$(Data_Copy)' == ''" />
This will break the build if Backend.msbuild.xml was imported before $(Data) is defined.
Is it a limitation of MSBuild 4.0 property functions that I cannot access a property from inside of one?
Here is an example that works just fine:
<PropertyGroup>
<PartialConnection>$(TargetConnectionString.Substring( 0 + 12))</PartialConnection>
</PropertyGroup>
Here is another example that doe snot work. (I replace the 0 with another property)
<PropertyGroup>
<LocationOfDataSource>$(TargetConnectionString.IndexOf("Data Source="))</LocationOfDataSource>
</PropertyGroup>
<Message Importance="high" Text="Location is = $(LocationOfDataSource)"/>
<PropertyGroup>
<PartialConnection>$(TargetConnectionString.Substring( $(LocationOfDataSource) + 12))</PartialConnection>
</PropertyGroup>
this outputs
Location is = 0
Error MSB4184: The expression ""Data Source=MySQLServer;Integrated Security=True;Pooling=False".Substring(0 + 12)" cannot be evaluated. Input string was not in a correct format.
I took the output and plugged into a console app and it works just fine. I have tried several variations and I they always fail when I put a property inside a property function. (I even tried access the same property twice in a my property function and that failed too.)
Do property functions not support accessing properties?
I think my issue was assuming that math came for free.
I needed to do this kind of thing:
<PropertyGroup>
<LocationOfDataSource>$(TargetConnectionString.IndexOf("Data Source="))</LocationOfDataSource>
<LenthOfDataSourceString>12</LenthOfDataSourceString>
<LocationOfEndOfDataSourceString>$([MSBuild]::Add($(LocationOfDataSource), $(LenthOfDataSourceString)))</LocationOfEndOfDataSourceString>
<PartialConnectionString>$(TargetConnectionString.Substring($(LocationOfEndOfDataSourceString)))</PartialConnectionString>
</PropertyGroup>
Note that I am adding using Add($(Property), $(Property)) in this version. Add is one of the built-in MSBuild Property Functions (since MSBuild 4).
It seems to be working now.
Can anyone tell me how does the MsBuild picks up the value of the property..??
eg.
<TempProperty>Property Value</TempProperty>
now I can use $(TempProperty) anywhere to get the value of it.
now the scenario is I have made custom task that has configuration like this..
<PropertyGroup>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
now instead of getting "Property Value" in the tag I am getting $(TempProperty)... can anyone tell me how to get actual value in tag ???
thanks in advance.
Hey, Guys I have got the actual Problem and solution too... I think I was not able to describe my problem here.. .the problem was.. In my custom task I was passing the file path of the file containing the above ItemList tag..now when I tried to parse the XMLNode "ItemList" it was getting "$(TempProperty)" as value in the Item.. and I think thats correct because thats what is present in the passed XML.
So to overcome the issue , I did two things..
I created ItemGroup instead of propertyGroup and passed that ItemGroup to my custom task instead of file path.. thus now at my code I am getting the desired values.
thanks for your replies.
There's not enough there to really diagnose your problem. It looks correct at first glance, but where is the PropertyGroup for TempProperty declared?
For an illustrative example, here's a snippet from a C# project file:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
...
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
...
</PropertyGroup>
The Configuration Property is being set to Debug (if it is blank at at that point). In the following group, it keys off of the Confuration and Platform properties.
The only gotcha tidbit is that for a property value to show up properly, it must be declared before it is used.
Please try to provide a little more context, that may help with understanding the issue.
I just tried this:
<Project
xmlns="http://schemas.microsoft.com/developer/msbuild/2003"
ToolsVersion="4.0"
DefaultTargets="Demo" >
<PropertyGroup>
<TempProperty>property value</TempProperty>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
<Target Name="Demo">
<Message Text="TempProperty: $(TempProperty)"/>
<Message Text="ItemList: $(ItemList)"/>
</Target>
</Project>
And my results where:
Task "Message"
TempProperty: property value
Done executing task "Message".
Task "Message"
ItemList:
<ConfigChange xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>property value</Value>
<IsList>False</IsList>
</ConfigChange>
Done executing task "Message".
Are you seeing something else?
on Ritch's suggestion I am adding my solution.
Initially I had
<TempProperty>Property Value</TempProperty>
<PropertyGroup>
<ItemList>
<ConfigChange>
<PlaceHolder>#MACHINE_NAME#</PlaceHolder>
<Value>$(TempProperty)</Value>
<IsList>False</IsList>
</ConfigChange>
</ItemList>
</PropertyGroup>
now my problem was I was providing my properties file part to my custom task
like
<UpdatePegasusConfigXML
Environment="$(Environment)"
Instance="$(Instance)"
BuildSourceRoot="$(BuildSourceRoot)"
></UpdatePegasusConfigXML>
now since I was providing file path itself so it took the value which was put in "Value" tag and was not picking up the property value, thus at code level I was getting "$(TempProperty)" instead of "Property Value"
now what I did was instead of creating property Group I created ItemGroup like this
<ItemGroup>
<PlaceHolders Include="#MACHINE_NAME#">
<Value>$(TempProperty)</Value>
<IsList>True</IsList>
</PlaceHolders>
</ItemGroup>
now I updated my custom task to take IteamGroup as one of the inputs, thus calling changed to
<UpdatePegasusConfigXML
Environment="$(Environment)"
Instance="$(Instance)"
BuildSourceRoot="$(BuildSourceRoot)"
PlaceHolders="#(PlaceHolders)"
></UpdatePegasusConfigXML>
now at code level I am able to get the value in "Value" tag of the ItemGroup.
I hope I had explained my solution that it was understandable.