danlucraft /blog

Plugin Manager

January 2010 • Daniel Lucraft

Everything in Redcar is a plugin, so it’s important that the plugin loader be well put together.

Redcar has been through 3 plugin loaders now:

  1. A hacked together thing I wrote in 2007, when I barely knew how to write Ruby.
  2. The FreeBASE plugin loader, which is the same one that the FreeRIDE Ruby IDE uses.
  3. The plugin_manager gem, which I wrote this weekend and replaces FreeBASE.

FreeBASE was a good choice back in 2008 when Redcar switched to it. It’s stable, full featured, and designed for use in an editor.

However it has some downsides that have led to us abandoning it:

  • It is a little too full featured. There’s a lot of code in there, adding new features is not so easy. There are whole systems, like a databus, that we just don’t need.
  • It’s 2003-style Ruby. Not what we would call idiomatic Ruby for 2010. This makes it a little bit unpleasant to go in there.
  • It has no tests, making maintenance a bit scary.
  • The plugin metadata is declared in somewhat unsightly .yaml files.
  • The way it locates the plugins to load is a common source of problems for users.

So this weekend I wrote a replacement: plugin_manager. plugin_manager is fully tested, lean code. It supports:

  • loading plugins from multiple sources,
  • plugin dependencies,
  • complex versioning (>=3.1, !=2.0.2 etc.),
  • a clean Ruby DSL for declaring plugins,
  • plugins with broken declaration files (because your users will always write plugins that break things),
  • plugins with broken code (ditto),
  • plugin code reloading,
  • callbacks onto a named object when the plugin is loaded.

And best of all it’s one-tenth the code size of FreeBASE, while still doing everything we need it to.

It’s not tied to Redcar or to Rubygems, all it assumes is that you have a collection of directories of Ruby code (or a few collections) that you want to load respecting dependencies and load order.

To register a plugin, drop a plugin.rb file into each directory you want to turn into a plugin:

Plugin.define do
  name         "Extras"
  version      "1.0"

  # the file to load to load the plugin. It is expected to be an .rb
  # file relative to this definition
  file         "extras"

  # this is an object that is defined by the plugin code
  object       "App::Extras"

  # Dependencies of the plugin
  dependencies "core", ">=1.0",
               "fonts", ">=0.5, <1.9",
               "debug", ">0, !=0.95, < 2"
end

Once you have declared all your plugins like this, you can load them like this:

manager = PluginManager.new
manager.add_plugin_source("plugins/")
manager.load

The code in the appropriate plugins will be loaded and you will then have available:

# plugins that were loaded successfully
manager.loaded_plugins

# plugins that could not be loaded because of unmet dependencies, 
# or because a more recent version was available.
manager.unloaded_plugins

# plugin.rb files that could not be read
manager.unreadable_definitions

# plugins that raised exceptions while being loaded
manager.plugins_with_errors

The code is up on github, and it is available as a gem if anyone finds it useful. (Obviously someone does because it has already been downloaded 5 times!)

blog comments powered by Disqus