.NET 4.0 MSBuild API introduction

.NET 4.0 MSBuild API introduction

A new MSBuild API was introduced in .NET 4.0. It is documented here . It comes in three namespaces :

  • Microsoft.Build.Construction
  • Microsoft.Build.Evaluation
  • Microsoft.Build.Execution

If you compile under .NET 4.0 a program using the Microsoft.Build.BuildEngine.Engine class, from the old API, the compiler warns you that this class is obsolete (deprecated), and that you must instead use the Microsoft.Build.Evaluation.ProjectCollection class.

I faced this problem by migrating Perspective FX under .NET 4.0. This library can generate Pixel Shader effects with their .NET wrapper, and produce an assembly using MSBuild.

Unfortunately, until today I have found no examples of implementation of the new API, nor in the documentation or on the Web. So I decided to write this article to share my experiences.

The example described below shows how to dynamically create an MSBuild project file for Silverlight and then run the MSBuild compilation. The full code (for a Silverlight or a WPF project) can be viewed here .

Reading this article requires a basic knowledge of the constituents of an MSBuild project.

Reference assemblys

To use the MSBuild 4 API, it is necessary to add a reference to these assemblies :

  • Microsoft.Build.dll
  • Microsoft.Build.Framework.dll

The following namespaces are referenced in the code :

using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Logging;

Project file creation

The Project class from the namespace Microsoft.Build.Evaluation represents an MSBuild project. The first thing to do is to instantiate it :

Project project = new Project();

The namespace Microsoft.Build.Evaluation centralizes the classes to evaluate an MSBuild project.

The definition of a project property is done through the SetProperty method :

project.SetProperty("DefaultTargets", "Build");

The ProjectElement abstract class represents a project item. Concrete classes inherit from it, such as:

  • ProjectPropertyElement,
  • ProjectImportElement,
  • ProjectElementContainer, ProjectElement objects container ; the properties FirstChild, LastChild, Children and Count allow to find child elements ; AppendChild, InsertAfterChild, InsertBeforeChild or Prepend methods allow to position an element,
  • etc.

These classes are defined in the namespace Microsoft.Build.Construction.

The project content is defined using the Xml property of the Project object. This property references an object of type ProjectRootElement, class inherited from ProjectElementContainer. It has methods to create child elements, such as :

  • CreatePropertyGroupElement to create a PropertyGroup element,
  • CreateItemGroupElement to create an ItemGroup element,
  • etc.
var propertyGroup = project.Xml.CreatePropertyGroupElement();

After being created, an element must be explicitly positioned in the tree :

project.Xml.InsertAfterChild(propertyGroup, project.Xml.LastChild);

The PropertyGroup class has an AddProperty method, which adds a child element :

propertyGroup.AddProperty("TargetFrameworkVersion", "v4.0");

An ItemGroup section is generated the same way :

var slItemGroup = project.Xml.CreateItemGroupElement();
project.Xml.InsertAfterChild(slItemGroup, project.Xml.LastChild);
slItemGroup.AddItem("Reference", "mscorlib");
slItemGroup.AddItem("Reference", "System");
slItemGroup.AddItem("Reference", "System.Core");
slItemGroup.AddItem("Reference", "System.Windows");

The principle remains the same for the different types of elements : UsingTask, Import, Target, etc.

var usingTaskElement2 = project.Xml.CreateUsingTaskElement("Perspective.PixelShader.BuildTask.PixelShaderBuildTask", null, "Perspective.PixelShader.BuildTask");
project.Xml.InsertAfterChild(usingTaskElement2, project.Xml.LastChild);
var projectImportElement1 = project.Xml.CreateImportElement(@"$(MSBuildExtensionsPath32)\Microsoft\Silverlight\v4.0\Microsoft.Silverlight.CSharp.targets");
project.Xml.InsertAfterChild(projectImportElement1, project.Xml.LastChild);
var buildTarget = project.Xml.CreateTargetElement(buildTargetName);
project.Xml.InsertAfterChild(buildTarget, project.Xml.LastChild);
buildTarget.Condition = "'@(" + pixelShaderItemEntry + ")' != ''";

A task and its parameters are configured as follows :

var buildTask = buildTarget.AddTask("PixelShaderBuildTask");
buildTask.SetParameter("SourceFiles", "@(" + pixelShaderItemEntry + ")");
buildTask.AddOutputItem("OutputFiles", "Resource");

Finally, the Save method of the Project class creates the project file :

project.Save(projectFileName);

