How A .NET Developer Hacked Out A Rake Task

I spent the last two days having my brain melted by Scott Bellware, in a crash course on Ruby, Rake, RSpec, Selenium, web UI test automation best practices, and all-around uber-normal-person-language-oriented development. It was great! After the first 7 hour day, I felt like I had been awake for 24 hours. After the second 8 hour day, I felt like I actually knew something about Ruby and good web UI test automation practices.

 

Will The Real Rake Task Please Stand Up

In the conversations and snappy banter that ensued during and after the sessions, Scott and I talked a little about Rake and building what he referred to as ‘real’ Rake tasks instead of the ‘not-Rakeish’ tasks that most people build. By ‘not-Rakeish’, Scott was talking about our propensity as .NET developers to make everything a class and make heavy use of the “system.Object of Rake: the ‘task’”. This point is illustrated by a google search of ‘build custom rake task’. Practically every result that comes up will show you how to use “task :somename do … end” and call “rake :somename”.

Whether or not you think using standard ruby objects in a standard rake ‘task’ is ‘Rakeish’ or not, isn’t really the point. Of course that works. Yes, many people do it that way, because it works. The point Scott was making is that professional tools like RSpec and Selenium don’t provide tasks in that way.  We don’t see these simple constructs being used. Rather, we see professional tasks being built so that we can call something more like this:

   1: Spec::Rake::SpecTask.new do |t|

   2:   t.ruby_opts = ['-rtest/unit']

   3:   t.spec_files = FileList['test/**/*_test.rb']

   4: end

That conversation with Scott and some other conversations around the idea of building .NET solutions with Rake got me thinking about how we could build a much more ‘professional’, ‘real’ Rake task to call out to MSBuild. While I have not yet put this solution together, I have spent some time learning how to build tasks such as the RSpec ‘SpecTask’ shown above. Not surprisingly, it’s a very simple endeavor. I honestly don’t claim to understand exactly why everything in my hacked-together custom task is set up the way it is, but I have a pretty good idea of what’s going on, at this point. So, I wanted to share my newfound knowledge and hopefully inspire some of the .NET Rake building people out there to create something a little more ‘Rakeish’, rather than just using the default ‘task :whatever’ syntax and calling into an ‘MSBuild’ object.

 

Defining A Custom Rake Task Using TaskLib

Since I was unable to find any good info via my google search, I started examining the RSpec rake task and the Selenium rake tasks that were installed via the gems. The first thing I found is that I needed to have the ‘rake’ and ‘rake/tasklib’ files, so that I could create a class that inherits from ‘TaskLib’. For example, I can create a task called “FooTask” like this:

   1: require 'rake'

   2: require 'rake/tasklib'

   3:  

   4: class FooTask < Rake::TaskLib

   5: end

This basic definition gives me a task that I can use via Rake. However, it doesn’t really do anything yet and doesn’t even have a name that we can call from rake.

 

Giving The Task A Name

