Tuesday, June 12, 2012

Connecting Java Projects in Gradle

The problem: multiple Java projects, each with their own Gradle build, dependent on each other but not linked.
The goal: connect them into one great build to rule them all.
The caveat: without messing with the individual builds. It is 3 days before go-live at this startup.

The solution:
1) get all of the projects under one subdirectory. They don't have to be immediate subdirectories, just somewhere under one directory to rule them all. Note that soft links won't work, because the subprojects need to see up to the rule-them-all.
2) in that directory to rule them all, create settings.gradle to list the projects
import 'picture', 'tools:hammer', 'tools:hanger'
Note the colon delimiter for subdirectories.

3) in the directory to rule them all, create build.gradle to set up the dependencies.

project(':picture') {
  dependencies {
    compile project(':tools:hammer'), project(':tools:hanger')

this is all that's needed to define the dependencies... except that it doesn't quite work. The error is:

> No signature of method: org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.compile() is applicable for argument types: (org.gradle.api.internal.project.DefaultProject_Decorated, org.gradle.api.internal.project.DefaultProject_Decorated) values: [project ':tools:hammer', project ':tools:hanger']

Before that 'compile' concept exists, we have to apply the Java plugin to each subdirectory. Even though each local build.gradle applies the plugin, we need it earlier than that, at the top level.

4) Put this at the top of the new build.gradle file:
subprojects {
  apply plugin: 'java'

Once the dependencies are defined here, builds of individual projects will trigger builds of their dependencies. The output of each dependency will automatically be added to the classpath of the dependent project. It's pretty sweet: whether you perform the build from the rule-them-all directory or from the subproject you're currently working on, Gradle looks upward in the directory structure, learn about dependencies, and build other subprojects as necessary.

On the path from the cryptic error message, I consulted the one true documentation (the source code).
The example in the documentation ran, but not my build. My build gave that "No signature of method" error. To learn what was going on inside the dependencies block, I printed info about the delegate objects (see this post); the delegate objects were the same. No compile method existed on either of them. Where to look next? How is it finding the compile method in the example that runs?
This is where just a little knowledge of Ruby comes in. I spotted a method in the delegate object called methodMissing. In Ruby, creating this method lets the object decide what to do with any method call not already defined. It turns out that Groovy works the same way. This was the right place to put the debug point in IntelliJ IDEA.
Comparing the behavior of methodMissing showed that the compile configuration existed in the example but not in my build. That led to "oh, maybe we need to apply the Java plugin first."

The multiproject build is working beautifully now, with 0 modifications to the subprojects. Happy birthday, Gradle 1.0!

No comments:

Post a Comment