Creating a gem compatible with Ruby and JRuby
I am working a lot with Apache Kafka (A high-throughput distributed messaging system) these days and for the new versions (0.8.x and 0.9.x) there are no de facto "drivers" for the ruby community. We have been developing a high-level framework for ruby kafka applications (it will be open sourced soon) and like other frameworks, we delegate the connection responsibility to other libraries.
We started the framework relying on poseidon, a kafka client for kafka 0.8, which never fulfilled our expectations regarding stability. The project is inactive and queuing issues, recently the maintainer flagged it as "unmaintained". Hopefully, we have new contenders in this battlefield (zendesk/ruby-kafka I'm looking at you) and we can keep focusing on the scope we decide to tackle.
Foreseeing a "driver" change we decided to make this piece of the framework pluggable and to include an alternative to poseidon. The Java client for kafka is well known and more important, battle tested. We decided to support this driver through JRuby, finally arriving at the subject of this post.
How to make your gem compatible with JRuby?
There are no secrets here, but the devil lies in the details. The first thing is to check which dependencies you have that rely on native code and find compatible versions for JRuby. Let's imagine that we have a gem that uses activerecord with postgresql, the standard driver is the pg gem which contains native code and is incompatible with JRuby.
Fear nothing, my friend. Let's take a look on a possible gemspec for this scenario:
We can use the jdbc postgres adapter as a driver and everything will work as usual, including activerecord. Right now you are probably thinking — "This code will be evaluated at runtime, how will bundler resolve which dependencies it should install?"
And your guess is right, this will work fine with MRI but if you gem install and try to use it with JRuby it will fail. How can we fix it? Well, we need to publish a different version of this gem targeting the platform java we just defined. If you check gems like ffi you will see that they have different targets for every version. ffi, for example, has specific versions for java, x86 and x64.
To generate the different versions, you’ll need to call rake build from every supported platform, in this case, MRI and JRuby. This will force you to use something like rvm in development, to be able to switch to different ruby interpreters, or use a prepared environment for the package and release process. We included this as a new step in our CI pipeline instead of including rake tasks or shell scripts dependents on rvm or rbenv. There isn’t a single solution here, use your creativity and adapt it to your reality.
Sometimes it's good to manage dependencies with bundler instead of gemspec; when working with different platforms, it's possible to configure bundler to install certain dependencies on one platform but not in the other. To achieve that we can use the platform block directly in the Gemfile.
JRuby is a really powerful interpreter and it's evolving, with simple practices you can make your gem available to more developers and use a lot of mature libraries and solutions from the Java world, adding your ruby style and awesomeness.