Resolving gradle plugin dependency conflicts - java

TL;DR Two gradle plugins use different versions of the same dependency, resulting in compile errors when one of the plugins is invoked.
The Situation
I have a Java project compiled using Gradle 4.x.
The project relies on two plugins: gradle-jaxb-plugin and serenity-gradle-plugin.
Both plugins share a dependency, guice.
The Problem
I need to upgrade one of the plugins (serenety). The upgrade results in a conflict at the point in which the jaxb plugin is invoked.
...
Caused by: java.lang.NoClassDefFoundError: com/google/inject/internal/util/$Maps
at com.google.inject.assistedinject.BindingCollector.<init>(BindingCollector.java:34)
at com.google.inject.assistedinject.FactoryModuleBuilder.<init>(FactoryModuleBuilder.java:206)
at org.openrepose.gradle.plugins.jaxb.schema.guice.DocSlurperModule.configure(DocSlurperModule.groovy:43)
...
I did some sleuthing and googling, and am fairly sure that the issue is rooted in the fact that the version of the serenity plugin uses guice 4.x when it used to use guice 3.x. The jaxb plugin uses guice 3.x.
The Question
How do I separate the plugin dependencies from one another? I would like to include both plugins, but it appears that gradle will take the highest dependency set and use that everywhere.
The Code
Here are the relevant portions of my build.gradle
buildscript {
repositories {
mavenCentral()
maven { url 'https://plugins.gradle.org/m2/' }
}
dependencies {
classpath 'gradle.plugin.org.openrepose:gradle-jaxb-plugin:2.4.1'
classpath 'net.serenity-bdd:serenity-gradle-plugin:1.5.1'
}
}
...
project(':integration-tests') {
apply plugin: 'java'
apply plugin: 'net.serenity-bdd.aggregator'
...
}
...
project(':cms-business-model') {
apply plugin: 'org.openrepose.gradle.plugins.jaxb'
apply plugin: 'java'
...
}
Note: You can replicate the issue by adding the serenity 1.5.1 plugin to the classpath dependencies block of the jaxb plugin examples

TL;DR: When Gradle plugins share a dependency but use different versions of that dependency only the highest version is actually used. You have to explicitly exclude the higher-dependency version.
The conflict here came because the jaxb plugin depends on guice:3.0 AND guice-assistedinject:3.0.
When serenity uses guice:4.0 there was a version mismatch between guice:4.0 and guice-assistedinject:3.0
The solution is to exclude the guice dependency from serenity, therefore falling back on guice:3.0
Updated Code
buildscript {
repositories {
mavenCentral()
maven { url 'https://plugins.gradle.org/m2/' }
}
dependencies {
classpath 'gradle.plugin.org.openrepose:gradle-jaxb-plugin:2.4.1'
classpath ('net.serenity-bdd:serenity-gradle-plugin:1.5.1') {
exclude group: 'com.google.inject', module:'guice'
}
}
}
...
Alternative Solution
Another possibility may have been to require guice-assistedinject:4.0, but the above worked so I didn't continue to explore.

Related

Gradle spring dependency management plugin and publishing the library

Im have my library, for help me with spring stuff, build.gradle looks like
plugins {
id 'java-library'
id 'maven-publish'
id "io.spring.dependency-management" version "1.0.11.RELEASE"
}
dependencyManagement {
imports {
mavenBom 'org.springframework.boot:spring-boot-dependencies:2.7.0'
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
}
publishing {
// Default maven publish settings
}
When publishing, Gradle generates .module files, and then if the library is used by gradle, it prioritizes .module files over .pom files
The problem is that no dependencyManagement information is written to the .module file, and so when I try to use the library in another project, I get
Could not determine the dependencies of task ':shadowJar'.
> Could not resolve all dependencies for configuration ':runtimeClasspath'.
> Could not find org.springframework.boot:spring-boot-starter-data-mongodb:.
Required by:
project : > project :database > ru.spliterash:spcore-spring-database-mongo:1.0.0-SNAPSHOT:20220714.235637-1
I can solve this problem by disabling the generation of .module files, and gradle will have to use a pom file in which the version is written, but maybe if there is some more correct solution, because it seems to me that this is not entirely correct
I have found a solution to the problem.
In new versions of gradle, this appeared as a built-in solution, and now spring dependency management plugin is not needed. In order to add a maven bom, it is enough just to write, and it will be correctly entered into the module file
dependencies {
implementation(platform('org.springframework.boot:spring-boot-dependencies:2.7.0'))
implementation(platform('org.springframework.cloud:spring-cloud-dependencies:2021.0.2'))
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'
}

Gradle : How to bump transitive dependency version which is coming from plugin?

I have a huge java multimodule application which uses gradle to manage build and dependencies.
In one of the modules let's say module1 the project is using gretty plugin
module1/build.gradle
plugins{
id 'org.gretty'
}
gretty is having a transitive dependency on ch.qos.logback:logback-classic:1.1.3
I want to bump the logback version to the latest. For that I have tried below solutions
dependencies {
// 1 try
implementation 'ch.qos.logback:logback-classic:1.2.6'
// 2nd try
implementation ('ch.qos.logback:logback-classic:1.2.6') {
force = true
}
// 3rd try
constraints {
implementation('ch.qos.logback:logback-classic:1.2.6') {
because 'some xyz reason'
}
}
}
But none of this is having any impact on logback version. Need some suggestion now
What you have done so far is for application dependencies, not build dependencies. To change or add additional dependencies to the build itself, use the buildscript block. So for you case, to bring in a more recent version of Logback:
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
mavenCentral()
}
dependencies {
classpath 'ch.qos.logback:logback-classic:1.2.6'
}
}
https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
You can then invoke the buildEnvironment task to view the dependencies of the build.

