"Client module dependencies" don't seem to work

If I create a new build and drop in an example I pulled straight from the Gradle documentation:

dependencies {
  runtime module("org.codehaus.groovy:groovy:2.3.10") {
    dependency("commons-cli:commons-cli:1.0") {
      transitive = false
    }
    module(group: 'org.apache.ant', name: 'ant', version: '1.9.4') {
      dependencies "org.apache.ant:ant-launcher:1.9.4@jar",
                   "org.apache.ant:ant-junit:1.9.4"
    }
  }
}

And then try to run this build, I get:

* What went wrong:
A problem occurred evaluating root project 'subproject1'.
> Could not find method runtime() for arguments [org.gradle.api.internal.artifacts.dependencies.DefaultClientModule_Decorated@a8f1dd96] on root project 'subproject1'.

I wasted all of today trying to figure out this error in the context of our much larger project, thinking that we had done something wrong with how we had refactored the dependencies out. I tried many different ways to use them in the real build and got similar-looking errors for everything I tried. Then I spent a couple of hours making a new build from scratch with just this in it, before finding out that it didn’t work either. Then I finally try pasting one of the examples straight from the docs, and that doesn’t work either. So at this point, I’d say that Gradle is just broken.

In case any of it matters:

------------------------------------------------------------
Gradle 2.8
------------------------------------------------------------

Build time:   2015-10-20 03:46:36 UTC
Build number: none
Revision:     b463d7980c40d44c4657dc80025275b84a29e31f

Groovy:       2.4.4
Ant:          Apache Ant(TM) version 1.9.3 compiled on December 23 2013
JVM:          1.8.0_51 (Oracle Corporation 25.51-b03)
OS:           Mac OS X 10.11.1 x86_64

I also tried updating to Gradle 2.12 just in case (the release notes didn’t mention anything obvious, but what the hell, it’s the first thing support will ask), and get the same error.

------------------------------------------------------------
Gradle 2.12
------------------------------------------------------------

Build time:   2016-03-14 08:32:03 UTC
Build number: none
Revision:     b29fbb64ad6b068cb3f05f7e40dc670472129bc0

Groovy:       2.4.4
Ant:          Apache Ant(TM) version 1.9.3 compiled on December 23 2013
JVM:          1.8.0_51 (Oracle Corporation 25.51-b03)
OS:           Mac OS X 10.11.1 x86_64

Now my new build which is more complete. This does pretty much mirror the way that our build was set up, so if I can get this working then I can get the real build working.

build.gradle:

allprojects {
  apply plugin: 'java'
}

dependencies.gradle:

ext.libraries = [

  jaxen_core: [
    'org.jdom:jaxen-core:1.0-FCS'
  ],

  jaxen_jdom: [
    module('org.jdom:jaxen-jdom:1.0-FCS') {
      runtime jaxen_core
    }
  ],

  jdom: [
    module('org.jdom:jdom:1.0') {
      runtime jaxen_jdom
    }
  ],

]

subproject1/build.gradle:

apply from: '../dependencies.gradle'

dependencies {
  compile libraries.jdom
}

This gives an error too:

* What went wrong:
A problem occurred evaluating script.
> Could not find method module() for arguments [org.jdom:jaxen-jdom:1.0-FCS, dependencies_5he5nx8rm9zd7i6d98itung1c$_run_closure1@47e4d9d0] on root project 'subproject1'.

Sounds like you didn’t apply the java plugin.

  • the module method needs to be called on the dependencies container, not on the project.
  • calling runtime inside the module method doesn’t make sense, you probably wanted to call dependency

OK, so whoever set this up to remove the duplication between subprojects was probably doing it wrong and now I’m stuck with cleaning it up. :frowning:

  • I had previously tried files() and found that to work, so I assumed that module() would work too, as both were documented in the same place in the Gradle docs, but it seems like this is not the case, which seems inconsistent to me. But if I can’t call module from here, how do I get a “client module” dependency that I can reuse between two subprojects?
  • OK, so runtime can’t be used inside module - I can use dependency, but is there a way to specify that it’s only a runtime dependency? Or do I just not worry about precision of the module dependency definitions?
    • I have even tried to use dependency, but now the problem is referencing the other library. jaxen_core doesn’t work. libraries.jaxen_core doesn’t work either. I could define all libraries outside the array declaration so that they’re normal variables and that would work, but I’d have to be careful to get the order right and we wouldn’t be able to organise things alphabetically anymore. There must surely be a proper way to do all this…
  • Are there any documented examples of reusing dependencies between modules? It seems like it’s just being done wrong in our project but I can’t find any reference to how to do it right. (I imagine that the original author couldn’t figure it out either, which is why it’s wrong now.)

