Writing Extensions using Hoe
Published 2008-08-07 @ 12:59
Tagged hoe, ruby, rubyinline
Despite popular belief, it is possible and easy to write extensions with projects based on Hoe. It is possible to do both classic and inline extensions. Let’s take a look at what both of them look like.
Classic Extensions
This is a standard extconf.rb
based extension, with the build mechanism folded into the Rakefile
instead of also having a Makefile
in the ext
directory.
Rakefile
EXT = "ext/blah.#{Hoe::DLEXT}" # 1
class Blah
VERSION = '1.0.0' # 2
end
Hoe.new('blah', Blah::VERSION) do |p|
p.developer('FIX', 'FIX@example.com')
p.spec_extras[:extensions] = "ext/extconf.rb" # 3
p.clean_globs << EXT << "ext/*.o" << "ext/Makefile"
end
task :test => EXT
file EXT => ["ext/extconf.rb", "ext/blah.c"] do # 4
Dir.chdir "ext" do
ruby "extconf.rb"
sh "make"
end
end
1) EXT
defines the path to the built extension, used below for dependencies and the clean_globs
array.
2) Normal Hoe-based projects require the library in the Rakefile
in order to grab the VERSION
constant. This means you only have to update the version in a single location, but can use that value for packaging and deploying. We can’t do that here (since the extension isn’t necessarily built yet), so we cheat by duplicating the version constant. Violates DRY, but it is not that big of a deal.
3) The hoe spec also uses spec_extras
to define :extensions
to specify that we’ve got an extconf.rb
file to use.
4) Finally, and most importantly, we have a file rule for EXT
that executes extconf.rb
to generate a Makefile and then executes make to build the extension binary.
ext/ext/extconf.rb
require 'mkmf'
create_makefile("blah")
Nothing special here to explain.
ext/ext/blah.c
#include "ruby.h"
static VALUE blah(VALUE self) {
puts("hello world");
return Qnil;
}
#ifdef __cplusplus
extern "C" {
#endif
void Init_blah() {
VALUE c = rb_define_class("Blah", rb_cObject);
rb_define_method(c, "blah", (VALUE(*)(ANYARGS))blah, 0);
}
#ifdef __cplusplus
}
#endif
Here is your basic C extension for ruby. We’ve got a function blah
and an Init_blah
function that declares the Blah
class with a blah
method and binds it to the blah
C function.
Remember, this is as easy as it gets. blah
doesn’t take any arguments and it doesn’t actually do anything.
Inline Extensions
Inline makes your life a lot easier than the above.
Rakefile
require './lib/blah.rb'
Hoe.new('blah', Blah::VERSION) do |p|
p.developer('FIX', 'FIX@example.com')
end
Every mechanism above is replaced with a simple Hoe spec. It gets its VERSION
from lib/blah.rb
as usual because everything is just happy ruby and there are no bootstrap steps. There are no extra dependencies or anything to take care of because inline does everything for you.
lib/blah.rb
require 'rubygems'
require 'inline'
class Blah
VERSION = '1.0.0'
inline(:C) do |builder|
builder.c %{
void blah() {
puts("hello world");
}
}
end
end
Here we have a regular ruby class Blah
with a VERSION
constant defined. The only thing special is the call to inline
with :C
as the specifier of what type of builder we want. That builder is then told to define a C function with a C function signature of void blah()
. Our simple hello world is inside and pretty much the same as above. The main difference is that there is no return value, that’s because Inline knows that a C function returning void
should return nil
automatically. Inline deals with all arguments and return values automatically.
Another difference with the classic extension example, is I’m not forced to always run rake
before I can play. I can run a test file directly, and if the C source has changed, inline deals with it automatically behind the scenes.
Conclusion
For everything I do, there is little reason to use anything but inline. There are times where you need to write a classic extension. Luckily, it isn’t much work to make it work in a classic Rake
/Hoe
setup.