What's the difference between a Gradle Plugin that requires an explicit classpath and one that doesn't?

What's the difference between a Gradle Plugin that requires an explicit classpath and one that doesn't?
Also, how do I create a Gradle Plugin that doesn't require an explicit classpath specification?
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath'com.google.firebase:firebase-crashlytics-gradle:2.4.1'
}
plugins {
id "com.cookpad.android.plugin.license-tools" version "1.2.5" // Plugin that does not require explicit classpath specification
id "com.google.firebase.crashlytics" // Plugin that requires explicit classpath specification
}
For core Gradle plugins or plugins already available to the build
script
id «plugin id»
For binary Gradle plugins that need to be resolved
id «plugin id» version «plugin version» [apply «false»]
You can refer this official documentation for further information

How to set "-aspectpath" for the FreeFair AspectJ Gradle Plugin?

I am trying to use an AspectJ Annotation that is in a Library, that I am pulling into my project. My project uses Gradle, so I am attempting to use FreeFair AspectJ Gradle Plugin.
I need to be able to set the AspectJ -aspectpath argument, to the Library Dependency that Gradle is pulling in.
FreeFair, does not seem to have much Documentation, mainly just Sample Code.
In their sample code, I see that I can use this to set the -aspectpath to a local "project":
aspect project(":aspectj:aspect")
Does anyone know how to set the -aspectpath to an external library dependency?
I created an example Project and put it on GitHub: freefair-aspectpath-external-library.
Note: I am using io.freefair.gradle:aspectj-plugin version 2.9.5 because my project is stuck using Gradle version 4.10.3.
Update: I have created a bug for this: https://github.com/freefair/gradle-plugins/issues/46
Thanks to #larsgrefer, who provided the answer in the GitHub Issue (46).
For the io.freefair.aspectj.compile-time-weaving plugin 2.9.5 the configuration is named "aspects" instead of "aspect".
The following fixed the issue:
aspects project(":aspectj:aspect")
The full build file resembles:
buildscript {
repositories {
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
// 2.9.5 for use with Gradle 4.10.3.
classpath "io.freefair.gradle:aspectj-plugin:2.9.5"
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: "io.freefair.aspectj.compile-time-weaving"
aspectj.version = '1.9.3'
group 'xyz.swatt'
version '1.0.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
///// SWATT ///// https://mvnrepository.com/artifact/xyz.swatt/swatt
compile group: 'xyz.swatt', name: 'swatt', version: '1.12.0'
aspect "xyz.swatt:swatt:1.12.0"
}
aspect is a plain old gradle configuration.
This means you can use all the notations described here:
https://docs.gradle.org/current/userguide/dependency_types.html#dependency_types
https://docs.gradle.org/current/userguide/declaring_dependencies.html
dependencies {
aspect project(":my-aspect")
aspect "com.example.foo:bar-aspect:1.0.0"
aspect file("foo.jar")
}

Including Java library built with Gradle throws NoClassDefFoundError

I am writing a Java library and I would like to build the library with Gradle and then test it from a local test project.
I would prefer using Gradle 3.3 for my objective.
The library should be built for Java5 and higher.
So far my build.gradle looks like this:
plugins {
id 'jvm-component'
id 'java-lang'
}
repositories {
mavenCentral()
}
model {
components {
main(JvmLibrarySpec) {
sources {
java {
dependencies {
module 'commons-codec:commons-codec:1.10'
module 'org.apache.httpcomponents:httpcore:4.4.6'
module 'org.apache.httpcomponents:httpclient:4.5.3'
}
}
}
api {
exports 'io.simplepush'
}
targetPlatform 'java5'
}
}
}
The source code of the library is located in src/main/java/io/simplepush/Notification.java and depends on the dependencies stated in the build.gradle file.
Building the library with ./gradlew build works fine and generates build/jars/main/jar/main.jar.
However when I run a test project from IntelliJ (after including main.jar into the test project), I get the following runtime error:
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/http/HttpEntity.
It seems like the test project does not know about the runtime dependencies needed by my library.
I am not sure on what is the correct way to tell the test project about the dependencies of my library.
I do not want a fat jar which includes all dependencies.
Listing all dependencies in the test project itself is also not an option.
Preferably I want the library itself to tell the test project about which dependencies it needs.
The library jar which you have created does not contain any dependency information which the IDE/Gradle can then resolve to be able to compile/run the test project. I see that you are using the maven central repository so what you need to do is to publish your library to your local maven repository and in the test project just add a dependency information (no just plain jar file).
So in both library and test project build.gradle add a maven local repository config.
repositories {
mavenLocal()
mavenCentral()
}
And now you need to publish the library to local repository. As you are using the gradle 3.3 you can use the Maven Publishing.
So in the library build.gradle add a maven publishing information.
publishing {
publications {
maven(MavenPublication) {
groupId 'io.simplepush'
artifactId 'project1-sample'
version '1.1'
from components.java
}
}
}
Gradle “maven-publish” plugin makes this easy to publish to local repository automatically creating a PublishToMavenLocal task.
So you can just run
gradle publishToMavenLocal
Which will publish your library with all the dependency information into local maven repository.
And then you just need to add a library information to you test projects build.gradle
dependencies {
// other dependencies .....
module 'io.simplepush:project1-sample:1.1'
}
I solved it by changing several things.
Thanks to #Babl for pointing me in the right direction.
My new library build.gradle looks like this:
plugins {
id 'java'
id 'maven-publish'
}
sourceCompatibility = 1.5
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile 'commons-codec:commons-codec:1.10'
compile 'org.apache.httpcomponents:httpcore:4.4.6'
compile 'org.apache.httpcomponents:httpclient:4.5.3'
}
publishing {
publications {
maven(MavenPublication) {
groupId 'io.simplepush'
artifactId 'project1-sample'
version '1.1'
from components.java
}
}
}
Now I can push the library to the local maven repository with ./gradlew publishToMavenLocal.
The build.gradle of the test project uses the application plugin and defines a main class (which is Hello in my case). Then I can run ./gradlew installDist to generate an executable file (see Application plugin docs) which puts all dependencies in the classpath and runs just fine.
group 'com.test'
version '1.0-SNAPSHOT'
apply plugin: 'java'
apply plugin: 'application'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile 'io.simplepush:project1-sample:1.1'
}
mainClassName = "Hello"
This specify what repositories to check to fetch the dependencies from
repositories {
mavenCentral()
}
Therefore, anything that is in the dependecies{} will be fetched from those above.
If the test project is not coupled with the library project, (#RaGe example) new test project needs to know where to take the dependency from - you need to publish it, using preferred method.
After that, your new test project needs to specify the library with the preferred configuration (compile...runtime etc) in the build.gradle dependencies{}
After that depending on IDE you need to refresh the classpath and download the dependency from the specified before repository, the transitive dependencies specified in the library dependency (in this case) will get fetched from test projects repositories{}
Library build.gradle
repositories {
mavenCentral()
}
dependencies {
module 'commons-codec:commons-codec:1.10'
module 'org.apache.httpcomponents:httpcore:4.4.6'
module 'org.apache.httpcomponents:httpclient:4.5.3'
}
test project build.gradle
repositories {
mavenCentral() repository to fetch transitives
mavenLocal() or any other repo that you published the library to
}
dependencies {
pref-conf librarygroup:name:version
}
You can use idea or eclipse plugin in gradle for gradle idea or gradle eclipseClasspath tasks to refresh it with your freshly added dependencies.
With this solution, you should not need to pack the transitive dependencies within the library,
PS. I am just confused after you said you want executable jar.

Categories