Grails Build Dependency Scopes

Grails provides a convenient mechanism for defining and managing dependencies using BuildConfig.groovy.  The Grails documentation describes each of the dependency scopes as follows:

  • build: Dependencies for the build system only
  • compile: Dependencies for the compile step
  • runtime: Dependencies needed at runtime but not for compilation (see above)
  • test: Dependencies needed for testing but not at runtime (see above)
  • provided: Dependencies needed at development time, but not during WAR deployment

These descriptions are helpful but there have been enough questions on the mailing list and stackoverflow that it would be nice to have a more concrete explanation of how each of these scopes behaves.

Let’s say that we have the following code which introduces a dependency on the Apache POI framework.

import org.apache.poi.xssf.usermodel.XSSFSheet
 
class ExampleController {
 
    def index() {
        XSSFSheet sh = new XSSFSheet()
        render "success: ${sh.class}"
    }
}

In this case, the dependency will need to be available at compile time, runtime and, assuming we write some tests for this piece of code, at test time.  So, which scope or scopes do we need?  If you’re like me, this may not be immediately obvious after reading the documentation.

First, do not use multiple entries in BuildConfig for the same dependency.  Even though you will be able to start your application fine with the following dependencies, Grails will only pay attention to the last entry.  So if we tried to do something like this, we would end up with only a build scoped dependency:

dependencies {
  test 'org.apache.poi:poi-ooxml:3.7'
  runtime 'org.apache.poi:poi-ooxml:3.7'
  compile 'org.apache.poi:poi-ooxml:3.7'
  build 'org.apache.poi:poi-ooxml:3.7'  // the previous entries are ignored
}

The following matrix shows if the dependency was included (indicated by a value of Y) or not included (indicated by a value of N) for each grails command (run-app, test-app, etc.)

compile run-app test-app war
build Y Y Y N (java.lang.NoClassDefFoundError)
compile Y Y Y Y
runtime Y Y Y Y
test Y Y Y N (java.lang.NoClassDefFoundError)
provided Y Y Y N (java.lang.NoClassDefFoundError)

The most interesting observations from these experiments are that:

  • Dependencies with a scope of build, test, and provided seem to be equivalent
  • Dependencies with a scope of compile and runtime seem to be equivalent

Of course, it’s possible my tests were flawed or that I have ignored some other aspect in which these scopes are different.  As far as how these tests were conducted, I exited the console and ran ‘grails clean’ between each step.  At various points I tried clearing my local ivy cache and grails projects directory to ensure I wasn’t picking up left over dependencies from previous experiments.  I also tried removing the dependency entirely at various points to make sure that the steps were failing due to a ClassNotFoundError.

As far as I can tell the results above are accurate, which makes me wonder what aspects of the dependency I might be failing to think about.  If there are none, it makes me wonder why there are five scopes instead of just two.  Any insights into either of these topics would be very welcome.

 

About Chris

Chris Latimer is an IT professional and Grails enthusiast.
This entry was posted in grails and tagged , , , , , . Bookmark the permalink.