Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dependency exclusions declared in spring-boot-dependencies have no effect when using Gradle 6 #21350

Closed
dawi opened this issue May 7, 2020 · 10 comments
Assignees
Labels
type: bug A general bug
Milestone

Comments

@dawi
Copy link

dawi commented May 7, 2020

Disclaimer: I am not sure wether this issues belongs to Gradle or Spring Boot.

Two different issues, but in both cases Springs dependency excludes are ignored.

Case 1

Given the following Gradle build script:

plugins {
    `java-library`
}

repositories {
    jcenter()
    maven { url = uri("https://repo.spring.io/libs-milestone") }
}

dependencies {
    api(platform("org.springframework.boot:spring-boot-dependencies:2.3.0.RC1"))
    api("org.springframework.boot:spring-boot-starter-quartz")
}

With Gradle 5.2.1 you will end with following quartz dependencies on the classpath:

\--- org.springframework.boot:spring-boot-starter-quartz -> 2.3.0.RC1
     \--- org.quartz-scheduler:quartz:2.3.2
          +--- com.mchange:mchange-commons-java:0.2.15
          \--- org.slf4j:slf4j-api:1.7.7 -> 1.7.30

With Gradle 6.4 you will end with following quartz dependencies on the classpath:

\--- org.springframework.boot:spring-boot-starter-quartz -> 2.3.0.RC1
     \--- org.quartz-scheduler:quartz -> 2.3.2
          +--- com.mchange:c3p0:0.9.5.4
          |    \--- com.mchange:mchange-commons-java:0.2.15
          +--- com.mchange:mchange-commons-java:0.2.15
          +--- com.zaxxer:HikariCP-java7:2.4.13
          |    \--- org.slf4j:slf4j-api:1.7.21 -> 1.7.30
          \--- org.slf4j:slf4j-api:1.7.7 -> 1.7.30

I assume that this change of behavior occurs because Gradle 6.4 uses Gradle Module Metadata and Gradle 5 uses Maven BOMs.

Case 2

Given the following dependency declaration:

dependencies {
    api(platform("org.springframework.boot:spring-boot-dependencies:2.3.0.RC1"))
    api("org.springframework.boot:spring-boot-starter-data-jpa")
}

The following transitive hibernate-core dependencies will be added to the classpath.
Interestingly, exclusions work for spring-boot-starter-data-jpa but not for spring-boot-starter-quartz.

\--- org.springframework.boot:spring-boot-starter-data-jpa -> 2.3.0.RC1
     +--- org.hibernate:hibernate-core -> 5.4.14.Final
     |    +--- org.jboss.logging:jboss-logging:3.3.2.Final -> 3.4.1.Final
     |    +--- org.javassist:javassist:3.24.0-GA
     |    +--- net.bytebuddy:byte-buddy:1.10.7 -> 1.10.10
     |    +--- antlr:antlr:2.7.7
     |    +--- org.jboss:jandex:2.1.1.Final
     |    +--- com.fasterxml:classmate:1.5.1
     |    +--- org.dom4j:dom4j:2.1.1
     |    +--- org.hibernate.common:hibernate-commons-annotations:5.1.0.Final
     |    |    \--- org.jboss.logging:jboss-logging:3.3.2.Final -> 3.4.1.Final
     |    \--- org.glassfish.jaxb:jaxb-runtime:2.3.1 -> 2.3.3
     |         +--- jakarta.xml.bind:jakarta.xml.bind-api:2.3.3
     |         +--- org.glassfish.jaxb:txw2:2.3.3
     |         +--- com.sun.istack:istack-commons-runtime:3.0.11
     |         \--- com.sun.activation:jakarta.activation:1.2.2

If I add hibernate-jcache, which also depends on hibernate-core, the result is that the dependency excludes defined via Spring Boot are "ignored".