To set up a good task name to use, we need to include a ‘:name’ accessor (think auto property in c#) and parameter in the initializer. This name will be used later, to tell the TaskLib base class what the name of the task is, at runtime.

   1: class FooTask < Rake::TaskLib

   2:   attr_accessor :name

   3:   def initialize(name = :some_default_name)

   4:     @name = name

   5:   end

   6: end

Notice the ‘:some_default_name"’ – this does exactly what it sounds like… sets the default name of the task. In this case, we are literally giving the name a default of ‘some_default_name’. We are then setting the @name (which has been automatically generated by the ‘attr_accessor :name’ usage… again, this is like an auto property in C#).

 

Letting The Task Do Custom Work

If we want to provide the ability for the user of the task to actually do some work, we need to include the lambda block evaluation that ruby has built in. This is done by yielding self if a lambda block is present, in the constructor.

   1: def initialize(name = :test)

   2:   @name = name

   3:   yield self if block_given?

   4: end

this block of code let’s us call our custom task with a ‘do … end lambda’ block

   1: FooTask.new do |t|

   2:  #do stuff with t, here

   3: end

The ‘|t|’ in this code refers to the actual FooTask instance that we yielded (‘self’), and will let us have access to all of the methods in the FooTask object, once we add some.

 

Defining The Default Work To Do

In all of the custom rake tasks that I have examined so far, I have found a method called ‘define’ that appears to be a standard convention for rake tasks as the location of code that gets executed by the custom task that we are creating. This method calls the TaskLib method ‘task’ with a name parameter (the name we supplied in the initializer) and then does the work. For example, RSpec does all of it’s spec parsing and running from here, and Selenium does all of it’s server initializing or shutdown here. For this example, we’re just going to print some junk out to the screen.

   1: def define

   2:   task name do

   3:     puts 'some test being printed'

   4:   end

   5: end

The ‘task name’ code is important here, as it provide rake with the knowledge of the task’s name, so that we can call it from the rake command line.

 

Running What We Have

At this point, we have enough code in place to run the sample rake task. Create a ‘rakefile’ with all of the above code, then add code similar to how we called the ‘SpecTask’ earlier. The contents of the rakefile should be:

   1: require 'rake'

   2: require 'rake/tasklib'

   3:  

   4: class FooTask < Rake::TaskLib

   5:  

   6:   attr_accessor :name

   7:  

   8:   def initialize(name = :test)

   9:     @name = name

  10:     yield self if block_given?

  11:     define

  12:   end

  13:  

  14:   def define

  15:     task name do

  16:       puts 'some test being printed'

  17:     end

  18:   end

  19: end

  20:  

  21: FooTask.new :my_task do

  22: end

Notice that we are giving the task a new name when we call ‘FooTask.new :my_task’. The name is being set to ‘my_task’. We can now run the task from rake via a shell prompt (command prompt in windows):

image

So we now have a custom rake task that is much more ‘Rakeish’. However, it’s still not terribly useful. There is no way to get data into the task and there is no way to call any dependencies before this task.

 

Adding Data To The Task

To add data, methods, or do something useful with the task, we can use all the usual ruby ways – methods with parameters, with ‘=’ assignment, attr_accessors, etc etc. For simplicity, let’s use an attr_accessor to create a ‘property’ (I know, it’s not really a property in Ruby, but it sure behaves like one). We’ll add an accessor call ‘:data’ and we’ll have the define method write that data out instead of our hard coded string.

   1: class FooTask < Rake::TaskLib

   2:  

   3:   attr_accessor :name, :data

   4:  

   5:   def initialize(name = :test)

   6:     @name = name

   7:     yield self if block_given?

   8:     define

   9:   end

  10:  

  11:   def define

  12:     task name do

  13:       puts @data

  14:     end

  15:   end

  16: end

Adding the data accessor will let us use the ‘|t|’ yielded to our task at runtime, like this:

   1: FooTask.new :my_task do |t|

   2:   t.data = "I'm using the data accessor!"

   3: end

Now when we run the task again, we get this output:

image

Using this basic pattern, along with other methods, accessors, etc. we can begin to create a task that can do much more for us by allowing the user of the task to provide configuration of the task at runtime.

 

Calling Dependency Tasks

The last thing I want to show for now, is how to add the ability to call task dependencies when calling our custom task.

In the ‘task :somename’ style of usage, we may want to call other tasks as dependencies of ‘somename’. This is done by using a lambda with the task names. For example:

   1: task :somename => [:somedepdency, :anotherdependency, :whatever, :etc] do

   2:  # do stuff here

   3: end

This code would call the ‘somedependency’, ‘anotherdependency’, ‘whatever’, and ‘etc’ tasks before running the ‘somename’ task.

To provide this same dependency execution in our custom task, we first need to provide a way to specify the dependencies. We can do this by adding a second parameter to the initializer, with a default value that shows we have no dependencies, and then calling the that parameter as a method or hash of methods after storing it in another accessor.

   1: class FooTask < Rake::TaskLib

   2:  

   3:   attr_accessor :name, :task_dependencies, :data

   4:  

   5:   def initialize(name = :test, task_dependencies = {})

   6:     @name = name

   7:     yield self if block_given?

   8:     @task_dependencies = task_dependencies

   9:     define

  10:   end

  11:  

  12:   def define

  13:     task name => task_dependencies do

  14:       puts @data

  15:     end

  16:   end

  17: end

Notice the additional ‘task_dependencies’ accessor, the assignment of an empty hash as the default value in the initializer, storage of the parameter in the ‘task_dependencies’ accessor variable, and then the ‘ => task_dependencies’ lambda execution in the define method. This basic code set allows us to specify a number of dependent tasks and have them executed prior to our custom task executing.

To set a dependency for this task, we only need to add the task names as the second parameter to the constructor in our rakefile. Here’s example rakefile with all of our code, a simple task to use as a dependency, and our custom task being called with that dependency.

   1: require 'rake'

   2: require 'rake/tasklib'

   3:  

   4: class FooTask < Rake::TaskLib

   5:  

   6:   attr_accessor :name, :task_dependencies, :data

   7:  

   8:   def initialize(name = :test, task_dependencies = {})

   9:     @name = name

  10:     yield self if block_given?

  11:     @task_dependencies = task_dependencies

  12:     define

  13:   end

  14:  

  15:   def define

  16:     task name => task_dependencies do

  17:       puts @data

  18:     end

  19:   end

  20: end

  21:  

  22: FooTask.new :my_task, :call_me_first do |t|

  23:   t.data = "I'm using the data accessor!"

  24: end

  25:  

  26: task :call_me_first do

  27:   puts "I'm called first, because I'm the dependency!"

  28: end

The output of this task with its dependency is this:

image

 

Moving On

Now that we have all of the core parts of a real, ‘Rakeish’ custom task in place, we can easily move our FooTask out into it’s own ‘FooTask.rb’ file and have it be a required file in our Rakefile. We could even package this up as a gem and have it be available for installation via the gem system. However, this is not a professional quality task, at this point. There are no tests written for this task, and it doesn’t really do anything valuable other than show how to hack together a custom rake task.

I hope that this little tutorial will at least provide some inspiration to the rest of my Rake using .NET developing colleagues out there in the world, though. I’d really like to see (and will probably build and distribute) a much more professional ‘MSBuild’ rake task, based on this new found knowledge. There are plenty of MSBuild ruby objects out there… it shouldn’t be that difficult to convert it into a rake task and put some unit tests around it.


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in .NET, C#, Craftsmanship, Lambda Expressions, Rake, Ruby. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • mendicant

    I started out using this form for my project, Rubicant. After a while, I realized that the issues that I have to go through in order to have a custom MsBuild far outweighed the much easier solution of having an MsBuild class.

    For example:

    MsBuildTask.new (:compile, [:call_first, :call_second], {:opt => ‘opt1′, :opt2 => ‘opt2′}) do

    end

    doesn’t feel as nice to me as eventually moving towards:

    task :compile => [:call_first, :call_second] do
    MsBuild.new(:opt1 => ‘opt1′, :opt2 => ‘opt2′)
    end

    In the end, I decided that letting Rake handle the tasks and just creating custom classes (or custom methods which wrap those classes) feels and looks much nicer than custom tasks a la NAnt.

    Remember, once you get into Rake, you have the full power of an entire language behind you and it’s easy to take advantage of.

  • http://blog.beigesunshine.com mendicant

    To try and be a little more clear…

    In the end, I found that once you start having to pass information into your custom task, such as the sln file, build properties like Output dir and such, that having everything in a custom task just got too messy.

    In your define, you just end up wrapping what is essentially a custom class with:
    task :name => :dependencies do
    #custom code
    end

    So why do all the extra work, violate SRP, etc just to end up with less clean and expressive code that could just be avoided by having the person using the task just write: write the task :name => :dependencies themselves.

    Also, using your footask example, what if they do:

    FooTask.new :name, [:dependencies] do
    #some more stuff here
    end

    Where do you do the #some more stuff here part… before or after? Even better, since you’ve made this a custom task, will you ever really need to do anything inside the block?

    These aren’t all answers that I have. Just things that I’ve been thinking about a lot.

  • http://www.lostechies.com/members/derick.bailey/default.aspx derick.bailey

    @mendicant,

    You bring up some good questions that I’m going to have to think through while playing with this idea.

    In terms of any extra work that it would take to maintain a custom task and an msbuild object – they are separate objects. I am calling ‘yield @msbuild if block_given?’ where @msbuild is a var pointing to an MSBuild object.

    with this in play, my custom task is all of 27 lines of code, and there’s not much else that I’ll have to do with it, if anything. I should be able to continue expanding the core MSBuild object without changing the task. this should let people have the choice of using the custom task or the generick task w/ the MSBuild class directly.

    If anyone’s interested in seeing what I’m doing, it’s up on GitHub: http://github.com/derickbailey/Albacore/ – it’s still very much a work in progress, though… don’t expect it to be a great solution, yet. :)

  • http://www.lostechies.com/members/derick.bailey/default.aspx derick.bailey

    @mendicant,

    just checked out your ‘rubicant’ project on github… looks like great stuff! i’m probably just going to throw out what i’m playing with after a bit, and fork your solution so I can add on to it.