Here is the resulting project file:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup>
    <DefaultTargets>Build</DefaultTargets>
  </PropertyGroup>
  <UsingTask TaskName="Perspective.PixelShader.BuildTask.PixelShaderBuildTask" AssemblyName="Perspective.PixelShader.BuildTask" />
  <PropertyGroup>
    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    <SchemaVersion>2.0</SchemaVersion>
    <ProjectGuid>5efb8945-ccba-49f2-a884-6984421cddb0</ProjectGuid>
    <OutputType>Library</OutputType>
    <RootNamespace>FxDemo.Effects</RootNamespace>
    <AssemblyName>FxDemo.Effects</AssemblyName>
    <WarningLevel>4</WarningLevel>
    <ErrorReport>prompt</ErrorReport>
    <GenerateResourceNeverLockTypeAssemblies>true</GenerateResourceNeverLockTypeAssemblies>
    <OutputPath>bin\Silverlight\Release</OutputPath>
  </PropertyGroup>
  <PropertyGroup>
    <DebugType>pdbonly</DebugType>
    <Optimize>true</Optimize>
    <DefineConstants>TRACE;SILVERLIGHT</DefineConstants>
  </PropertyGroup>
  <PropertyGroup>
    <SilverlightApplication>false</SilverlightApplication>
    <NoStdLib>true</NoStdLib>
    <NoConfig>true</NoConfig>
  </PropertyGroup>
  <ItemGroup>
    <Reference Include="mscorlib" />
    <Reference Include="System" />
    <Reference Include="System.Core" />
    <Reference Include="System.Windows" />
  </ItemGroup>
  <ItemGroup>
    <Compile Include="ClipperEffect.cs" />
    <Compile Include="ColorTonerEffect.cs" />
    <Compile Include="ConvolutionEffect.cs" />
    <Compile Include="DisplaySettingEffect.cs" />
    <Compile Include="GrayScalerEffect.cs" />
    <Compile Include="InverterEffect.cs" />
    <Compile Include="LithEffect.cs" />
    <Compile Include="MixerEffect.cs" />
    <Compile Include="SolarizerEffect.cs" />
    <Compile Include="SubtractorEffect.cs" />
    <Compile Include="WaveEffect.cs" />
  </ItemGroup>
  <ItemGroup>
    <PixelShader Include="ClipperEffect.fx" />
    <PixelShader Include="ColorTonerEffect.fx" />
    <PixelShader Include="ConvolutionEffect.fx" />
    <PixelShader Include="DisplaySettingEffect.fx" />
    <PixelShader Include="GrayScalerEffect.fx" />
    <PixelShader Include="InverterEffect.fx" />
    <PixelShader Include="LithEffect.fx" />
    <PixelShader Include="MixerEffect.fx" />
    <PixelShader Include="SolarizerEffect.fx" />
    <PixelShader Include="SubtractorEffect.fx" />
    <PixelShader Include="WaveEffect.fx" />
  </ItemGroup>
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Silverlight\v4.0\Microsoft.Silverlight.CSharp.targets" />
  <Target Name="PixelShaderCompile" Condition="'@(PixelShader)' != ''">
    <PixelShaderBuildTask SourceFiles="@(PixelShader)">
      <Output TaskParameter="OutputFiles" ItemName="Resource" />
    </PixelShaderBuildTask>
  </Target>
  <PropertyGroup>
    <PrepareResourcesDependsOn>PixelShaderCompile;$(PrepareResourcesDependsOn)</PrepareResourcesDependsOn>
  </PropertyGroup>
</Project>

MSBuild Compilation

ProjectCollection class manages the evaluation of MSBuild project files (or equivalent memory structures).

LoadProject method adds a Project object to the collection by loading it from a project file.

var projectCollection = new ProjectCollection();
projectCollection.DefaultToolsVersion = "4.0";
projectCollection.LoadProject(projectFileName);

Another approach is to pass the ProjectCollection object the constructor of the Project object (that will be created by code and non-loaded from a file) :

var projectCollection = new ProjectCollection();
projectCollection.DefaultToolsVersion = "4.0";
Project project = new Project(projectCollection);

MSBuild compilation is triggered by calling the Build method of the Project object.

To view or save the compilation report, the ProjectCollection object uses loggers that must be registered before, using the RegisterLoggers method. Our example uses the ConsoleLogger class that uses the console output.

ConsoleLogger logger = new ConsoleLogger();
List<ILogger> loggers = new List<ILogger>();
loggers.Add(logger);
//...
projectCollection.RegisterLoggers(loggers);
try
{
    project.Build();
}
finally
{
    projectCollection.UnregisterAllLoggers();
}

About this article

Author : Olivier Dewit.

History :

  • April 5, 2011: first publication.