dependencies {
    api(platform("org.springframework.boot:spring-boot-dependencies:2.3.0.RC1"))
    api("org.springframework.boot:spring-boot-starter-data-jpa")
    api("org.hibernate:hibernate-jcache")
}

The following transitive hibernate libraries will also be added tom the classpath, as long as I don't exclude them manually.

+--- org.springframework.boot:spring-boot-starter-data-jpa -> 2.3.0.RC1
|    +--- org.hibernate:hibernate-core -> 5.4.14.Final
|    |    +--- javax.persistence:javax.persistence-api:2.2
|    |    +--- org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:1.1.1.Final
|    |    +--- javax.activation:javax.activation-api:1.2.0
|    |    +--- javax.xml.bind:jaxb-api:2.3.1
|    |    |    \--- javax.activation:javax.activation-api:1.2.0
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label May 7, 2020
@wilkinsona
Copy link
Member

wilkinsona commented May 7, 2020

Thanks for the report.

In the Quartz case, the excludes are defined in spring-boot-dependencies where they're then published in the pom.xml. They don't, however, appear in the module.json file. We currently apply them manually to the XML that Gradle generates from the dependency constraints. I don't know if it's possible for use to add exclusions to the constraints themselves so that they'd appear in the module.json as well (and perhaps make it into the pom.xml automatically as well).

@melix is it possible to specify exclusions in the constraints described by Gradle's module metadata? I looked at the documentation and didn't spot any information about exclusions. Specifically, we'd like to include exclusions in the module metadata for our spring-boot-dependencies Java platform. We add the constraints like this at the moment:

this.dependencyHandler.getConstraints().add(JavaPlatformPlugin.API_CONFIGURATION_NAME,
		createDependencyNotation(group.getId(), module.getName(), library.getVersion()));

this.dependencyHandler is a org.gradle.api.artifacts.dsl.DependencyHandler. Is it possible to declare exclusions at the same time?

We'd like to publish module metadata for our platform that results in the same behaviour as you'd get from a Maven bom that contains something like the following:

<dependencyManagement>
   <dependencies>
      <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>${quartz.version}</version>
        <exclusions>
          <exclusion>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
          </exclusion>
          <exclusion>
            <groupId>com.zaxxer</groupId>
            <artifactId>*</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
   <dependencies>
</dependencyManagement>

@dawi
Copy link
Author

dawi commented May 7, 2020

I'm afraid that it's not possible: gradle/gradle#12214.

I asked this question, because I had the same issue with our custom platform module. This platform is based on the spring boot platform and I wanted to add additional libraries with exclusions, so that subprojects (and we have quite a few of them) don't have to exclude everything again.

Since it was not possible to define the exclusions, I added constraints to out platform module, so that subprojects at least recognize that they may have to exclude some libraries. This constraints also help if you add or update dependencies. Because the build will fail if a excluded dependency is accidentally leaks into the classpath.

But I am not happy with this solution, because it feels like it should be able to define this via Gradle platform.

My biggest concern currently is, that this Gradle dependency management is getting to complex for "normal" users (like me). I really spent weeks just to get the dependency management right and I still have issues and am not sure if I am doing it right.

Just to get an idea, this is a short extract from our constraints, which we mainly maintain, because we cannot rely on Gradle/Spring exclusion of transitive dependencies:

constraints {

    api("org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec") {
        version {
            rejectAll()
            because("We should use jakarta.transaction-api instead.")
        }
    }

    api("org.apache.geronimo.specs:geronimo-jms_1.1_spec") {
        version {
            rejectAll()
            because("We should use jakarta.jms-api instead.")
        }
    }

    api("javax.servlet:javax.servlet-api") {
        version {
            rejectAll()
            because("We should use jakarta.servlet-api instead.")
        }
    }

    api("com.zaxxer:HikariCP-java7") {
        version {
            rejectAll()
            because("Optional quartz-scheduler dependency. Not needed in our case.")
        }
    }

    api("com.mchange:c3p0") {
        version {
            rejectAll()
            because("Optional quartz-scheduler dependency. Not needed in our case.")
        }
    }

    api("com.mchange:mchange-commons-java") {
        version {
            rejectAll()
            because("Optional quartz-scheduler dependency. Not needed in our case.")
        }
    }
}

