Understanding MSBuild in Longhorn

Source code: MSBuildLonghorn.zip 19 KB

Abstract: The concepts of the build engine have changed in the recent years. As part of the code name Longhorn Microsoft propose a new build system named MSBuild. MSBuild defines what are going to be build as well as how are going to be build. In this article we explore the main MSBuild concepts. Also explain some code examples that illustrate the main MSBuild capacities.

MSBuild concepts.

MSBuild is written completely in managed code and distributed as part of the Longhorn operating system or the Whidbey Framework. Additionally it is fully integrated with the Visual Studio "Whidbey". MSBuild use a similar conception that is exposed as part of Ant tools. The core build process is defined in a new XML file format with a .proj extension. Change build capacities in our project are as simple as change some definitions in the .proj file. Takes a look to a simple file:

<Project MSBuildVersion="1.0" DefaultTargets = "Compile">
  <Property appname = "HelloWorldCS"/>
  <Item Type = "CSFile" Include = "consolehwcs1.cs"/>
  <Target Name = "Compile">
    <Task Name = "CSC" Sources = "@(CSFile)">
       <OutputItem TaskParameter = "OutputAssembly" Type = "EXEFile" Include = "$(appname).exe"/>
    </Task>
    <Message Text="The output file is @(EXEFile)"/>
  </Target>
</Project>

Project element.

All project files must have a Project as the root element. Its DefaultTarget attribute specifies the names of the targets that the system should build when you don't otherwise specify a target.

The PropertyGroup and Property Elements.

Using the Property we define the build properties. Properties can be at the project level or at the item level. Properties by default have two attributes. The name attribute specifies the property's name and the condition attribute is optional and specifies that the property be set only if some condition is met. Some basic properties like ApplicationIcon or Config are available for standard builds using the targets defined by WindowsApplication.target.

The PropertyGroup is used to group a set of properties together. You can have the same property in more that one PropertyGroup. When define a PropertyGroup we can set an optional Condition attribute that specifies that the PropertyGroup will apply in a build only when the condition is met.

Item and ItemGroup Elements.

An Item element is used to define the parts that are include in the build process. The Item elements have four attributes:

  • Type. The file type ex: CSFile.
  • Include. Specifies a file to be included in the build. Using wildcards we can specified multiple files ex: *.cs
  • Exclude. This optional attribute specifies a file or file specification that should be excluded from the build.
  • Condition. This optional attribute specifies that the file will be included in the build if some conditions are met.

In a similar way that PropertyGroups we can define ItemGroups to declare a set of Items that will be include in the build when some conditions are evaluated to true.

Import element.

It is very probable that similar projects have similar build configuration. Defining in every project all the rules may be very tedious. Using the Import element we can import a previously defined project file and reuse it.

Other elements.

There are other elements that we use in the project file. Of course this article is not a project file's reference. Some of the most important are the Task and Target elements.

The Task element specifies a one task that needs to be performed to produce the targets. The Task element is child of the Target element.

The Target element group a set of tasks that defines a particular build scenario.

MSBuild in action.

Until now we explore the basic concepts of the project definition file. In this section we explain some examples that show the use of the concepts illustrated above. These samples try to illustrate some of the most remarkable potentialities of MSBuild.

Build all C# files in a directory.

It is very common that projects include several files. In the build process you must include each of these files. This process can be performed in different ways. Each of these files may be included separately or you can use wildcards to specify the files.

In this sample we have two .cs files Main.cs and MsgProvider.cs. The sample is very simple and just prints a message returned by a method of the ConstProvider in the MsgProvider.cs file. The .proj file is showed below:

<Project MSBuildVersion="1.0" DefaultTargets = "Compile">
  <Property appname = "HelloMSBuildCS"/>
  <Item Type = "CSFile" Include = "*.cs"/>
  <Target Name = "Compile">
    <Task Name = "CSC" Sources = "@(CSFile)">
        <OutputItem TaskParameter = "OutputAssembly" Type = "EXEFile" Include = "$(appname).exe"/>
    </Task>
    <Message Text="The output file is @(EXEFile)"/>
  </Target>
