Lately I have been going on about how to write Ruby C Extensions and I thought at this point I should share with you how to wire my favourite Gem manager Jeweler and the famous rake-compiler together to make Ruby C extension development easier.
Jeweler Setup
You will need to configure Jeweler for four things:
- Setting up the extensions flag of the gemspec
- Including the generated native files
- Storage of the generated gemspec
- Fixing the gemspec version
Let's look at each in turn:
The Extensions Flag
Jeweler::Tasks.new do |gem|
...
gem.extensions = FileList['ext/**/extconf.rb']
end
This will enable you to package a gem for all the platforms that you don't provide a native binary for. This gem will perform the well known
Building native extensions. This could take a while...
task on installation.
Including the Binaries
Jeweler::Tasks.new do |gem|
...
gem.files.include('lib/cranberry.*')
end
This will include all generated canberry.so
or cranberry.dll
and so on in your generated
gem.
Storing the GemSpec
Since rake-compiler needs the gemspec on initialization, we need to store it:
jeweler_tasks = Jeweler::Tasks.new do |gem|
...
end
$gemspec = jeweler_tasks.gemspec
This also means that rake-compiler tasks must be set up after the Jeweler tasks.
Fixing the GemSpec Version
(UPDATE: This is a simpler solution. The one posted previously works as well, but this is less invasive)
$gemspec.version = jeweler_tasks.jeweler.version
This fixes an issue in the interaction of rake-compiler
and Jeweler. Rake-compiler needs the gemspec to have a
version, but Jeweler will only store the current version
in the gemspec if certain tasks are called, e.g.
gemspec
.
If not applied, native building will fail with
Version required (or :noversion)
Rake-Compiler Setup
The rake-compiler itself doesn't need any special configuration, except for passing in the gemspec:
Rake::ExtensionTask.new('craberry', $gemspec) do
...
end
But there are some bugs when wiring it to Jeweler.
Gemspec Marshalling Fix
Rake::Task.tasks.each do |task_name|
case task_name.to_s
when /^native/
task_name.prerequisites.unshift("fix_rake_compiler_gemspec_dump")
end
end
task :fix_rake_compiler_gemspec_dump do
%w{files extra_rdoc_files test_files}.each do |accessor|
$gemspec.send(accessor).instance_eval {
@exclude_procs = Array.new
}
end
end
This forces Rake to always execute
fix_rake_compiler_gemspec_dump
before
invoking any native
tasks.
Rake-compiler at one point needs to draw a copy of
the gemspec. But since gemspecs don't have a dup
method, it resorts to YAML-dump-loading the whole
thing.
But when using Jeweler, the gemspec will have some Procs attached (to the FileLists), which can't be YAML-loaded (there's no dumping of Ruby code), so the gemspec cloning will fail with
allocator undefined for Proc
The above code snippet prevents that by removing those Procs before the YAML dumping.
The Rest
For the rest, you can
follow the rake-compiler tutorial
except that with Jeweler the gem
task is
called build
.
Hope this helps some folks out there!