Another non-working example. Since I can’t figure out how to reference the other library in the middle of defining the list of dependencies, I thought I would try inlining it:

  jdom = [
    dependencies.module('org.jdom:jdom:1.0') {
      // Actually a runtime dependency but I can't see how to do that.
      dependency dependencies.module('org.jdom:jaxen-jdom:1.0-FCS') {
        dependency dependencies.module('org.jdom:jaxen-core:1.0-FCS')
      }
    }
  ]

That gives:

> No signature of method: java.util.HashSet.module() is applicable for argument types: (java.lang.String, dependencies_5he5nx8rm9zd7i6d98itung1c$_run_closure1$_closure2) values: [org.jdom:jaxen-jdom:1.0-FCS, dependencies_5he5nx8rm9zd7i6d98itung1c$_run_closure1$_closure2@732c9b5c]

So whether I put dependencies. in there or not, it doesn’t work, so I assume that isn’t the way to get access to that.

What about putting it directly into dependencies

dependencies {
  compile module('org.jdom:jdom:1.0') {
    dependency module('org.jdom:jaxen-jdom:1.0-FCS') {
      dependency 'org.jdom:jaxen-core:1.0-FCS'
    }
  }
}

This gives:

> Cannot convert a null value to an object of type Dependency.
  The following types/formats are supported:
    - Instances of Dependency.
    - String or CharSequence values, for example 'org.gradle:gradle-core:1.0'.
    - Maps, for example [group: 'org.gradle', name: 'gradle-core', version: '1.0'].
    - FileCollections, for example files('some.jar', 'someOther.jar').
    - Projects, for example project(':some:project:path').
    - ClassPathNotation, for example gradleApi().

Needless to say, there are no nulls in that example.

I also tried:

dependencies {
  compile module('org.jdom:jdom:1.0') {
    dependency module('org.jdom:jaxen-jdom:1.0-FCS') {
      dependency module('org.jdom:jaxen-core:1.0-FCS') {}
    }
  }
}

This gives the same error.

The next try:

dependencies {
  compile module('org.jdom:jdom:1.0') {
    module('org.jdom:jaxen-jdom:1.0-FCS') {
      module('org.jdom:jaxen-core:1.0-FCS') {}
    }
  }
}

This gives:

> Could not find method compile() for arguments [org.gradle.api.internal.artifacts.dependencies.DefaultClientModule_Decorated@ffe5b8fd] on root project 'subproject1'.

Which is similar to the Java plugin not being applied, except that in the build.gradle at the top, it is being applied to all projects:

allprojects {
  apply plugin: 'java'
}

Well, I give up. Nothing seems to work. Error messages aren’t informative enough to figure out what’s wrong. Documentation is incomplete when you’re lucky enough for it to be present at all and reading Javadoc seems to be the only way to find out half the things you can do. Rather than wasting any more time trying to get this to work, I will just cram all the dependencies into libraries.jdom and live with our classpath including things it shouldn’t for the time being.

Maybe after the next release is done we can ditch this build system for something better.

No other build system even allows you to override dependency metadata from other libraries :wink:

Which begs the question: Why are you using client modules in this case? jdom has correct dependency metadata. Or is this just an example you came up with?

Here’s a minimal example for what you want to do:

apply plugin: 'java'

repositories.jcenter()

ext.libs = [
  junit : 'junit:junit:4.12',
  jaxen : dependencies.module('org.jdom:jaxen-jdom:1.0-FCS') {
    dependency 'org.jdom:jaxen-core:1.0-FCS'
  }
]

//if you want to refer to earlier declared entries, you cannot use the map
//literal syntax. Instead, you'll have to put it in another statement like this:
libs['jdom'] = dependencies.module('org.jdom:jdom:1.1') {
  dependency libs.jaxen
}

dependencies {
  compile libs.jdom
  testCompile libs.junit
}

JDOM was the actual one I was trying to fix because their POM is missing the dependency on Jaxen, causing our tests to fail at runtime.

Although to be precise, they do have the dependency listed, but it’s “optional”. Gradle apparently took this to mean “not a dependency” so it ended up missing from the classpath. (That bit at least makes sense. I personally hate the notion of optional dependencies. Either something is a dependency, or it isn’t.)

If you just want more than one dependency whenever someone references libs.jdom, then you don’t need client modules.

You can simply do:

ext.libs = [
  jdom : [
      'org.jdom:jdom:1.0',
      'org.jdom:jaxen-jdom:1.0-FCS',
      'org.jdom:jaxen-core:1.0-FCS'
    ]
  }
]

It’s actually quite a useful mechanism: Optional means: “I also integrate with this other library if you use it. If not, that’s okay”.