Wednesday, June 01, 2011

Extending JRuby, Compile and Jar Java Extension Code

I wrote about how to extend JRuby by Java in my blog post, Extending JRuby. At that time, I didn't compile Java code since Eclipse performed that automatically. To jar Java code, I used jar command and made an archive manually. However, this is definitely not nice. In general, Java people use maven or ant to package Java code into a single jar. Although maven and ant are among choices to package JRuby extension code, there is a more Ruby-like way of packaging. It is rake-compiler. Nokogiri uses rake-compiler for both CRuby and pure Java versions to compile and package.


Rake-compiler might be known as a cross-compiling tool, but it is also a compiling tool for Java code. Not like maven and ant, we don't write XML files. Instead, we write Rakefile. This means we compile and package Java code by a rake task on JRuby.


Let's get started. First, you need to install rake-compiler gem to your *JRuby* since it is Java code compilation. Make sure you are using JRuby.
jruby -S rake gem install rake-compiler

Next, make sure your Java code is under an *ext* directory. Rake-compiler assumes extension codes are under the *ext* directory in terms of Convention over Configuration. My Eclipse project created a *src* directory for Java sources, so I refactored the name from src to ext on Eclipse. Then, my Rakefile became as in below:
# -*- ruby -*-

require 'rubygems'
require 'rake/javaextensiontask'

Rake::JavaExtensionTask.new('commons/math/fraction') do |ext|
jruby_home = ENV['MY_RUBY_HOME'] # this is available of rvm
jars = ["#{jruby_home}/lib/jruby.jar"] + FileList['lib/*.jar']
ext.classpath = jars.map {|x| File.expand_path x}.join ':'
ext.name = 'commons/math/poplar'
end

To compile Java code for JRuby extension, requiring 'rake/javaextensiontask' and writing Rake::JavaExtensionTask are all you need. The question would be what should be in JavaExtensionTask. The constructor argument specifies the directory Java code resides. The jruby_home is to know where jruby.jar is. jars is just an array to set ext.classpath effectively. You may assign whole classpath directly to ext.classpath variable. If you are on Windows, you need to change classpath delimiter from ':' to ';' I also specified ext.name parameter. Without this, JavaExtensionTask creates an jar archive, "lib/commons/math/fraction.jar" from the constructor arguments. In my case, this name is confusing since I have lib/commons/math/fraction.rb, too. Once JRuby finds fraction.rb, JRuby happily quits searching loop. So, lib/commons/math/fraction.jar won't be loaded. You can also specify other parameters like Ruby exntesion, for example,
  • ext.gem_spec
  • ext.tmp_dir
  • ext.lib_dir
  • ext.platform
  • ext.config_options
  • ext.source_pattern
For Java extension, we can set
  • source_version
  • target_version
The default values of these parameters are 1.5.

Everything is ready. Let's compile and jar Java extension code. On your terminal, type
rake compile

Just this compiles and jars Java extension code. The created jar archive is lib/commons/math/poplar.jar. This directory is natural for JRuby extension gems. You can see other rake tasks from rake -T. On your terminal, rake clean, rake clobber and two compile tasks will show up. Next time, you might type
rake clean
rake compile


Like above, rake-compiler works to compile and jar JRuby Java extension code. This might be more familiar and better to J/Rubyists.


All of my code are on GitHub, https://github.com/yokolet/Poplar