JRuby 1.1.6: Gems-in-a-jar
Posted by Nick Sieger Sat, 10 Jan 2009 20:04:00 GMT
As a result of some fruitful hacking at RubyConf 2008, I was able to modify JRuby so that gems can be loaded and used without having to unpack them. The feature became generally available with the 1.1.6 release last month. Gems in a jar!
This opens up a couple new possibilities for running, packaging and deploying JRuby-based applications. Here are some ideas:
Run Gem-based applications with jruby-complete.jar
JRuby has bundled Rake and RSpec since version 1.0. As of JRuby 1.1.6 the versions we bundle are Rake 0.8.3 and RSpec 1.1.11. With jruby-complete-1.1.6.jar
you can easily run these with java -jar
:
$ java -jar jruby-complete-1.1.6.jar -S rake --help
rake [-f rakefile] {options} targets...
Options are ...
$ java -jar jruby-complete-1.1.6.jar -S spec --help
Usage: spec (FILE|DIRECTORY|GLOB)+ [options]
-p, --pattern [PATTERN] ...
Package gem collections into reusable jar files
One of the features I added was to enhance RubyGems to search for directories named specifications
on the classpath and add them to the Gem.path
automatically. This means you can package up a whole gem repository into a jar file for easy reuse and sharing of commonly used gems. There isn’t a tool for this yet, but the process is pretty straightforward. (If someone plays with this and can come up with a patch to build this into JRuby, we’ll gladly accept one.)
First, create a gem repository by installing the gems you want into it. Let’s say you want to package the natural language date/time parser chronic
:
$ java -jar jruby-complete-1.1.6.jar -S gem install -i ./chronic-gems chronic --no-rdoc --no-ri
Successfully installed rubyforge-1.0.2
Successfully installed rake-0.8.3
Successfully installed hoe-1.8.2
Successfully installed chronic-0.2.3
4 gems installed
With this command, RubyGems created chronic-gems/bin
, chronic-gems/cache
, chronic-gems/gems
, and chronic-gems/specifications
directories and installed chronic
and its dependencies into it. Now, simply package those directories into a jar file. WARNING: make sure you don’t give the jar file name the same basename as the library or gem inside you wish to use (e.g., chronic.jar), because JRuby will load chronic.jar when you require 'chronic'
!
$ jar cf chronic-gems.jar -C chronic .
When you inspect the contents of the jar, you’ll see the gem repository structure in the root of the jar file:
$ jar tf chronic-gems.jar | head
META-INF/
META-INF/MANIFEST.MF
bin/
bin/rake
bin/rubyforge
bin/sow
cache/
cache/chronic-0.2.3.gem
cache/hoe-1.8.2.gem
cache/rake-0.8.3.gem
Chronic is now a re-useable jar library that can be easily loaded, either by requiring the jar in ruby code or adding to the classpath:
$ # Without chronic-gems.jar
$ java -jar jruby-complete-1.1.6.jar -S gem list
*** LOCAL GEMS ***
rake (0.8.3)
rspec (1.1.11)
sources (0.0.1)
$ # With chronic-gems.jar
$ java -jar jruby-complete-1.1.6.jar -rchronic-gems.jar -S gem list
*** LOCAL GEMS ***
chronic (0.2.3)
hoe (1.8.2)
rake (0.8.3)
rspec (1.1.11)
rubyforge (1.0.2)
sources (0.0.1)
Bundle pure-Ruby gem applications into an uber-jar
Taking a cue from the previous techniques, we can stuff the gem directories into a copy of jruby-complete-1.1.6.jar
rather than creating a new jar, and distribute an entire gem-based application in a single file. Imagine something like:
$ java -jar jruby-complete-1.1.6.jar -S gem install -i ./mycoolapp mycoolapp
$ jar uf jruby-complete-1.1.6.jar -C mycoolapp .
$ mv jruby-complete-1.1.6.jar mycoolapp.jar
$ java -jar mycoolapp.jar -S mycoolapp
Bonus points to the enterprising individual who provides a patch to make this a one-step process, including creating a mechanism to provide a default -S
script so that java -jar mycoolapp
is all that’s needed to run the application.
I hope you find interesting uses for this new feature. Let us know what you make with it!