@wilkinsona
Copy link
Member

Looking at the second case, this is standard behaviour for Gradle. While we have some exclusions declared on hibernate-core in spring-boot-starter-data-jpa, adding a dependency on hibernate-jcache adds another route to hibernate-core to the dependency graph. This route does not have the same exclusions so the unwanted dependencies are pulled in. This happens because, unlike Maven, Gradle requires exclusions to be declared on every route to a dependency for those exclusions to be effective.

@wilkinsona
Copy link
Member

Thinking about this issue got me wondering if we could provide some dependency substitutions out of the box. I've opened #21359 to explore that idea.

@wilkinsona
Copy link
Member

We currently have 22 exclusions declared in spring-boot-dependencies. We could move/copy those to the various starters, but not all managed dependencies have a starter. Liquibase is one example of such a dependency. The alternative is to disable the publication of Gradle's module metadata for spring-boot-dependencies. In its absence, Gradle will use the pom file where it will find and honour the exclusions. It's a little surprising that Gradle's behaviour is better-suited to our needs when it's using Maven's metadata rather than its own, but it may well be our best option here.

@wilkinsona wilkinsona changed the title Spring dependency excludes are ignored Dependency exclusions declared in spring-boot-dependencies have no effect when using Gradle 6 May 7, 2020
@wilkinsona wilkinsona added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels May 7, 2020
@wilkinsona wilkinsona added this to the 2.3.0 milestone May 7, 2020
@dawi
Copy link
Author

dawi commented May 7, 2020

Regarding the second case: I would appreciate if Gradle would offer an additional simpler solution, but yes, currently there is no other option then to add the exclusions to all the starter poms. But it will also have downsides:

  • It only works if your project uses the starters. You would still get all transitive dependencies when you add a hibernate-core dependency to your project. Something that would not be the case with Maven BOMs, and this is hard to explain to users.
  • From a maintainers perspective: You may have to repeat the same exclusion multiple times over various starters. Again something that would be easy with Maven BOMs.
  • Beside the technical issues, almost certainly there will be users that just don't care and they will end up with libraries on their classpath that shouldn't be there.

Since Gradle Module Metadata is still young, and not yet widely used, maybe it would be good to talk to them about this topics?

Last but not least: @wilkinsona Thank you for your answer and your great work. :)

@ljacomet
Copy link

ljacomet commented May 11, 2020

Hey there,

Just to confirm that Gradle does not support defining exclusions on dependency constraints.

The support for excludes defined in Maven dependencyManagement was added when BOM support was added to Gradle, so that producers of Maven BOM would not have to deal with too big differences between Maven and Gradle consumers.

What happens in the Spring Boot build is that the excludes are added on the generated POM file, which indicates this is not a supported thing from the Gradle's perspective and thus indeed is not replicated in the produced Gradle Module Metadata file.

However the discussion on this issue and the suggested workaround with rejectAll() constraints has led the Gradle team to re-open the conversation internally. Updates will be done on gradle/gradle#12214. Note that this is not a statement that a change will be made.

@wilkinsona
Copy link
Member

Thanks very much, @ljacomet. We'll disable publication of Gradle module metadata for spring-boot-dependencies for now.

@wilkinsona
Copy link
Member

A recent change has broken some of the integration tests in start.spring.io. Given the timing of the failures starting to occur, our best guess is that it is this change that has caused them, although we do not really understand why. There is a usage of enforcedPlatform in spring-boot-parent which I believe should be platform along with our internal dependency management plugin so that it isn't enforced for external consumers but is within our build.

@snicoll
Copy link
Member

snicoll commented May 14, 2020

c35ed91 fixed it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

5 participants