I've been doing some exploring of MSBuild as a programming language. There are some interesting results regarding mutability/immutability, but that's for another post.
This post is about functions. In particular, a Target may be invoked using the MSBuild task, so I'm exploring using Targets as functions. MSBuild can pass parameters to a Target by sending it Properties. Property changes are not propagated back to the caller, though, so getting a return value is a bit trickier.
It turns out that MSBuild does return one bit of information from a Target: its Outputs. It's possible to set the Outputs of a Target to a Property, and have that Target depend on another Target that sets that Property. In this way, it is possible to create a pair of Targets that can "calculate" the outer Target's Outputs.
By combining these approaches (setting Properties for arguments, and using the Target's Outputs as a return value), it is possible to treat a Target as a function.
To demonstrate, I wrote this program, which uses MSBuild to recursively calculate the factorial of the $(Input)
property. Have fun playing!
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\ExtensionPack\MSBuild.ExtensionPack.tasks"/>
<Target Name="Default">
<Error Condition="'$(Input)' == ''"
Text="Usage: msbuild factorial.proj [/nologo] [/clp:v=minimal] /p:Input=nnn"/>
<MSBuild.ExtensionPack.Science.Maths TaskAction="Compare"
P1="$(Input)" P2="1" Comparison="LessThan">
<Output TaskParameter="LogicalResult" PropertyName="InputCheck"/>
</MSBuild.ExtensionPack.Science.Maths>
<Error Condition="'$(InputCheck)' != 'False'"
Text="Input cannot be less than 1."/>
<MSBuild Projects="$(MSBuildProjectFile)"
Targets="Factorial" Properties="Input=$(Input)">
<Output TaskParameter="TargetOutputs"
ItemName="FactorialResult"/>
</MSBuild>
<Message Importance="high" Text="Result: @(FactorialResult)"/>
</Target>
<Target Name="Factorial"
DependsOnTargets="FactorialCore" Outputs="$(FactorialResult)" />
<Target Name="FactorialCore">
<PropertyGroup Condition="'$(Input)' == '1'">
<FactorialResult>1</FactorialResult>
</PropertyGroup>
<CallTarget Condition="'$(FactorialResult)' ==
''" Targets="CalculateFactorial"/>
</Target>
<Target Name="CalculateFactorial">
<MSBuild.ExtensionPack.Science.Maths TaskAction="Subtract" Numbers="$(Input);1">
<Output TaskParameter="Result" PropertyName="InputMinus1"/>
</MSBuild.ExtensionPack.Science.Maths>
<MSBuild Projects="$(MSBuildProjectFile)"
Targets="Factorial" Properties="Input=$(InputMinus1)">
<Output TaskParameter="TargetOutputs" ItemName="SubResult"/>
</MSBuild>
<MSBuild.ExtensionPack.Science.Maths TaskAction="Multiply"
Numbers="@(SubResult);$(Input)">
<Output TaskParameter="Result" PropertyName="FactorialResult"/>
</MSBuild.ExtensionPack.Science.Maths>
</Target>
</Project>
msbuild factorial.proj /nologo /clp:v=minimal /p:Input=5
Default:
Result: 120
msbuild factorial.proj /nologo /clp:v=minimal /p:Input=7
Default:
Result: 5040
Useless, but cool nonetheless.