`gradle idea` configures dependencies incorrectly in modules

Given a project with two submodules where the first submodule depends on a library and the second submodule depends on the first submodule:

a/build.gradle:

apply from: '../dependencies.gradle'

dependencies {
  compile libraries.somelib
}

b/build.gradle:

dependencies {
  compile project(':a')
}

If I run gradle idea, it generates b/b.iml containing two incorrect entries for somelib:

    <orderEntry type="module-library" scope="RUNTIME">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.example/somelib/1.0/1d63f369ac78e4838a3197147012026e791008cb/somelib-1.0.jar!/" />
        </CLASSES>
      </library>
    </orderEntry>
    <orderEntry type="module-library" scope="PROVIDED">
      <library>
        <CLASSES>
          <root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/com.example/somelib/1.0/1d63f369ac78e4838a3197147012026e791008cb/somelib-1.0.jar!/" />
        </CLASSES>
      </library>
    </orderEntry>

Neither of these entries should exist in the IDEA project, because if I were setting up the same structure in IDEA, I would not create these entries. Our old IDEA project prior to gradle did not have these incorrect entries, but because they’re there, people are accidentally importing classes from packages that aren’t even supposed to be on the compilation classpath.

Gradle Version: 2.14
Operating System: OSX 10.11

Example project: https://github.com/trejkaz/gradle_dependency_leak

(Notice how this project even tries to set the compile dependencies as transient - doesn’t stop gradle idea putting in the dependency for compile!)

This works as intended, Gradle does transitive dependency resolution and puts the transitive dependencies into the module.

Notice that in your example project the dependency is no longer in the compile scope, but only in the runtime and provided scope. So the compile.transtive=false worked.

We plan to add api/implementation dependency separation in the future, which will solve this use case.

If the transitive declaration worked, why has it added a PROVIDED dependency which puts it on the compile classpath? It really shouldn’t be doing that, and it doesn’t do that in Gradle 2.8.

This changed with the introduction of the compileOnly configuration, which is mapped to Idea’s provided and which you did not mark as non-transitive.

configurations {
    compile.transitive = false
    compileOnly.transitive = false
  }

In any case, transitive=false is not the solution to the problem you are facing, as it also removes the dependencies you do want. What you really need is api/implementation separation, which is already on our radar.

1 Like

I don’t want it pulling in transitive dependencies for compile-time because it breaks encapsulation, and a build system that breaks basic programming principles is not something that I can accept.

I am fine with it doing so at runtime, and I have left runtime as transitive.

So I’m not quite sure what you mean when saying that it will remove dependencies that I want. If I wanted a dependency, I would have added it as a dependency.

Surprisingly, compileOnly.transitive doesn’t even remove the dependency I don’t want.

So I don’t get it. Why is it so hard to get an IML file like what we had before switching to Gradle? Do we have to go back to maintaining these files by hand maybe?

Note that even the RUNTIME dependency in the second project is incorrect - in IDEA projects, IDEA already takes care of transitive dependencies, so no reference to log4j is expected in the second project at all.