Grails plugin dependencies

If you need a jar library in a Grails plugin, the best approach is to add a dependency in BuildConfig.groovy from a Maven repository so you (and your users) only download it once and use it for each project that needs it. This works well as long as the jar is in Maven Central or some other repo, but some smaller projects don’t publish to Maven repos, so sometimes you are stuck with jar files in the lib directory. Using dependency management is convenient, especially since you can override the version in the application later if a newer jar version is released.

The standard syntax is simple, for example

dependencies {
   compile 'com.atomikos:transactions-jms:3.8.0'

This is great, but due to the way Maven works this can result in far more than just that one jar file since all of its dependencies and transitive dependencies will also be downloaded and added to the project’s classpath. That’s what you want most of the time, but often I’ve found that there are unneccessary depdendencies, or dependencies that are already provided by Grails or another plugin but with a different version. So I tend to list out all of the dependencies that I actually want to end up with, adding the necessary exclusions to the dependency declarations. For example here are the two that the spring-security-core plugin defines:

compile('') {
   excludes 'spring-expression', 'spring-core', 'spring-context', 'spring-tx',
            'spring-aop', 'spring-jdbc', 'spring-web', 'spring-test', 'aspectjrt',
            'aspectjweaver', 'cglib-nodep', 'ehcache', 'commons-collections',
            'hsqldb', 'jsr250-api', 'log4j', 'junit', 'mockito-core', 'jmock-junit4'

compile('') {
   excludes 'spring-security-core', 'spring-web', 'spring-jdbc', 'spring-test',
            'commons-codec', 'hsqldb', 'servlet-api', 'junit', 'mockito-core', 'jmock-junit4'

I could have cheated and just used transitive = false:

compile('') {
   transitive = false

compile('') {
   transitive = false

but this is really only appropriate for applications; in plugins it’s better to avoid this so the pom files are correct.

This usually isn’t that hard to do; it’s just a matter of reading pom.xml files and copy/pasting the relevant bits. But it can be tedious and I realized recently that this was an excellent use case for a Groovy script.

If you’ve installed the release plugin (it should be there since it’s added to the plugin’s BuildConfig.groovy by default) then you can run the generate-pom and it will generate target/pom.xml.

The process that I use is iterative. The first step is to use the transitive = false attribute that I said not to use earlier. This will generate a pom.xml with all of the dependencies and transitive dependencies enumerated for you for each of your specified dependencies. Then run this script in a Grails or Groovy console and it will build the proper syntax for each declaration:

String xml = new File('/path/to/pom.xml').text
for (dependency in new XmlSlurper().parseText(xml).dependencies.dependency) {
   String scope = dependency.scope ?: 'compile'
   Set exclusions = []
   for (exclusion in dependency.exclusions.exclusion) {
      String groupId = exclusion.groupId
      String artifactId = exclusion.artifactId

      if ( (groupId == 'xml-apis' && artifactId == 'xml-apis') ||
           (groupId == 'xml-apis' && artifactId == 'xmlParserAPIs') ||
           (groupId == 'xerces' && artifactId == 'xmlParserAPIs') ) {

      exclusions << "'$artifactId'"

   String combined = dependency.groupId.text() + ':' +
       dependency.artifactId.text() + ':' +
   if (exclusions) {
      println "\t$scope('$combined') {"
      println "\t\texcludes ${exclusions.sort().join(', ')}"
      println "\t}\n"
   else {
      println "\t$scope '$combined'\n"

Replace transitive = false with the output from the script. If this is sufficient then you’re done, but if you’ve excluded a jar file that you will need, then add it as an explicit dependency with transitive = false and run the script again. Repeat the process until all of the important jar files are listed, and all of their dependencies are properly excluded. Keep in mind that you don’t have to list everything, just what you need to get your plugin working. If a user needs another related jar that you excluded, it can always be added to the application’s BuildConfig.groovy.

For a more extensive example, see the db-reverse-engineer plugin’s BuildConfig.groovy. I could have just defined a dependency for the Hibernate Tools jar, but to do it correctly I ended up with six dependency declarations.

Comments are closed.

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.