The Rakefile Chicken and Egg Situation

As part of the jruby-gradle suite, I’ve been on and off on a plugin whereby the tasks of a Rakefile could be reflected back into Gradle in a similar fashion to Ant. This is working for trivial Rakefiles, but is when the Rakefile itself depends upon additional GEMs that it gets to be tricky.

Current implementation:

buildscript {
  repositories {
    mavenLocal()
    jcenter()
  }

  dependencies {
    classpath 'com.github.jruby-gradle:rake:0.1.0-SNAPSHOT'
  }
}

apply plugin: 'com.github.jruby-gradle.rake'

// Loads src/ruby/Rakefile
rake.loadfile()

When rake.loadfile() is called, it instantiates a Ruby runtime object using whatever jruby-complete.jar happends to be on the classpath. Usually this will be the one that happens to be listed in the plugin’s transitive dependencies and not the one defined in the jruby.defaultVersion in the build script. (Not an ideal situation. but more about that later).

The Ruby instance is used to load the Rakefile and via some means we then end up with a colleciton of proxy tasks that is visible in Gradle, but when executed will invoke a call on the Ruby object. The Ruby object is in-process.

When we run gradle tasks --all all of the tasks from the Rakefile will be show prefixed by rake. i.e. rakeDoc.

Problems:

This works until the Rakefile requires some GEMs. This cannot be resolved by the usual jrubyPrepare (0.4+) or jrubyPrepareGems (<0.4) tasks, as the tasks are added during the configuration phase, and the prepare task can obviously only be called during execution phase.

Mantra:

Firstly it is important to remember that whatever the solution, the build script author should be able to write a clean script and not having to jump through many hoops.

This now leads to some potential solutions, each with some potenital problems too and this is here where I can really do with some guidance and feedback.

Potential solution #1:

Add GEMs to the buildscript.dependencies.classpath configuration.

buildscript {
  repositories {
    mavenLocal()
    jcenter()
   // Need to add this so that we can find some GEMs
   maven { url 'http://rubygems.lasagna.io/proxy/maven/releases' }
  }

  dependencies {
    classpath 'com.github.jruby-gradle:rake:0.1.0-SNAPSHOT'
    classpath 'rubygems:hoe:3.13.1'
  }
}

Now the script author has to add the the extra repo too. We also have to add some code in rake.loadfile() to look for .gem files in the buildscript.dependencies.classpath during the configuration phase and unpack them. The latter is possible as the appropriate methods will already be on the classpath.

Problems with solution #1:

  • We still have not control over which version of jruby-complete will be used.
  • Would have like to avoid adding the extra repo in the buildscript closure.
  • The buildscript author needs to be aware which GEMs are required by the Rakefile itself.

Potential solution #2:

With this approach it is now possible to use the version of jruby specified in jruby.defaultVersion.

We delay evaluation of the tasks in the Rakefile by changing rake.loadfile() to add a rule for tasks beginning with rake and add tasks only when requested.

The creation of the Ruby instance needs to be delayed until evaluation is finished or to just before execution starts.

Problems with solution #2:

  • Can no longer easily reflect tasks from Rake into Gradle.
  • Cannot guarantee that Rakefile tasks exist until actually trying to execute the task.
  • Loose ability to reflect dependencies between Rake tasks into Gradle as dependencies between Gradle tasks.
  • Will need to hook into the TaskReportTask to display tasks from Rakefile. This also means that somehow all the tasks from Rakefile needs to be loaded when gradle tasks is run.
  • If project.afterEvaluate is used to instantiate Ruby, we have no control over the order when this will happen.

That is as much as I can think of now. Some suggestions would definitely be appreciated.