Practical .NET2 and C#2: An introduction to MSBuild
The main content for this post is taken from an article published by Patrick Smacchia (author of Practical .NET 2 and C# 2) which was posted here (on CodeProject.com).
Introduction
The .NET 2 platform is delivered with a new tool named msbuild.exe. This tool is used to build .NET applications. It accepts XML files which describe the sequence of tasks for the build process, in the same spirit as makefile files. Actually, in the beginning of this project, Microsoft had code named the tool XMake. The msbuild.exe executable is located in the .NET installation folder [Windows install folder]\Microsoft.NET\Framework\v2.0.50727\ . It is planned that MSBuild will be part of the Windows Vista operating system. Its range of action will then be increased and may be used to construct all type of applications.
Until now, to construct your .NET applications, you needed to:
- rither use the Build command in Visual Studio,
- or use the Visual Studio devenv.exe executable as a command line,
- or use a third party tool such as the open source tool named NAnt, or even use batch files which called the csc.exe C# compiler.
MSBuild aims to unify all these techniques. Those who know NAnt will not be in unknown territory as MSBuild borrows many concepts from this tool. The main advantage of MSBuild over NAnt is that it is used by Visual Studio 2005. MSBuild has no dependency on Visual Studio 2005 as it is an integral part of the .NET 2 platform. However, the .proj, .csproj, .vbproj etc. generated by Visual Studio 2005 to build projects are authored in the MSBuild XML format. During compilation, Visual Studio 2005 uses the services of MSBuild. In addition, the XML format used by MSBuild is fully supported and documented. The support of MSBuild is a significant progress for Visual Studio since until now, it used undocumented build scripts.
Building a multi modules assembly without MSBuild
Here, we will create an assembly with:
- A main module Foo1.exe.
- A module Foo2.netmodule.
- A resource file Image.jpg.
Place in the same folder both the C# source files (Foo1.cs and Foo2.cs) as well as an image file named Image.jpg.
namespace Foo {
public class Bar {
public override string ToString() {
return "Hi from Foo2";
}
}
}
using System;
using System.Reflection;
[assembly: AssemblyCompany("ParadoxalPress")]
namespace Foo {
class Program {
public static void Main(string[] argv) {
Console.WriteLine("Hi from Foo1");
Bar b = new Bar();
Console.WriteLine( b );
}
}
}
Since we wish to construct an assembly with more than one module, we have no choice other than to use the csc.exe command line compiler as the Visual Studio environment cannot handle multi-module assemblies.
Create the files Foo2.netmodule and Foo1.exe by typing, in order, the following command (the csc.exe compiler can be found in the folder <WINDOWS-INSTALL-FOLDER>\Microsoft.NET\Framework\v2.0.50727):
> csc.exe /target:module Foo2.cs
> csc.exe /Addmodule:Foo2.netmodule /LinkResource:Image.jpg Foo1.cs
Launch the Foo1.exe executable and the program displays the following on the console:
Hi from Foo1
Hi from Foo2
.proj files, targets and tasks
The root element of the MSBuild XML documents is <Project>
. This element contains <Target>
elements. These <Target>
elements are the build units named targets. An MSBuild project can contain multiple targets and msbuild.exe is capable of chaining the execution of multiple targets. When you launch msbuild.exe through the command line, it takes as input a single .proj from the current folder. If multiple .proj files are present, you must specify to msbuild.exe which one to use. A single file must be specified.
An MSBuild target is a set of MSBuild tasks. Each child element to <Target>
constitutes the definition of a task. The tasks of a target are executed in the order of their declaration. About forty types of tasks are provided by MSBuild, for example:
Task type |
Description |
Copy |
Copies a file from a source folder to a destination folder. |
MakeDir |
Creates a folder. |
Csc |
Calls the csc.exe C# compiler. |
Exec |
Executes a system command. |
AL |
Calls the al.exe tool (Assembly Linker). |
ResGen |
Calls the resgen.exe tool (Resources Generator). |
The complete list of possible tasks is available in the article named MSBuild Task Reference, on MSDN. An interesting aspect of MSBuild is that each type of task is materialized by a .NET class. It is then possible to extend MSBuild with new types of tasks by supplying your own classes. We will discuss this a little later.
Let us revisit our multi-module assembly example introduced in the previous section. Let me remind you that to build this assembly constructed from three modules Foo1.exe, Foo2.netmodule, and Image.jpg, we had to execute the following two commands:
>csc.exe /target:module Foo2.cs
>csc.exe /Addmodule:Foo2.netmodule /LinkResource:Image.jpg Foo1.cs
In addition, we wanted that at the end of the construction of the assembly, the three modules be located in the \bin sub-folder from the current folder. Here is the MSBuild project Example1.proj which accomplishes the same work:
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="FooCompilation">
<MakeDir Directories= "bin"/>
<Copy SourceFiles="Image.jpg" DestinationFiles=".\bin\Image.jpg"/>
<Csc Sources="Foo2.cs" TargetType="module"
OutputAssembly=".\bin\Foo2.netmodule" />
<Csc Sources="Foo1.cs" TargetType="exe"
AddModules=".\bin\Foo2.netmodule" LinkResources="Image.jpg"
OutputAssembly=".\bin\Foo1.exe" />
</Target>
</Project>
We see that the target named FooCompilation
is built with four tasks:
- A task of type
MakeDir
which creates the \bin folder; - A task of type
Copy
which copies Image.jpg to the \bin folder; - Two tasks of type
Csc
which invoke the csc.exe compiler.
To execute this build project, you need to create a folder containing the following files:
.\Foo.proj
.\Foo1.cs
.\Foo2.cs
.\Image.jpg
Go in this folder with the command window (Start Menu --> Microsoft .NET Framework SDK v2.0 --> SDK Command Prompt) and then launch the msbuild.exe command. Each target must be named. By default, msbuild.exe only executes the first target. You can specify a list of targets separated by semi-colons using the /target
(shortcut /t
). You can also specify such a list using the DefaultTarget
attribute of the <Project>
tag. If multiple targets are specified, the order of execution is undefined.
The default behavior of MSBuild is to stop execution as soon as one of the tasks emits an error. You may wish to have a build script tolerate errors. Also, each element containing a task can contain a ContinueOnError
attribute which is set to false
by default but may be set to true
.
Properties
To allow you to add parameters to control your scripts, MSBuild presents the notion of a property. A property is a key/value couple defined in the <PropertyGroup>
element. MSBuild properties work as an alias system. Each occurrence of $(key)
in the script is replaced by the associated value. Typically, the name of the /bin folder is used in five places in our project. It constitutes a good candidate for a property:
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputPath>.\bin</OutputPath>
</PropertyGroup>
<Target Name="FooCompilation">
<MakeDir Directories= "$(OutputPath)"/>
<Copy SourceFiles="Image.jpg"
DestinationFiles="$(OutputPath)\Image.jpg"/>
<Csc Sources="Foo2.cs" TargetType="module"
OutputAssembly="$(OutputPath)\Foo2.netmodule" />
<Csc Sources="Foo1.cs" TargetType="exe"
AddModules="$(OutputPath)\Foo2.netmodule"
LinkResources="Image.jpg"
OutputAssembly="$(OutputPath)\Foo1.exe" />
</Target>
</Project>
You can also use pre-defined properties defined by MSBuild, such as:
Property |
Description |
|
Folder hosting the current MSBuild project. |
|
Name of the current MSBuild project. |
|
Calls the csc.exe. Extension of the current MSBuild project. |
|
Full path to the current MSBuild project. |
|
Name of the current MSBuild project without the extension. |
|
Folder which contains msbuild.exe. |
During the edition of properties with Visual Studio 2005, you will notice that a certain number of keys are proposed by intellisense. OutputPath
, for example, is such a key. You can use these keys, but nothing prevents you from defining your own keys.
Items
The base behind building a project by a script is the manipulation of folders, files (source, resource, executable…), and references (to assemblies, to COM classes, to resource files…). We use the term item to designate these entries which constitute the entries and outputs for most of the tasks. In our example, the Image.jpg file is an item consumed both by the Copy
task and the second Csc
task. The file Foo2.netmodule is an item produced by the first Csc
task and is consumed by the second Csc
task. Let us rewrite our project using the notion of item:
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup><OutputPath>.\bin</OutputPath></PropertyGroup>
<ItemGroup>
<File_Image Include="$(OutputPath)\Image.jpg"/>
<NetModule_Foo2 Include="$(OutputPath)\Foo2.netmodule"/>
</ItemGroup>
<Target Name="FooCompilation">
<MakeDir Directories= "$(OutputPath)"/>
<Copy SourceFiles="Image.jpg"
DestinationFiles="@(File_Image)"/>
<Csc Sources="Foo2.cs" TargetType="module"
OutputAssembly="@(NetModule_Foo2)" />
<Csc Sources="Foo1.cs" TargetType="exe"
AddModules="@(NetModule_Foo2)"
LinkResources="@(File_Image)"
OutputAssembly="$(OutputPath)\Foo1.exe" />
</Target>
</Project>
We notice the use of the @(item name)
to reference an item. Moreover, an item can define a set of files through the use of the wildcard syntax. For example, the following item references all the C# files in the current folder except for Foo1.cs:
<cs_source Include=".\*.cs" Exclude=".\Foo1.cs" />
Conditions
We may wish that the same MSBuild project build multiple different versions. For example, it would be a shame to have to create and maintain two projects to handle the build of a Debug and a Release version of the same application. Also, MSBuild introduces the notion of a condition. A Condition
attribute can be added to any element of a MSBuild project (properties, item, target, task, property group, item group…). If during the execution, the condition of an element is not satisfied, the MSBuild engine will ignore it. The MSDN article named MSBuild Conditions describes the complete list of expression which can be used in a Condition
attribute.
In the following example, we use the condition of type string equality compare to make sure that our script supports both a Debug and Release mode. We also use a condition of type test for the presence of a file or folder to execute the MakeDir
task only when needed. This condition is there purely for learning reasons as the MakeDir
only executes if the folder does not already exist:
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<Optimize>false</Optimize>
<DebugSymbols>true</DebugSymbols>
<OutputPath>.\bin\Debug</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<Optimize>true</Optimize>
<DebugSymbols>false</DebugSymbols>
<OutputPath>.\bin\Release</OutputPath>
</PropertyGroup>
<ItemGroup>
...
<Target Name="FooCompilation">
<MakeDir Directories= "$(OutputPath)"
Condition="!Exists('$(OutputPath)')"/>
...
When they are defined, the Optimize
and DebugSymbols
standard properties are automatically taken into account by Csc
tasks.
Before launching this script, you must specify as a command line parameter the value of the Configuration
property. This can be done using the /property
(shortcut /p
) option:
>msbuild /p:Configuration=Release
The astute reader has noticed that it is possible to use a condition to define the default value for the Condition
property:
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)'==''">
<Configuration>Debug</Configuration>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
...
Incremental build and dependencies between targets
In a real environment, the execution of an MSBuild project can take several minutes (even hours) to execute. As a side fact, did you know that since its beginning, the Windows operating system takes about 12 hours to build? This means the ever growing volume of code to be compiled compensates the performance increase of machines.
It is not necessarily a good thing to completely restart the build process for a minor change made on a source file for which no other components depend. Also, you can use the notion of incremental construction. For this, you must specify the list of input items and the output items for a target using the Inputs
and Outputs
attributes. If MSBuild detects that at least one of the input items is older than one of the output items, it will execute the target.
This incremental build technique forces you to partition your tasks into several targets. We have seen that if we specify multiple targets to build by MSBuild, for example with the DefaultTargets
attribute, you can not assume any execution order. You can however specify a set of dependencies between targets using the DependsOnTargets
attribute. MSBuild executes a target only when the totality of the targets on which it depends is executed. Naturally, the MSBuild engine will detect and emit an error when circular dependencies between targets exist:
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="FooCompilation">
...
<Target Name="CreateOutputPath" Condition="!Exists('$(OutputPath)')">
<MakeDir Directories= "$(OutputPath)"/>
</Target>
<Target Name="FooCompilation" DependsOnTargets="CreateOutputPath"
Inputs="Foo2.cs;Foo1.cs"
Outputs="@(NetModule_Foo2);$(OutputPath)\Foo1.exe">
...
</Target>
</Project>
MSBuild transforms
You have the possibility of establishing an objective correspondence between the input items and the output items of a target. For this, you need to use MSBuild transformations detailed in the article named MSBuild Transforms, on MSDN. The advantage of using transformations is that MSBuild decides to execute the target if at least one of the input items is older than the output item which corresponds to it. Logically, such a target is going to be executed less often, hence a performance gain.
Splitting an MSBuild project on several files
We have seen that the msbuild.exe tool can only process a single project file for each execution. However, an MSBuild project file can import another MSBuild project file by using the <Import>
element. In this case, all the children of the <Project>
in the imported file are copied in place of the <Import>
element. Our example script can then be broken into two files Example7.proj and Example8.target.proj as follows:
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003"
DefaultTargets="FooCompilation">
<PropertyGroup Condition="'$(Configuration)'==''"> ...
<PropertyGroup Condition="'$(Configuration)'=='Debug'"> ...
<PropertyGroup Condition="'$(Configuration)'=='Release'"> ...
<ItemGroup> ...
<Import Project="Foo.target.proj"/>
</Project>
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003" >
<Target Name="CreateOutputPath" ...
<Target Name="FooCompilation" ...
</Project>
How does Visual Studio 2005 harness MSBuild?
I have already mentioned that the files of extension .proj, .csproj, .vbproj etc. generated by Visual Studio 2005 to build projects are authored in the MSBuild XML format. If you analyze such a file, you will notice that no targets are explicitly specified. In fact, the project files generated by Visual Studio 2005 import some .targets files which contain generic targets. For example, a file with a .csproj extension contains the following element:
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
This Microsoft.CSharp.targets file contains two generic targets:
- A target named
CreateManifestResourceNames
which takes care of the organization of resource files (transformation of .resx files into .resources). - A target named
CoreCompile
which contains a task namedCsc
which takes care of building C# files.
We recommend that you take a look at the .targets files located in the folder defined by $(MSBuildBinPath)
which is the .NET 2.0 installation folder (i.e. [Windows folder]\Microsoft.NET\Framework\v2.0.50727).
In addition to importing such a .targets file, the files generated by Visual Studio 2005 contain essentially the definition of properties and items which will be used by the generic targets.
Creating custom MSBuild tasks
Another interesting aspect of MSBuild is that each type of task materializes itself in a .NET class. This means that it is possible to extend MSBuild with new task types by supplying your own classes. Such a class must support the following constraints:
- It must implement the
ITask
interface defined in Microsoft.Build.Framework.dll, or derive from the helper classTask
defined in Microsoft.Build.Utilities.dll. - It must implement the
bool Execute()
of theITask
interface. This method contains the body of the task and must returntrue
if the execution succeeded. - It can offer properties which will be set by MSBuild from the attribute values in the task, before the call to
Execute()
. Only the properties marked withRequired
must necessarily be initialized.
Here is an example of a task named MyTouch
which updates the timestamp of the files specified with the Files
attribute (let us mention that such a task named Touch
is offered by the MSBuild framework):
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace MyTask {
public class MyTouch : Task {
public override bool Execute() {
DateTime now = DateTime.Now;
Log.LogMessage(now.ToString() +
" is now the new date for the following files:");
try {
foreach(string fileName in m_FilesNames) {
Log.LogMessage(" " + fileName);
System.IO.File.SetLastWriteTime(fileName, now);
}
}
catch (Exception ex) {
Log.LogErrorFromException(ex, true);
return false;
}
return true;
}
[Required]
public string[] Files {
get { return (m_FilesNames); } set { m_FilesNames = value; }
}
private string[] m_FilesNames;
}
}
Note the use of the TaskLoggingHelper Task.Log{get;}
which allows displaying information relating to the execution of the task. Our MyTouch
task must be registered to all the MSBuild projects which may use it. This is done by using an element <UsingTask>
. Here is such a script which updates the timestamps of the C# files in the current folder (the MyTask.dll assembly must be located in the C:\CustomTasks\ folder):
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="C:\CustomTasks\MyTask.dll"
TaskName="MyTask.MyTouch"/>
<ItemGroup>
<FichierSrcCs Include="*.cs"/>
</ItemGroup>
<Target Name="TouchTheCsFiles" >
<MyTouch Files= "@(CsSrcFiles)"/>
</Target>
</Project>
It is interesting to note that all standard tasks are declared by <UsingTask>
elements located in the Microsoft.Common.Tasks file. This file is automatically and implicitly imported by msbuild.exe at each execution. By analyzing this file, we can see that the classes corresponding to the standard tasks are defined in the Microsoft.Build.Tasks.dll assembly. You can then access the code of these tasks using a tool such as Reflector.
Comments
- Anonymous
April 04, 2006
Download latest Ajax.NET Professional
6.4.3.2 [Via: Michael Schwarz ]
Eraser v5.8 - Cool Security... - Anonymous
February 26, 2008
PingBack from http://www.jasonkemp.ca/blog/msbuild-resources/