</Project>

 Using the * or ? Wildcards you can specify a set of files to include in the build process for example:

<Item Type="CSFile" Include="*.cs"/>

The Item declaration expressed above indicates that all the .cs files in the directory must be included in the build process. We can also use the wildcards to include files in other directories for instance:

Include="Images/**/*.jpg"

To build this project we type the following sentence:

msbuild Main.proj

Using tasks and properties.

In the first section we explain the Task and Property concepts. Combining these concepts we can construct a complex .proj file. There are several predefined tasks that we can use in the build process. The following file illustrated this:

<Project MSBuildVersion="1.0" DefaultTargets="Compile">
  <Property appname="HelloMSBuildCS"/>
  <Property OutDir="bin/"/>
  <Item Type="CSFile" Include="*.cs"/>
  <Target Name="Compile">
    <Task Name="MakeDir" Directories="Bin" Condition="!Exists('Bin')" />
    <Task Name="CSC" Sources="@(CSFile)">
       <OutputItem TaskParameter="OutputAssembly" Type="EXEFile" Include="$(OutDir)$(appname).exe"/>
    </Task>
    <Message Text="The output file is @(EXEFile)"/>
    <Message Text="SecondOutput directory already exists..." Condition="Exists('SecondOutput')" />
    <Message Text="SecondOutput directory created..." Condition="!Exists('SecondOutput')" />
    <Task Name="MakeDir" Directories="SecondOutput" Condition="!Exists('SecondOutput')" />
    <Task Name="Copy" SourceFiles="@(CSFile)" Destination="SecondOutput" />
    <Message Text="Files copied suscefully"/>
  </Target>
</Project>

This file extends in some ways the file in the first sample. We define the property OutDir to set the output directory. Next we use the MakeDir task to create the bin directory if it not exists and put the output inside it. In the final part we print some messages, create the SecondOutput directory and put the source files inside. When we build the file the following output is produced:

Target "Compile" in project "Main.proj"
Task "MakeDir"
Creating directory "Bin".
Task "CSC"
Csc.exe /out:"bin/HelloMSBuildCS.exe" "Main.cs" "MsgProvider.cs"
The output file is bin/HelloMSBuildCS.exe
SecondOutput directory created...
Task "MakeDir"
Creating directory "SecondOutput".
Task "Copy"
Copying file from "Main.cs" to "SecondOutput/Main.cs".
Copying file from "MsgProvider.cs" to "SecondOutput/MsgProvider.cs".
Files copied suscefully

As we can see in the output there are some custom messages that we define using the sentence:

<Message Text="Message"/>

Use several project files.

One of the most powerful capabilities of MSBuild is the fact that we split the configuration into several files and reuse some of them. The next sample is very similar to the previous but we define some of the properties in a separate file.

<Project MSBuildVersion="1.0">
  <Property appname="HelloMSBuildCS"/>
  <Property OutDir="bin/"/>
</Project>

In this file we declare the OutDir and appname properties that will use in the main configuration file. The content of this file are the following:

<Project MSBuildVersion="1.0" DefaultTargets="Compile2">
  <Import Project="Props.targets" />
  <Item Type="CSFile" Include="*.cs"/>
  <Target Name="Compile2">
    <Task Name="MakeDir" Directories="Bin" Condition="!Exists('Bin')" />
    <Task Name="CSC" Sources="@(CSFile)">
       <OutputItem TaskParameter="OutputAssembly" Type="EXEFile" Include="$(OutDir)$(appname).exe"/>
    </Task>
   <Message Text="The output file is @(EXEFile)"/>
  </Target>
</Project>

In this file we use the Import clause to use the props.targets file.

Extending the core build process.

In the previous sample we explain how to reuse some files in the build process. In the .NET Framework 1.0 and 1.1 we can redefine the project file, however the core build process remain like a black box we never see it. Now with the version 1.2 of the .NET Framework we can explore the build process and also make some changes. As part of it installation usually in the WindowsDir/Microsoft.NET/Framework/v1.2.30703 we found the following files:

  •  CSharp.targets.
  •  VisualBasic.targets.
  •  framework.targets.

