Embed Ruby code directly inside MSBUILD file using DLR and IronRuby
You know the MSBUILD drill. Everything is a custom task. Need to add two numbers? Write a custom task.
Need to replace spaces in a string with '_' character? Write a custom task. I don't like the overhead of
writing custom tasks.
What you have with MSBuild is bascially an XML file and good amount of "build logic". Anything that you
could have done with even a simple batch script has to be in a custom task. I don't like to have
some code in the MSBUILD script and some other code in a custom task tucked in somewhere else.
I want to be able to embed a simple script in the MSBuild file. I searched for it and I could not find a way.
I have to to roll my own way? Well thats what I did.
I found that I could actually embedding IronRuby in MSBuild file.
<Project DefaultTargets="ExecuteTest" xmlns="https://schemas.microsoft.com/developer/msbuild/2003"><UsingTask TaskName="MSBuildTasks.RubyHost" AssemblyFile="MSBuild_Ruby.dll"/><PropertyGroup>
<RubyScript><![CDATA[
File.open("MyTestFile.txt", "w") do |file|
5.times do |i|
file.write "This is line #{i}\n"
end
end
"success"
]]></RubyScript>
</PropertyGroup>
<Target Name="ExecuteTest">
<RubyHost ScriptSource="$(RubyScript)">
<Output TaskParameter="ReturnValue" PropertyName="ReturnValue" />
</RubyHost>
<Message Text="$(ReturnValue)" />
</Target>
</Project>
With this, you can see that the ruby code is embedded within the MSBUILD file itself, and no need to maintain
separate set of tasks. NO MORE a task for adding, deleting, and multiplying numbers!!.
Of course you need 1 custom task that hosts the ruby engine and runs the ruby code for you.
Here is the source code for the custom task
using
System;
using
System.Collections.Generic;
using
System.Text;
using
Microsoft.Scripting.Hosting;
using
IronRuby.Runtime;
using
IronRuby;
using
Microsoft.Build.Framework;
namespace
MSBuildTasks
{
public class RubyHost : ITask
{
private IBuildEngine engine;
public IBuildEngine BuildEngine
{
get { return engine; }
set { engine = value; }
}
private ITaskHost host;
public ITaskHost HostObject
{
get { return host; }
set { host = value; }
}
private string scriptSource;
[
Required]
public string ScriptSource
{
get { return scriptSource; }
set { scriptSource = value; }
}
private string returnValue;
[
Output]
public string ReturnValue
{
get { return returnValue; }
}
private void LogMessage(string message, MessageImportance imp)
{
BuildMessageEventArgs args = new BuildMessageEventArgs(
message,
string.Empty, "RubyIntegration.RunRake", MessageImportance.Normal);
engine.LogMessageEvent(args);
}
public bool Execute()
{
try
{
var runtime = IronRuby.Ruby.CreateRuntime();
ScriptEngine Ruby_Engine = runtime.GetEngine("Ruby");
ScriptSource src = Ruby_Engine.CreateScriptSourceFromString(scriptSource, Microsoft.Scripting.SourceCodeKind.Statements);
Object result = src.Execute();
returnValue = result.ToString();
}
catch (Exception ex)
{
LogMessage(ex.ToString(),
MessageImportance.High);
}
return true;
}
}
}
I know that the code is quick and dirty and can be improved on this.
I hope to enhance it soon. But I can't contain my joy and wanted to
spread the love....
Let me know if you liked this idea and share any further thoughts
Comments
Anonymous
September 11, 2009
Very cool Durgaprasad! I have run many times into situations where it would be more convenient to add logic to simplify an msbuild script. In these situations I have typically created a custom task but the development overhead is always making the work longer than it should. Since I really like IronRuby, I will now be able to regain some productivity back. Thanks!Anonymous
December 15, 2009
Awesome! Durga garu. I guess this is one the best way to deal with custom tasks. Now all we need is one task rest are all scripts.