MSBuild 4.0 property functions cannot access properties inside of them - msbuild

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.

Related

msbuild - what are rules for scope/inheritance of properties/items?

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.

MSBUILD - Remove a character from a variable without knowing its position

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>

MsBuild Getting propertyvalue inside $([System.IO.Path]::GetFullPath

i am creating a folder in the script executing directory with currentdatetime and pull doown source from source control.
<PropertyGroup>
<CurrentDate>$([System.DateTime]::Now.ToString(yyyy-MM-dd-mmss))
</CurrentDate>
</PropertyGroup>
But when i use the below inside a propertyGroup i do not get the value of $(CurrentDate) it gets escaped.
<FullPath>$([System.IO.Path]::GetFullPath('.\$(CurrentDate)\Build\Service\'))</FullPath>`
can any one suggest me what is wrong in the above. Is there any option use the property group value inside $([System.IO.Path])

Does MSBuild has an OnImport callback or something similar to detect importing?

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.

How can task parameters be defaulted in MSBuild

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.