Rake recipes for working with Visual Studio projects

Despite spending my day job coding in Microsoft-land, I find myself using ruby tools more and more during my daily development. I recently wrote some rake tasks that I think are worth sharing and explaining. Specifically, I wrote a tasks to control building with msbuild (seems redundant, I know) and some tasks for starting and stopping Cassini (or webdev.webserver as it is now named).

Monkey patch Pathname for Windows paths

Since I am using the Pathname class to build paths in my examples, I need to give you the monkey patch that I use to make Pathname correctly display win32 paths.


require 'pathname'
class Pathname
  alias_method :original_to_s, :to_s
  def to_s
    original_to_s.gsub('/', '\\')
  end
end

Visual Studio command line environment

Running command line Visual Studio tools requires having certain environment variables loaded. This can be done by running vsvars32.bat directly or by launching a Visual Studio command prompt from the start menu. This is something that I always forget to do; my terminal windows spring into life by typing cmd in the run box. So, I wanted to write a task to ensure that the environment was properly set up.

I am working with Visual Studio 2005. If you want to use the Visual Studio 2008 tools then you will need to adjust the vsvars32_bat variable accordingly.


vsvars32_bat = Pathname.new(
  "c:\\program files\\") +
  "microsoft visual studio 8" +
  "common7\\tools\\vsvars32.bat"
task :vsvars do
  if ENV["VSINSTALLDIR"].nil?
    `\"#{vsvars32_bat}\" && set`.each do |line|
      if line =~ /(\w+)=(.+)/
        ENV[$1] = $2
      end
    end
  end
  raise "Eek!" if ENV["VSINSTALLDIR"].nil?
end

This code is the product of about 30 minutes of googling. I eventually found this trick in the shoes rakefile[1].

Now any task that needs to call a Visual Studio command line tool just needs to declare vsvars as a prerequisite, like so.


task :csc => [:vsvars] do
  sh "csc test.cs"
end

Building with msbuild

This is actually pretty easy once we have the environment set up correctly. Just create a task that calls msbuild from a sh call.


namespace :build do
  desc "Build the core project"
  task :core => [:vsvars] do
    sh "msbuild #{core_solution_path}"
  end
end

Controlling Cassini (or webdev.webserver)

There is a lot going on here, so let me first overwhelm you with the code, and then explain what it is doing.


def wait_until_site_loaded
  puts "Please be patient. Waiting for site to respond...."
  site_loaded = false
  until site_loaded
    site_loaded = system
      "curl -L -I -f http://localhost:2088/Default.aspx > NUL 2>&1"
  end
  puts "done."
end

namespace :web do
  desc "Start the local web server"
  task :start => [:vsvars] do
    Thread.new do
      sh "webdev.webserver /path:#{web_root_path} /port:2088 /vpath:/"
    end    

    wait_until_site_loaded
  end

  desc "Stop the local web server"
  task :stop => [:vsvars] do
    `taskkill /im webdev.webserver.exe > NUL 2>&1`
  end

  desc "Restart the local web server"
  task :restart => [:vsvars, :stop, :start]
end

Before the code block above will work, you will need to create a web_root_path variable that points to the absolute path of your website. Relative paths will not work.

The web:start task will start the web server. If an instance is already running at the specified port, then you will get an error message in the form of a dialog box. I wish I knew how to make it fail silently. (I also wish I knew how to prevent it from displaying an annoying balloon notification.)

After starting the web server, the web:start calls out to curl to make sure that the site is responding to get requests. I have curl installed as a result of installing cygwin. There are other ways to get curl, but you will need to make sure your path points at it’s location to use the code above without modifications. curl is pretty chatty, so I have silenced it by routing its standard out and standard error streams to NUL.

The web:stop task will kill all instances of Cassini. This might be annoying if you have more than one instance running. If that is the case, then you will need to write in some form of accounting for the process id of the web server process, or develop a way to figure out which process id owns the port you want. Once you know the specific pid, you can call taskkill /pid and pass it the pid of the process you want to kill.

The web:restart task will call web:stop task followed by web:start task.

One more thing: display available tasks from default task

Note: This recipe does not apply to just Windows development. It will work on any platform.

You can get a list of documented tasks by calling rake -T, but I always forget to do that. I usually just call rake when I want to know what it does. So I created a :default task that displays the same task list that you get when you call rake -T.


task :default do |task|
  puts "You must specifiy a task. Available tasks are listed below:"
  task.application.options.show_task_pattern = /.*/
  task.application.display_tasks_and_comments
end

That’s it. I hope you found these recipes helpful. Happy coding!

[1]: This is way off topic, so I stuck it in a footnote. I know it’s old news, but the way _why, the creator of shoes, committed Internet suicide really irritates me. There were a lot of people benefiting from his contributions to the ruby world, and then one day he just decides to take his toys and go home. He could have bowed out graciously, explaining that he had moved on, but he instead chose identity death. His works remain in archived form, but I am not sure if there is still any energy behind them.

Tags:

Leave a Reply