Calling RAKE from MSBUILD

These days, I am spending quite a bit of time with Ruby,  Ruby On Rails and RAKE. I am not into the Ruby religion yet,
and not drinking the ruby Kool-aid that much. Just a little.

I needed to integrate the TeamBuild (Team System) with Ruby/RAKE.  So in other words, I needed to
call RAKE from MSBUILD. It turned out to be slightly harder than I expected. I am new to MSBUILD, Ruby and RAKE,
so I had to figure out quite a few of things.

I ended up writing a custom task. From the custom task, I called Rake and captured the output.
Definitely not rocket science, but getting everything working properly is not easy either.

So here it is.

First the custom task.

Here is the entire code file

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Build.Framework;
using System.Diagnostics;
using System.IO;
namespace RubyIntegration
{
    public class RunRake : 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 RakeFileDir;
        [Required]
        public string RakeFileDirectory
        {
            get { return RakeFileDir; }
            set { RakeFileDir = value; }
        }

        private string RubyInstallDir;
        [Required]
        public string RubyInstallDirectory
        {
            get { return RubyInstallDir; }
            set { RubyInstallDir = value; }
        }
        private string RakeTargetName;
        [Required]
        public string RakeTarget
        {
            get { return RakeTargetName; }
            set { RakeTargetName = value; }
        }

        private string strOutput;
        [Output]
        public string RakeOutput
        {
            get { return strOutput; }
            set { strOutput = value; }
        }

        private void LogMessage(string message, MessageImportance imp)
        {
            BuildMessageEventArgs args = new BuildMessageEventArgs(
                message, string.Empty, "RubyIntegration.RunRake", MessageImportance.Normal);
            engine.LogMessageEvent(args);
        }

        public bool Execute()
        {
            bool result = false;
            try
            {
                LogMessage("***** MSBUILD  <--> RAKE ********", MessageImportance.Normal);
                LogMessage("Preparing to invoke Rake", MessageImportance.Normal);
                LogMessage("*** INPUTS", MessageImportance.Normal);
                LogMessage("Ruby Install Directory " + RubyInstallDir, MessageImportance.Normal);
                LogMessage("Rake File Directory " + RakeFileDirectory, MessageImportance.Normal);
                LogMessage("RakeTargetName " + RakeTargetName, MessageImportance.Normal);
                LogMessage("***", MessageImportance.Normal);

                LogMessage("*** CHEKING PARAMETERS", MessageImportance.Normal);
                if (Directory.Exists(RubyInstallDir)){
                    LogMessage("Ruby Install Directory " + RubyInstallDir + " exists.", MessageImportance.Normal);
                }
                else{
                    LogMessage("Ruby Install Directory " + RubyInstallDir + " does not exist - exiting task.", MessageImportance.High);
                }
                string RubyPath = Path.Combine(RubyInstallDir, "ruby.exe");
                if (File.Exists(RubyPath))
                {
                    LogMessage("Ruby.exe is found at " + RubyPath, MessageImportance.Normal);
                }
                else
                {
                    LogMessage("Ruby.exe is not found at path" + RubyPath + " . - exiting task.", MessageImportance.High);
                }
                string RakePath = Path.Combine(RubyInstallDir, "rake");
                if (File.Exists(RakePath))
                {
                    LogMessage("rake file (rake ruby code) is found at " + RakePath, MessageImportance.Normal);
                }
                else
                {
                    LogMessage("rake file (rake ruby code) is is not found at path" + RakePath + ". - exiting task.", MessageImportance.High);
                }

                LogMessage("***", MessageImportance.Normal);
                ProcessStartInfo info = new ProcessStartInfo();
                info.RedirectStandardOutput = true;
                info.UseShellExecute = false;
                info.FileName = RubyPath;
                info.Arguments = RakePath + " "  + RakeTargetName;
                info.WorkingDirectory = RakeFileDirectory;
                Process p = Process.Start(info);
                strOutput = p.StandardOutput.ReadToEnd();
                p.WaitForExit();
                result = true;
            }
            catch (Exception ex)
            {
                LogMessage(ex.ToString(), MessageImportance.High);
            }
            return result;
        }
    }
}

 

Here is the MSBUILD FILE

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask AssemblyFile="RunRake.dll" TaskName="RubyIntegration.RunRake" />
  <Target Name="default" >
    <RunRake    RubyInstallDirectory ="C:\ruby\bin"
                RakeFileDirectory="$(MSbuildProjectDirectory)\ruby\edgeserver"
                RakeTarget="alcoholic:getSmashed">
      <Output TaskParameter="RakeOutput" PropertyName="RakeOutput"/>
    </RunRake>
    <Message Text="The output from Rake is... $(RakeOutput)" />
  </Target>
</Project>

NOTES

Fist I attempted to creating a RAKE process by trying to call rake. (pass rake as the file name to the process start info).
The process failed to start saying that the file not found.
Then I tried with rake.bat (pass rake.bat as the file name to the process start info).
The problem seems to be that RAKE.BAT does some
manipulation and calls ruby.exe and passes the file "rake" to ruby.exe. Normally it would end up something like
[ruby.exe c:\ruby\bin\rake ..].
However The path to rake is getting resolved incorrectly when I pass the rake.bat as the
program name. It is using the working directory and ending up with

[ruby.exe <MY WORKING DIR>\rake...]
Obviously there is no file called "rake" in my working directory.
I need to resolve this, but for now this should get me going.

 

The installation for for Ruby could be possibly obtained by resolving
the file extension .rb to an executable. That is another task that can be added
to eliminate the need to send the ruby install directory as a parameter.