8

I am expriencing a strange behavior in gradle dependency management, where project A references project B as compile dependency and project B references library C as runtime dependency. Now I can use classes from library C in my project A.

My question: (Why) is this a bug or a feature?

The problem can be reproduced with gradle 2.9 and 2.10 and the following minimal setup:

// settings.gradle
include ':A', ':B'
// build.gradle
allprojects {
    apply plugin: 'java'
    apply plugin: 'maven'

    repositories {
        mavenLocal()
        mavenCentral()
    }
}

project(':A') {
    dependencies {
        compile project(':B')
    }
}

project(':B') {
    dependencies {
        runtime "org.slf4j:slf4j-log4j12:1.7.13"
    }
}

As you can see, a gradle :A:dependencies shows

[...]

compile - Compile classpath for source set 'main'.
\--- project :B
     \--- org.slf4j:slf4j-log4j12:1.7.13
          +--- org.slf4j:slf4j-api:1.7.13
          \--- log4j:log4j:1.2.17
[...]

and using log4j is totally possible in java code residing in project A.

  • Thanks for asking this Michael. The behaviour of gradle in this case is totally counterintuitive :-( – Peti Nov 03 '17 at 15:36

2 Answers2

6

See this Q&A. If you don't specify a configuration, Gradle will choose the default configuration which extends from runtime. A quick fix is to use

compile project(path: ":B", configuration: "compile")
Community
  • 1
  • 1
lance-java
  • 25,497
  • 4
  • 59
  • 101
  • The `default` configuration extends from `runtime` which means that, according to the explanation on the Q&A, "it contains all the dependencies and artifacts of the `runtime` configuration, and potentially more". The dependencies of project ":B" in the runtime configuration are `runtime`. This still doesn't explain why the dependency `slf4j-api` shows up as `compile` in project ":A". – dmoebius Jan 29 '16 at 14:36
  • 2
    The `compile project(':B')` method takes the `default` configuration from Project B and add's it to Project A's `compile` configuration. It is therefore logical that B's `runtime` dependencies are added to A's `compile` dependencies – lance-java Jan 29 '16 at 15:06
  • 3
    I discussed this offline with @dmoebius, and while the answer is totally correct and helpful, one should keep the following in mind: Specifying the `compile` configuration explicitly will indeed prevent your projects compile classpath from being 'polluted' with transitive runtime dependencies, but your projects *runtime* classpath is now also missing these transitive runtime dependencies. So you can either live with the `default`configuration being used or you have to add a second dependency for mapping `runtime`to `runtime`: `runtime project(path: ":B", configuration: "runtime")` – Michael Schaefers Feb 02 '16 at 07:57
  • There's no need to explicitly specify the `runtime` configuration in this case. Since `default` extends `runtime` you can just use `compile project(':B')` – lance-java Feb 02 '16 at 09:08
  • Ok, I understand. But I must say that specifying `compile project(path: ":B", configuration: "compile"); runtime project(':B')` for _each_ dependency just to get (what I would perceive as) correct transitive dependency behaviour is cumbersome at least. Not to mention: totally confusing for newbies. – dmoebius Feb 03 '16 at 08:56
  • 1
    Each line in the `dependencies {}` section configures a single configuration (ie compile or runtime) so it's logical (at least to me) that you will never configure two configurations in a single line of code. I would expect maven to behave in the same way (eg all transitive runtime/compile dependencies added to the same scope as the declared ) – lance-java Feb 03 '16 at 09:41
1

In case of Android libraries (aar) transitive runtime dependency, this was fixed with Gradle starting from 5.0.

Vairavan
  • 1,246
  • 1
  • 15
  • 19