Skip to content

Take control of MSBuild using MSBuild API

March 15, 2012

When you press Build in Visual Studio, MSBuild is invoked and made to execute the task called Build. Clean - MSBuild executes the task Clean. And so on.

How do you think Visual Studio invokes MSBuild ? If you take a look on stackoverflow you will see the majority of answers point to using MSBuild command line interface. And for good reason, it is quite versatile – you can add different loggers, specify tasks, pass in parameters and so much more. So naturally, an easy fix is to create a simple process that executes a command line, and redirect the log to a file. This is all good and noble, but:

 

Using the MSBuild cli sometimes limits flexibility

You are basically doing inter process communication – wait for the cli to finish and read some file. What if you want to react sooner to some event, like cancel the build mid way because of some change? For example, if you press CTRL+Break in Visual Studio it cancels the build. In this article I will show you how Visual Studio communicates with MSBuild via the MSBuild API and solve an actual problem.

 

The problem

My goal is to go over hundreds of solutions (retrieved from sites like codeplex) and see if the Architecture > Dependency Diagram works for them. But first, I need to make sure they build. It can be done using the command line, but I want to call this verification from different threads and inter process communication would complicate things. Also, I dread parsing log files!

 

MSBuild API 4.0 in action

The actual solution is also used by Visual Studio, however I found that the MSBuild API is not advertised a lot. There were major changes between versions 3.5 and 4 of the .net framework, so a lot of blog posts are out of date.

Anyway, here we go. Add MSBuild API references (I overindulged here, you can get away with less)

 

MSBuild API references

 

 

Now let’s invoke MSBuild API to build a solution:

 


string projectFilePath = Path.Combine(@"D:\solutions\App\app.sln");

ProjectCollection pc = new ProjectCollection();

// there are a lot of properties here, these map to the msbuild CLI properties
Dictionary<string, string> GlobalProperty = new Dictionary<string, string>();
GlobalProperty.Add("Configuration", "Debug");
GlobalProperty.Add("Platform", "x86");
GlobalProperty.Add("OutputPath", @"D:\Output");

BuildParameters bp = new BuildParameters(pc);
BuildRequestData BuidlRequest = new BuildRequestData(projectFilePath, globalProperty, "4.0", new string[] { "Build" }, null);
// this is where the magic happens - in process MSBuild
BuildResult buildResult = BuildManager.DefaultBuildManager.Build(bp, BuidlRequest);
// a simple way to check the result
if (buildResult.OverallResult == BuildResultCode.Success)
{              
    //...
}


Get more out of MSBuild with a custom logger

My next goal is to capture some high level details about the projects built in case of build success and to log the errors in case the build fails. And I found this is quite easy to do using a custom logger. So here’s how to create your own logger:

 

</pre>
private StringBuilder errorLog = new StringBuilder();

public string BuildErrors { get; private set; }

/// <summary>
 /// This will gather info about the projects built
 /// </summary>
 public IList<string> BuildDetails { get; private set; }

/// <summary>
 /// Initialize is guaranteed to be called by MSBuild at the start of the build
 /// before any events are raised.
 /// </summary>
 public override void Initialize(IEventSource eventSource)
 {
BuildDetails = new List<string>();

// For brevity, we'll only register for certain event types.
eventSource.ProjectStarted += new ProjectStartedEventHandler(eventSource_ProjectStarted);
eventSource.ErrorRaised += new BuildErrorEventHandler(eventSource_ErrorRaised);

 }

void eventSource_ErrorRaised(object sender, BuildErrorEventArgs e)
 {
 // BuildErrorEventArgs adds LineNumber, ColumnNumber, File, amongst other parameters
 string line = String.Format(": ERROR {0}({1},{2}): ", e.File, e.LineNumber, e.ColumnNumber);
 errorLog.Append(line + e.Message);
 }


 void eventSource_ProjectStarted(object sender, ProjectStartedEventArgs e)
 {
 BuildDetails.Add(e.Message);
 }


 /// <summary>
 /// Shutdown() is guaranteed to be called by MSBuild at the end of the build, after all
 /// events have been raised.
 /// </summary>
 public override void Shutdown()
 {
 // Done logging, let go of the file
 BuildErrors = errorLog.ToString();

}
<pre>

Registering the log 

Now that we created a custom logger, all we need to do is it pass it to  MSBuild, like this:

 

BuildParameters bp = new BuildParameters(pc);
MsBuildMemoryLogger customLogger = new MsBuildMemoryLogger();
bp.Loggers = new List<ILogger>() { customLogger };

Resources

This MSDN page features the entire sample on how to build a custom logger.

I also found this blog that describes how to inject stuff into a csproj.

About these ads
One Comment leave one →
  1. May 2, 2013 5:00 pm

    How do you map the command line flags to MSBuild API? For instance:
    msbuild Website.csproj “/p:Platform=AnyCPU;Configuration=Release;DesktopBuildPackageLocation=F:\Temp\Publish\Website.zip” /t:Package

    How do I map that /t:Package to your sample code above?

    Thanks in advance,

    -Serkan Sendur

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: