Showing posts with label extension. Show all posts
Showing posts with label extension. Show all posts

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

Sunday, May 15, 2011

Rubinius on JRuby … ?

Of course, compiled Rubinius binary, *.rbc, file doesn’t work on JRuby. This post is about JRuby’s rubinius branch. I’m not sure how may people are aware that, but JRuby does have a rubinius branch. As far as I looked at that branch, it is not to be merged into master, at least, in near future. Maybe it is headius’ pet project at this moment, and is implemented as a JRuby extension. Yes, it is a JRuby extension. The branch attempts “extending JRuby,” like I wrote about in my blog post (http://yokolet.blogspot.com/2011/05/extending-jruby.html). Since this extension is still small, it would be a good practice to figure out how extension works. So, I’m going to write how you can decipher existing JRuby extension. Hopefully, this will help you to write your own extension.


To try the rubinius branch, you have two preparations to be done. The first one is to build JRuby of the rubinius branch. It is easy. Just clone out JRuby, checkout rubinius branch, and run ant command as in below:

git clone git://github.com/jruby/jruby.git
cd jruby
git checkout rubinius

ant clean-all
ant

Next, you need Rubinius source. You don’t need to build rubinius just to see how it works. But, the rubinius extension uses Rubinius’ kernal sources, which is included only in the source archive. That’s why you need the source. Rubinius source archive is available to download from http://rubini.us/. Get the archive and unzip it.


Up until now, you had rubinius branch JRuby and Rubinius source. Set the environment variables. Suppose the JRuby’s home directory is /Users/yoko/Projects/jruby, then, set JRUBY_HOME and PATH like in below. Adjust them to fit in to your system:

export JRUBY_HOME=/Users/yoko/Projects/jruby
PATH=$JRUBY_HOME/bin:$JRUBY

Suppose, Rubinius source are in /Users/yoko/Projects/rubinius-1.2.2, then set RBX_KERNEL as in below:

export RBX_KERNEL=/Users/yoko/Projects/rubinius-1.2.2/kernel



Everything should be ready. Let’s try this out.

bash-3.2 rubinius$ jruby -S irb
irb(main):001:0> require 'rubinius'
=> true

Yay! Rubinus was successfully loaded on JRuby. What’s next? Look at the RubiniusLibrary.java (https://github.com/jruby/jruby/blob/rubinius/src/org/jruby/ext/rubinius/RubiniusLibrary.java). At the line 51, “Rubinius” module is defined.

RubyModule rubinius = runtime.getOrCreateModule("Rubinius");

So, there should be a constant, Rubinius.

irb(main):002:0> Rubinius
=> Rubinius
irb(main):003:0> Rubinius.class
=> Module

So far, so good. Then, at the line 56, you can see

RubyTuple.createTupleClass(runtime);

This means you should go to RubyTuple.java (https://github.com/jruby/jruby/blob/rubinius/src/org/jruby/ext/rubinius/RubyTuple.java). On the line 55-62 of RubyTuple.java, createTupleClass method is defined. In this method, "Tuple" class is defined under the "Rubinius" module. Then, annotated methods, which have Java annotation @JRubyMethod, are defined. Looking at the rest of the code in RubyTuple.java, you can see three annotated methods (new, [], and []=), and one override method (dup) are there. Let’s try these.

irb(main):013:0> Rubinius::Tuple
=> Rubinius::Tuple
irb(main):014:0> tuple = Rubinius::Tuple.new 3
=> #<Rubinius::Tuple:0x3df89785>

This constructor needs one argument because rbNew method is defined as:

public static IRubyObject rbNew(ThreadContext context, IRubyObject tupleCls, IRubyObject cnt) {

Here's the rule. First two arguments are given internally, and the rest of the arguments are given from users. So, I typed “3” as an argument. Let’s keep going on.

irb(main):018:0> tuple[0]
=> nil
irb(main):019:0> tuple[0]=123
=> 123
irb(main):020:0> tuple[0]
=> 123
irb(main):021:0> tuple_dup = tuple.dup
=> #
irb(main):022:0> tuple_dup[0]
=> 123

All right, methods worked.


Next, get back to RubiniusLibrary.java and let’s look at the lines 84-88.

runtime.getObject().deleteConstant("Hash");
runtime.getLoadService().lockAndRequire(rbxHome + "/common/hash.rb");
RubyClass hash = (RubyClass)runtime.getClass("Hash");
hash.defineAnnotatedMethods(RubiniusHash.class);
runtime.setHash(hash);

Soooo interesting! Could you figure out what’s going on here? Hash is redefined using Rubinius code! JRuby’s Hash is entirely written in Java (https://github.com/jruby/jruby/blob/rubinius/src/org/jruby/RubyHash.java). But, once “require rubinius” is done, the Hash is totally replaced by Rubinius’ Hash, which IS written in Ruby. See? JRuby can be extended also in Ruby, not just Java. To do double check this, let’s add one line in initialize method of kernel/common/hash.rb:

def initialize(key, key_hash, value)
@key = key
@key_hash = key_hash
@value = value
@next = nil
puts "Rubinius Hash!!" # this line is added
end

Then, restart irb and re-request rubinius.

bash-3.2 rubinius$ jruby -S irb
irb(main):001:0> require 'rubinius'
=> true
irb(main):002:0> h = Hash.new
Rubinius Hash!!
=> {}

Yay, Hash is really Rubinius’ Hash. What an idea!


As I wrote, you have a lot of options for “extending JRuby.” You can extend using mature Java APIs and, also, cutting edge Ruby code. Why don’t you try this fantastic extension? It should be fun.