MSuild Generic Targets
Once in a while a question pops up in different forums: How do I parameterize a common part of my script, making it possible to use it from different targets? This is a great question. We don’t want to have the same lines of code spread around our build script, making it longer than necessary and complex to maintain. MSBuild provides the CallTarget task, which executes another target inside the current build file. Unfortunately there is no way to parameterize the CallTarget task, making it unusable for anything other than simple situations where needed tasks are 100% identical.
Let’s look at and example where we have two targets with some identical tasks:
1
2
3
4
5
6
7
8
9
| <? xml version = "1.0" encoding = "utf-8" ?> < Target Name = "Target1" > < Message Text = "Hello World from Target1" /> </ Target > < Target Name = "Target2" > < Message Text = "Hello World from Target2" /> </ Target > </ Project > |
In the example we define two targets: Target1 and Target2. You will probably notice that the targets look very similar. The only difference is the text inside the Message task. This is a simple example of the problem and of course changing the text in both message tasks would be fairly simple. But you probably understand the problem here: It would be nice to refactor the targets (let’s call it “Extract Target”), so that the Hello World text will only be specified once. First of all we need to move the common tasks to a new target:
1
2
3
| < Target Name = "PrintMessage" > < Message Text = "Hello World from X" /> </ Target > |
Note that the replaced part of the Text with the X. Now we need to call this target, making sure that X will be replaced with the correct string. For this purpose we use another task called MSBuild. The MSBuild task was originally produced to be able to call targets inside other MSBuild scripts. The great thing about the MSBuild task is that it has the capability to receive properties. In order to call our PrintMessage target and print the correct string, we parameterize the target like this:
1
2
3
4
5
6
7
8
9
10
11
12
| <? xml version = "1.0" encoding = "utf-8" ?> < Target Name = "Target1" > < MSBuild Projects = "$(MSBuildProjectFile)" Targets = "PrintMessage" Properties = "Caller=Target1" /> </ Target > < Target Name = "Target2" > < MSBuild Projects = "$(MSBuildProjectFile)" Targets = "PrintMessage" Properties = "Caller=Target2" /> </ Target > < Target Name = "PrintMessage" > < Message Text = "Hello World from $(Caller)" /> </ Target > </ Project > |
We use the MSBuild tasks in both Target1 and Target2. The $(MSBuildProjectFile) is a built-in property in MSBuild, which returns the filename of the current build file. What we do here is that we call the PrintMessage task on this. The properties attribute specify a single property named Caller. Multiple properties can be specified inside this attribute by separating them with a semicolon. In the PrintMessage task we simply print the message with the Caller property specified inside the Text attribute.
Property functions
Property functions are a great addition to the MSBuild language, introduced in MSBuild 4.0. Before property functions, some of the most simple tasks to execute in .NET, took ages implementing in MSBuild, cause you’d need to write custom tasks for every function not build into MSBuild or available through third party tasks. Allow me to explain the basics of property functions through an example.
The purpose of the example MSBuild script is to write the current date and time to the console. Admitted not the most advanced script, but it serves our purpose of explaining property functions just fine. In the old days (before 4.0), you would either have to find some third party library or alternatively implement one yourself. The code is of course trivial, but you’d still need to write and maintain it. With property functions doing things like this is almost as easy as in .NET. The script can be implemented using property functions as this:
1
2
3
4
5
6
| <? xml version = "1.0" encoding = "utf-8" ?> < Target Name = "PrintCurrentDateTime" > < Message Text = "The current date and time is: $([System.DateTime]::Now)." /> </ Target > </ Project > |
Notice that no external tasks are imported to write this script. In order to generate the current data and time, I simply use the $() syntax, which we recognise from the normal way of referencing MSBuild properties. Instead of including a property name inside the parethesis, I use one of the build-in System types available through .NET, in this case System.DateTime. Also notice that System.DateTime is included in angular brackets ([ ]), which tells MSBuild that this is in fact not a property declared elsewhere in the build file, but one of the supported types from .NET. Knowing that DateTime contains a static property name Now, I can simply reference this through the double comma (::) syntax, which translates into a dot (.) in .NET. It may seem a bit strange for some people, why Microsoft choose the double comma syntax instead of the ordinary dot syntax in .NET, but for PowerShell developers this should be spot on.
0 comments:
Post a Comment