Gradle 1.9: Dependency resolution issue if artifact present in multiple scopes within the pom


(Mike Meessen) #1

Hi there,

After upgrading to Gradle 1.9 today, we found an issue with dependency resolutions. Here’s what we have:

A buildscript dependency for a custom plugin:

buildscript {
    repositories { ... }
    dependencies {
        classpath (group: 'mygroup', name: 'myplugin', version: '1.0')
    }
}

The POM of myplugin contains something like this:

<dependency>
  <groupId>mygroup</groupId>
  <artifactId>mylib</artifactId>
  <version>1.0</version>
  <scope>compile</scope>
</dependency>
<dependency>
  <groupId>mygroup</groupId>
  <artifactId>mylib</artifactId>
  <version>1.0</version>
  <scope>test</scope>
</dependency>

The reason why “mylib” ends up in the compile and test scopes is irrelevant for now. The problem is that Gradle 1.9 doesn’t include mylib within the buildscript classpath of the project that uses this plugin at all whereas with Gradle 1.8 it was present.

I checked that with the following task in the client project:

task show << {
   buildscript.configurations.classpath.each { println it }
}

Which outputs with Gradle 1.8: ’

d:\myproject>gradle show | find “mylib”

C:\Users\me.gradle\caches\artifacts-26\filestore\mygroup\mylib\1.0\jar\2457eb4909d40f7bcefe46e8c2a37c5f68c032\mylib-1.0.jar ’

And with Gradle 1.9 (without any changes to the buildscript), mylib is not present at all.

Eventually, this leads to ClassNotFoundExceptions as mylib is required by the plugin.

Best regards, Mike


(Luke Daley) #2

Hi,

Would you be able to put together a sample that exhibits the problem? Sounds like a bad regression and we need to make sure it’s fixed in 1.10.


(Benjamin Muschko) #3

The behavior you are seeing is a standard Maven dependency resolution behavior. If you declare multiple dependencies with the same group ID, artifact ID and version but different scopes, then the last declared dependency is picked. You can easily verify that by trying the same thing with Maven.

Let’s assume you have a published artifact in your local Maven repository that declares the following dependencies in its POM:

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.8.2</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.8.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

If you run mvn dependency:tree, you will see that JUnit is not shown in the dependency tree. If you turn around the scopes (test first, then compile), then the dependency is shown. You can observe the same behavior with Gradle 1.9. The behavior you see in Gradle 1.8 was actually not how Maven resolves this situation.


(Mike Meessen) #4

Good explanation :slight_smile: I’m not a mvn guy, but the Gradle 1.8 behavior seemed more logical to me… Well, okay, so there’s “nothing to be fixed” I guess?

Nevertheless, since the behavior changed, it might be worth mentioning in the 1.9 release notes…?


(Benjamin Muschko) #5

We prefer to be as close to the Maven POM parsing behavior as possible. I guess it’s kind of too late to add that to the release notes. We’ll certainly try to be even more diligent about the release notes in the future.


(Stefan Marklund) #6

Hello guys,

Thank you for the explanations this was very helpful and now I understand my own issue better.

I didnt know Maven used this style to select dependencies having jumped onto the Gradle-train directly. I dont agree with making this change. It seems like a very dubious gain to break long used functionality for the sake of following the Maven convention.

This change has raised some issues for me, currently minor but who knows what the future brings.

Let me show you.

In my projects I put the test artifact in the testRuntime configuration because it happens that some tests have dependencies on other tests and it allows me to depend on them. I think I picked up the trick by looking at the Gradle build. Now lets use such a dependency in my project called "B" with uses the test-artifact from project "A".

``` dependencies { compile project(":A") testCompile project(path: ":A", configuration: 'testRuntime') } ```

That works great with project dependencies. I use the maven plugin to publish the corresponding pom.xml.

<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>deptest.B</groupId>
  <artifactId>B</artifactId>
  <version>1.0</version>
  <dependencies>
    <dependency>
      <groupId>deptest.A</groupId>
      <artifactId>A</artifactId>
      <version>1.0</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>deptest.A</groupId>
      <artifactId>A</artifactId>
      <version>1.0</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

I reran the build a couple of times until I got the test scope last like I need for the example. This is not deterministic. Lets assume my project "A" has a dependency block like: ``` dependencies { compile "junit:junit:4.10" } ```

And now I specify a dependency to project "B" (first example project) in project "C": ``` dependencies { compile 'deptest.B:B:1.0' } ```

Project "C" resolved compile classpaths:

Gradle 1.8: ``` /home/s/.m2/repository/deptest/B/B/1.0/B-1.0.jar /home/s/.m2/repository/deptest/A/A/1.0/A-1.0.jar /home/s/.m2/repository/junit/junit/4.10/junit-4.10.jar /home/s/.m2/repository/org/hamcrest/hamcrest-core/1.1/hamcrest-core-1.1.jar ```

Gradle 1.11: ``` /home/s/.m2/repository/deptest/B/B/1.0/B-1.0.jar ```

As you can see project "A" in its entirety is missing in the resolved dependencies in project "C". Any artifact published up until this point cannot be trusted to produce workable dependencies past Gradle 1.8.

The maven plugin is publishing dependencies with a 50-50 risk that the test scope is last if a dependency on testRuntime was specified as above. This makes the Maven support not so desirable for me at the moment with the maven plugin producing bad poms without manual intervention and the maven-publish plugin not a dependencies block at all. While I might be ok only using project dependencies other teams will want to depend directly on my artifacts.

Thank you,

Stefan