These files define the core build process and we can now make some modifications.

In this sample we modify the framework.targets to include a custom task. We can start with the following file:

<Project MSBuildVersion="1.0">
  <Target Name="CommonTarget">
    <Message Text="Hello Custom Target...."/>
  </Target>
</Project>

In this file we define a Target CommonTarget that prints a Hello message.

In the framework.targets file we can find the following declaration:

 <Target Name="Build"
     Outputs="@(FinalAssembly->'$(MSBuildProjectDirectory)/%r%n%x')"
     DependsOnTargets="CheckPrerequisites;
                                      CreateOutputDirectories;
                                      PreBuildEvent;
                                      UnmanagedUnregistration;
                                      NetAssemblyResolver;
                                      BuildProjectReferences;
                                      ResGen;
                                      Compile;
                                      CreateSatelliteAssemblies;
                                      PrepareForRun;
                                      UnmanagedRegistration;
                                      PostBuildEvent;
                                      DumpSpecialMacros"
 />

This element defines the Build target and the sequence of targets that must be executed to complete the process. We can include the CommonTarget before the Compile target and the file look like this:

 <Target Name="Build"
     Outputs="@(FinalAssembly->'$(MSBuildProjectDirectory)/%r%n%x')"
     DependsOnTargets="CheckPrerequisites;
                                      CreateOutputDirectories;
                                      PreBuildEvent;
                                      UnmanagedUnregistration;
                                      NetAssemblyResolver;
                                      BuildProjectReferences;
                                      ResGen;

                                      SayHello;
                                      Compile;
                                      CreateSatelliteAssemblies;
                                      PrepareForRun;
                                      UnmanagedRegistration;
                                      PostBuildEvent;
                                      DumpSpecialMacros"
 />

Also add the following Import declaration in the framework.targets file:

<Import Project="SayHello.targets"/>

Next we can build the Main.proj using the new configuration and the output contains the following fragment:

Target "CommonTarget" in project "Main.proj"
Hello Custom Target....

Target "Compile" in project "Main.proj"

Defining custom tasks.

Tasks provide the code that runs during the build process. A library of common tasks is provided with MSBuild and you can also create your own tasks. In this sample we define a custom task and execute it as part of the build process.

In the previous examples we use tasks like Copy or MakeDir. Each task is implemented as a .NET class that implements the ITask interface. There are different ways to implement a Task. In all cases we can implement the Execute method that is called when the task run. The Execute method takes no parameters and returns a boolean that indicates when the task succeed or fail. The next code defines a simple task:

using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace CustomTask
{

    public class MyTask : Task
   {
       private string FmyProperty;

       [Required]
       public string MyProperty
      {
         get
         {
            return FmyProperty;
         }
         set
         {
             FmyProperty = value;
          }
   }

   public override bool Execute()
   {
     Console.WriteLine("MyTask was passed with property: " + MyProperty);
     return true;
   }
 }
}

This task only prints a simple message with the value of MyProperty.

Next we build this task and put the CustomTask.dll output file in the MSBuild directory. Now we define a project file that executes the CustomTask.

<Project>
   <UsingTask TaskName="CustomTask.MyTask" AssemblyName="CustomTask"/>
   <Target Name="MainTarget">
      <Task Name="MyTask" MyProperty="Hello!"/>
   </Target>
</Project>

With the UsingTask element we specify that we use one task define it in some assembly. In the Task element we set the MyProperty value. Now we build this project file and the output is the following:

Target "MainTarget" in project "Main.proj"
Task "MyTask"
MyTask was passed with property: Hello!

Using these mechanisms we can extend the build process in several ways.

Conclusions.

In this article we had explained the MSBuild concepts and show examples that illustrate some of the main MSBuild capacities. MSBuild brings together new concepts compared with previous versions of the .NET Framework. Now we have the build process defined in a readable XML file. Also we have the core CS and VB build process defined in files that can be modified. MSBuild also presents some extensions mechanisms that permit to extend the build process in several ways. Mixing some of these potentialities we can define build processes before only dreamed in the .NET environments. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值