Thursday, October 9, 2008

Running MSBuild tasks programmatically

I tried scouring the web for a description on how to run MSBuild tasks from your own code but came up with slim results. After some investigations this is one way that managed to run some tasks at least.

For this example, we'll compile a .cs file in the simplest way possible. To achieve this we need to use the Csc task.

There is one property on all tasks that deserves a special mention. It is Task.BuildEngine. The property is of type IBuildEngine and as far as I can tell the only implementation of this interface is an internal class called EngineProxy in the Microsoft.Build.BuildEngine namespace. Since it's internal we cannot use it.

We need to create our own IBuildEngine implementation. It turns out that this is pretty straightforward:


public class MyBuildEngine : IBuildEngine
{
public bool BuildProjectFile(string projectFileName, string[] targetNames,
IDictionary globalProperties,
IDictionary targetOutputs)
{
throw new NotImplementedException();
}

public int ColumnNumberOfTaskNode
{
get { return 0; }
}

public bool ContinueOnError
{
get { return false; }
}

public int LineNumberOfTaskNode
{
get { return 0; }
}

public string ProjectFileOfTaskNode
{
get { return ""; }
}

public void LogCustomEvent(CustomBuildEventArgs e)
{
Console.WriteLine("Custom: {0}", e.Message);
}

public void LogErrorEvent(BuildErrorEventArgs e)
{
Console.WriteLine("Error: {0}", e.Message);
}

public void LogMessageEvent(BuildMessageEventArgs e)
{
Console.WriteLine("Message: {0}", e.Message);
}

public void LogWarningEvent(BuildWarningEventArgs e)
{
Console.WriteLine("Warning: {0}", e.Message);
}
}
This implementation is very simple.
  • We'll simply ignore BuildProjectFile since we're running tasks manually here.
  • The *TaskNode methods are also almost ignored. I haven't investigated them but it looks like they're used for reporting errors in project files.
  • The Log* methods simply dump the message to the console.
Now we're ready to run the Csc task:


Csc cscTask = new Csc();
cscTask.BuildEngine = new MyBuildEngine();
cscTask.Sources = new TaskItem[]{
new TaskItem(@"C:\Code\Projects\MSBuildTest\HelloWorld\HelloWorld.cs")
};

if (cscTask.Execute())
{
Console.WriteLine("Task executed ok. Resulting assembly: {0}",
cscTask.OutputAssembly
);
}

This will produce an output file HelloWorld.exe in the working directory.

Obviously, there are lots of other properties available on the Csc task but this was just the simplest example possible.

Monday, October 6, 2008

BuildLib

I initiated yet another side project this weekend. It's an idea that has been growing on me for a while.

A build engine that uses C# as a scripting language.

The recipe for a build frequently includes a number of tools. A few utility .exes here, some .bat files there. A bit of scripting in NAnt or MSBuild. Maybe even some custom extensions to the build engine. Finally, a little continuous integration scripting to finish it all off.

Wouldn't it be neater to unify this into one tool?
Why should I need to learn yet another syntax (i.e. Nant or MSBuild)?
Why create clumsy custom language using XML when C# is more powerful then msbuild will ever be?

Imagine that you want to have a file that contains a simple timestamp. Something along the lines of:
namespace MyProject {
public class BuildInfo {
public string BuildDate = "_BUILDDATE_PLACEHOLDER_";
}
}

And you want to transform this into:
namespace MyProject {
public class BuildInfo {
public string BuildDate = "2008-10-07";
}
}

How would you update this using msbuild? My best bet is that you would need to obtain and include MSBuild community tasks and use FileUpdate task. I have no idea how to obtain a properly formatted date...

Compare this to:
string templateText = File.ReadAllText("BuildDate.cs.template");
string buildDate = DateTime.Now.ToShortDateString();
string updatedText = templateText.Replace("_BUILDDATE_PLACEHOLDER_", buildDate );
File.WriteAllText("BuildDate.cs", updatedText);

To solve some of these issues I initiated a project with the boring yet appropriate name BuildLib.

So, what is the current state of BuildLib?
  • It has a name! That took me a good two hours.
  • It has project page on google code.
  • It has stub projects and a directory structure (heavliy influenced by JP Boodhoos post)
I'll try to use this blog as a design diary for BuildLib. We'll see where the project heads. Perhaps it will turn out to be a fluent interface over MSBuild, a full fledged build engine or (perhaps most likely) yet another abandoned side project that got 60% done.

Since I have a 8 week old girl at home I don't expect any abundance of free time. On the other hand, she normally wakes me up at 6 in the morning so I do have an hour or two before work...