.NET 2.0 introduced a new tool called MSBuild. MSBuild is primarily used to build .NET applications, but can be used for other types of applications as well. From Windows Vista, MSBuild is distributed with Windows itself, making it a strong platform for implementing scripted application on non-developer PCs as well.
Before .NET 2.0, application were built by one or more of these strategies:
- Using the build menu from inside Visual Studio
- Using the devenv.exe process from the command line with some extra parameters
- Using a third-party tool like NAnt (which MSBuild is based on)
An MSBuild file is implemented in XML. The root element of an MSBuild XML file will always be a project containing target elements. It is possible to run multiple targets in an MSBuild file. When the MSBuild script is started from the command line, it takes a build file as parameter as well as the names of the targets, which should be executed.
A target is implemented as a sequential list of tasks. The tasks are executed in the order in which they are specified beneath the target element. Both targets and tasks can be dependent on each other.
This is an example of a very simple file to build a .NET project:
1
2
3
4
5
6
7
| <? xml version = "1.0" encoding = "utf-8" ?> < Target Name = "Build" > < Message Text = "Building msbuildintro" /> < MSBuild Projects = "msbuildintro.csproj" Targets = "Build" /> </ Target > </ Project > |
Properties
To be able to parameterize a build script, MSBuild has implemented properties. A property is a simple key/value type, which can be used from multiple locations in the scripts. Let’s look at an example using properties:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| <? xml version = "1.0" encoding = "utf-8" ?> < PropertyGroup > < MyReleaseOutput >.\release</ MyReleaseOutput > </ PropertyGroup > < Target Name = "Build" > < Message Text = "Building msbuildintro" /> < MSBuild Projects = "msbuildintro.csproj" Targets = "Build" /> </ Target > < Target Name = "Release" DependsOnTargets = "Build" > < MakeDir Directories = "$(MyReleaseOutput)" /> < Copy SourceFiles = ".\bin\debug\msbuildintro.exe" DestinationFolder = "$(MyReleaseOutput)" /> </ Target > </ Project > |
The release target introduces two new tasks as well. MakeDir, which (well you guessed it) creates a new directory and Copy, which copies on or more files from A to B. MakeDir contains a single attribute, defining the path of the new directory.
In this example that path is the value of the MyReleaseOutput property. Notice the use of the $(propertyname) syntax here. $() is used to reference properties in MSBuild. The copy task in our example points out a single file and the destination folder where this file should be copied to when executing the Release target. The $(MyReleaseOutput) used is again replaced by the value of this property.
Items
The example above copied a single file from one directory to another. This can certainly be useful in many situations, but sometimes this is simply not flexible enough. This is where items come in. An item gives the possibility of creating dynamic lists of, for instance, file names. Let’s start by looking at an example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <? xml version = "1.0" encoding = "utf-8" ?> < PropertyGroup > < MyReleaseOutput >.\release</ MyReleaseOutput > </ PropertyGroup > < ItemGroup > < MyReleaseFiles Include = ".\bin\debug\*.*" /> </ ItemGroup > < Target Name = "Build" > < Message Text = "Building msbuildintro" /> < MSBuild Projects = "msbuildintro.csproj" Targets = "Build" /> </ Target > < Target Name = "Release" DependsOnTargets = "Build" > < MakeDir Directories = "$(MyReleaseOutput)" /> < Copy SourceFiles = "@(MyReleaseFiles)" DestinationFolder = "$(MyReleaseOutput)" /> </ Target > </ Project > |
The Include filter decides which file to include. We also have the possibility to specify an Exclude filter. The following example shows how:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <? xml version = "1.0" encoding = "utf-8" ?> < PropertyGroup > < MyReleaseOutput >.\release</ MyReleaseOutput > </ PropertyGroup > < ItemGroup > < MyReleaseFiles Include = ".\bin\debug\*.*" Exclude = ".\bin\debug\*vshost.exe" /> </ ItemGroup > < Target Name = "Build" > < Message Text = "Building msbuildintro" /> < MSBuild Projects = "msbuildintro.csproj" Targets = "Build" /> </ Target > < Target Name = "Release" DependsOnTargets = "Build" > < MakeDir Directories = "$(MyReleaseOutput)" /> < Copy SourceFiles = "@(MyReleaseFiles)" DestinationFolder = "$(MyReleaseOutput)" /> </ Target > </ Project > |
The last two examples of using items are actually not that useful because the content for the MyReleaseFiles item is generated before running the actual targets. On a new checkout or after a clean, no files exist in the bin\debug directory meaning that the generated file list will therefore be completely empty. Bummer! Let us fix the example. We define a new item inside the scope of a target instead:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <? xml version = "1.0" encoding = "utf-8" ?> < PropertyGroup > < MyReleaseOutput >.\release</ MyReleaseOutput > </ PropertyGroup > < Target Name = "Build" > < Message Text = "Building msbuildintro" /> < MSBuild Projects = "msbuildintro.csproj" Targets = "Build" /> </ Target > < Target Name = "Release" DependsOnTargets = "Build" > < MakeDir Directories = "$(MyReleaseOutput)" /> < CreateItem Include = ".\bin\debug\*.*" Exclude = ".\bin\debug\*vshost.exe" > < Output TaskParameter = "Include" ItemName = "MyReleaseFiles" /> </ CreateItem > < Copy SourceFiles = "@(MyReleaseFiles)" DestinationFolder = "$(MyReleaseOutput)" /> </ Target > </ Project > |
Anyone still reading? If you have read this far, you deserve a little prize:
Conditions
Besides the CreateItem task in a previous example, our build scripts have been very static until now. This can be sufficient for a lot of projects, but sometimes you need slightly more flexible build scripts. A feature for fulfilling this in MSBuild is called Conditions. A condition in MSBuild is not that different from a condition in other software languages. It is a possibility, for instance, to make more flexible structures to avoid running targets and/or tasks when certain conditions are met. You can define conditions on all targets and almost all tasks. Let’s check out another example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <? xml version = "1.0" encoding = "utf-8" ?> < PropertyGroup > < MyReleaseOutput >.\release</ MyReleaseOutput > < Configuration Condition = " '$(Configuration)' == '' " >Debug</ Configuration > </ PropertyGroup > < Target Name = "Build" > < Message Text = "Building msbuildintro $(Configuration)" /> < MSBuild Projects = "msbuildintro.csproj" Targets = "Build" /> </ Target > < Target Name = "Release" DependsOnTargets = "Build" Condition = "$(Configuration) == 'Release'" > < MakeDir Directories = "$(MyReleaseOutput)" /> < CreateItem Include = ".\bin\$(Configuration)\*.*" Exclude = ".\bin\$(Configuration)\*vshost.exe" > < Output TaskParameter = "Include" ItemName = "MyReleaseFiles" /> </ CreateItem > < Copy SourceFiles = "@(MyReleaseFiles)" DestinationFolder = "$(MyReleaseOutput)" /> </ Target > </ Project > |
Msbuild.exe build.proj /p:Configuration=Debug /t:release
More new stuff. I changed all references to the debug, with the value of the new Configuration property. This means that I am now able to run the script in both Debug and Release mode.
The last new addition is the condition attribute on the release target. This condition examines if the value of the Configuration property is Release and only allows execution of the Release target if this condition is true.
Frequently Used Tasks
CallTarget – Executes another target inside the current build file. Used where a target needs to call another target at a specific place inside the target itself. If the target were just dependant on the other target being executed, we would use the DependsOnTargets attribute instead.Copy – We already saw this task in action. Copies one or more files from A to B.
CreateItem – Create dynamic items.
CreateProperty – Create dynamic properties.
Delete – Deletes on or more files.
Exec – Executes an external process. This is typically used to executed external tools, which did not implement MSBuild targets themself.
MakeDir – Creates one or more directories.
Message – Outputs a message to the console.
MSBuild – Executes one or more targets in an external MSBuild file.
RemoveDir – Removes one or more directories.
And there are a lot more.
Custom MSBuild Tasks
It is possible to write your own MSBuild tasks. This can be extremely useful if you want to integrate some of your own tools with MSBuild or if you need to do stuff which is not yet supported by MSBuild. The following example shows how to write a simple addition task:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| using System; using Microsoft.Build.Utilities; using Microsoft.Build.Framework; namespace MyTasks { public class AddTask : Task { private int number1; [Required] public int Number1 { get { return number1; } set { number1 = value; } } private int number2; [Required] public int Number2 { get { return number2; } set { number2 = value; } } private int sum; [Output] public int Sum { get { return sum; } set { sum = value; } } public override bool Execute() { try { sum = number1 + number2; } catch (ArithmeticException e) { Console.WriteLine( "Error occured during addition: {0}" , e.Message); return false ; } return true ; } } } |
I defined two properties which act as input variables to the addition task: Number1 and Number2. Both properties are marked with the Required attribute which tells the script to fail if they are not specified. An output property is added as well. The value of this property will be available within the build script. Let’s see how to use this new and fancy task:
1
2
3
4
5
6
7
8
9
10
| <? xml version = "1.0" encoding = "utf-8" ?> < UsingTask TaskName = "MyTasks.AddTask" AssemblyFile = ".\AddTask.dll" /> < Target Name = "Addition" > < AddTask Number1 = "10" Number2 = "12" > < Output TaskParameter = "Sum" PropertyName = "CalculatedSum" /> </ AddTask > < Message Text = "10 + 12 = $(CalculatedSum)" /> </ Target > </ Project > |
Because my AddTask in an external task not built into the MSBuild framework, I use the UsingTask element to reference it. The name of the task as well as the assembly name is specified.
The Addition target uses the AddTask with the two Number properties as attributes. The Output element copies the result of the task to a property called CalculatedSum. This property is accessible in the Message task for output on the console.
You just implemented your first custom MSBuild task. Pretty easy, right?
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 > |
1
2
3
| < Target Name = "PrintMessage" > < Message Text = "Hello World from X" /> </ Target > |
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 > |
Property functions
TODO
Links
The MSBuild documentation is actually quite good and a must for all serious MSBuild developers:http://msdn2.microsoft.com/en-us/library/wea2sca5(VS.80).aspxA lot of different tools for handling MSBuild files exist. I haven’t been able to find a tool that makes it possible to run build scripts from within Visual Studio. You could start by installing the MSBuildShellExtension tool from: MSBuildShellExtension.
Besides the build in tasks in MSBuild, there is a great community project, implementing a lot of nice tasks not implemented by Microsoft. Targets for search and replace, zipping, sending out mails, modifying subversion etc., can be found here: http://msbuildtasks.tigris.org/
Nessun commento:
Posta un commento