|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Content
IntroductionThe .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:
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 MSBuildHere, we will create an assembly with:
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 tasksThe root element of the MSBuild XML documents is An MSBuild target is a set of MSBuild tasks. Each child element to
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="http://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
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 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 PropertiesTo 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 <Project xmlns="http://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:
During the edition of properties with Visual Studio 2005, you will notice that a certain number of keys are proposed by intellisense. ItemsThe 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 <Project xmlns="http://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 <cs_source Include=".\*.cs" Exclude=".\Foo1.cs" />
ConditionsWe 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 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 <Project xmlns="http://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 Before launching this script, you must specify as a command line parameter the value of the >msbuild /p:Configuration=Release
The astute reader has noticed that it is possible to use a condition to define the default value for the <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)'==''">
<Configuration>Debug</Configuration>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
...
Incremental build and dependencies between targetsIn 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 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 <Project xmlns="http://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 transformsYou 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 filesWe 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 <Project xmlns="http://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="http://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:
We recommend that you take a look at the .targets files located in the folder defined by 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 tasksAnother 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:
Here is an example of a task named 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 <Project xmlns="http://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
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||