Rake Assets Precompile with JRuby Complete

Article summary

In my previous post, “Using Bundler with JRuby Complete”:https:/bundler-jruby-complete/, I described how to configure a Ruby environment using the JRuby Complete jar. While using those techniques, I ran into an issue in the development of a Rails 3.2 application when trying to precompile assets. I came up with a quick workaround which I will share here.

In case you haven’t read the post mentioned above, the important thing to know here is that @rake@ is actually something like:

PATH=bin:$PATH \
GEM_HOME=vendor/gem_home \
GEM_PATH=vendor/gem_home \
java -jar vendor/jruby-complete-1.7.0.jar -S rake

The Problem

For this particular application, I wanted to precompile the assets on the CI/build server as opposed to during deployment as is the normal case for a Rails app. If I tried to run the @assets:precompile@ rake task, I would get the following error:

file:/projects/rails_app/vendor/jruby-complete-1.7.0.jar!/META-INF/jruby.home/bin/jruby bin/rake assets:precompile:all RAILS_ENV=production RAILS_GROUPS=assets
rake aborted!
Command failed with status (127): [file:/projects/rails_ap...]
org/jruby/RubyProc.java:249:in `call'
/projects/rails_app/vendor/bundler/jruby/1.9/gems/actionpack-3.2.13/lib/sprockets/assets.rake:12:in `ruby_rake_task'
/projects/rails_app/vendor/bundler/jruby/1.9/gems/actionpack-3.2.13/lib/sprockets/assets.rake:21:in `invoke_or_reboot_rake_task'
/projects/rails_app/vendor/bundler/jruby/1.9/gems/actionpack-3.2.13/lib/sprockets/assets.rake:29:in `(root)'
org/jruby/RubyProc.java:249:in `call'
org/jruby/RubyArray.java:1612:in `each'
org/jruby/RubyArray.java:1612:in `each'
org/jruby/RubyKernel.java:1045:in `load'
Tasks: TOP => assets:precompile
(See full trace by running task with --trace)

It appears to be trying to shell out to a @jruby@ script _inside_ the @jruby-complete.jar@. The shell does not know how to interpret the path to a file inside of a jar, which is the cause of the error. Examining the @assets.rake@ file from the stack trace shows where this is happening:

def ruby_rake_task(task, fork = true)
  env    = ENV['RAILS_ENV'] || 'production'
  groups = ENV['RAILS_GROUPS'] || 'assets'
  args   = [$0, task,"RAILS_ENV=#{env}","RAILS_GROUPS=#{groups}"]
  args << "--trace" if Rake.application.options.trace
  if $0 =~ /rake\.bat\Z/i
    Kernel.exec $0, *args
  else  
    fork ? ruby(*args) : Kernel.exec(FileUtils::RUBY, *args)
  end    
end

You can see in line 9 the use of a @FileUtils::RUBY@ constant. When running a Rails app with the jruby-complete, jar @Kernel.exec@ is called, and the constant ends up being something like:

file:/projects/rails_app/vendor/ \
 jruby-complete-1.7.0.jar!/META-INF/jruby.home/bin/jruby

h2. The Work-Around

Fortunately there is a very simple work-around. First, I created a @script/jruby@ script that encapsulated the way I am running jruby:

#!/bin/sh
export PATH=bin:$PATH
export GEM_HOME=vendor/gem_home
export GEM_PATH=vendor/gem_home
cmd="java -jar vendor/jruby-complete-1.7.0.jar $@"
exec $cmd

Then I added a @file_utils_ruby.rake@ file to the @lib/tasks@ directory containing only:

FileUtils::RUBY = "script/jruby"

When Rails loads this file, the constant will point to my jruby script, instead of to something inside the jruby-complete jar. I had assumed I would get a warning about reassigning a constant, but no such warnings seem to be issued. And with this in place, the @assets:precompile@ task executes cleanly.

In addition, the alias for @rake@ can now be changed from what was listed at the beginning of this post to just be something like:

script/jruby -S rake