Spring Boot Gradle multi-project build not seeing internal dependencies during tests - java

I'm having a problem on a larger multi-project build that I was migrating from Gradle 4 to 5, and replicated the issue on a smaller, more concise build to demonstrate the issue.
I have 2 projects in the build. One is a dependency (basic library) used by the other.
demo (root project)
|- build.gradle
|
|--- demo-web
|---|- build.gradle
|
|--- demo-dependency
|---|- build.gradle
Snippet of demo-web: build.gradle
...
dependencies {
implementation project(':demo-dependency')
...
}
...
The dependency project defines one class that is used in the web project, DownstreamThing.
The web project attempts to use that to construct its own object, but during a build on the root project level, it fails.
> ./gradlew build
> Task :demo-web:test
com.example.demo.ThingTest > testThing FAILED
java.lang.NoClassDefFoundError at ThingTest.java:12
Caused by: java.lang.ClassNotFoundException at ThingTest.java:12
ThingTest.java
#Test
public void testThing() {
DownstreamThing t = new DownstreamThing(); //line 12, ClassNotFoundException
assertTrue(t != null);
}
I didn't have any trouble with this in Gradle 4, but only in Gradle 5. Why is the dependency not being found during the test task?
Full source for the example is here: https://bitbucket.org/travelsized/gradle-problem/src/master/

I believe he reason you are getting that exception is because you have applied the Spring Boot plugin to the demo-dependency project. What this plugin does is to repackage the jar file to a fat jar that needs a special classloader to load the content.
You can still use the Spring Boot dependencies (e.g. the starters) in the dependency project without the plugin. So if you can, just remove it.
If there is a particular reason why you have it, you will need to keep the original jar file so that is used as the actual dependency. For Spring Boot 1.5.x, you can do that with something like this:
bootRepackage {
classifier = "boot"
}
bootJar {
enabled = true
}
But do note that I don't think Spring Boot 1.5 is fully compatible with Gradle 5 and later (at this time we are on 6.0), so you might need to either downgrade Gradle or upgrade Spring Boot.

Bjørn Vester's answer got me pointed in the right direction. The spring-boot plugin was causing the jar tasks to go awry. I needed to make sure that the bootJar task was disabled for the dependency while the jar task was enabled.
The changes in the configuration to do this between versions of Gradle and the Spring Boot plugin made this get lost in the upgrades.
Previously, I could specify a classifier for the jar post-boot:
bootRepackage {
classifier = 'exec'
}
Now, I need to enable and disable the appropriate tasks:
bootJar {
enabled = false
}
jar {
enabled = true
archiveBaseName = "demo-dependency"
}
In the larger project, I previously had a jar task that specified the archiveBaseName, but didn't explicitly enable it to override the bootJar task. Once I made the above changes (while keeping the boot plugins in place), things started working.

Related

updating gradle version for a project that depends on another shared library project doesn't work in a toolchain (kubernetes)

I'm getting this error while trying to commit my changes into gitlab repo and deploy it in kubernetes after upgrading gradle version to 7.4
Also I think the problem in how I include my shared library inside the project it self, that's what I sense from the error output below
Could not determine the dependencies of task ':distTar'.
Could not resolve all task dependencies for configuration ':runtimeClasspath'.
Could not resolve project :vlc-shc.
Required by:
project :
> No matching configuration of project :vlc-shc was found. The consumer was configured to find a runtime of a library compatible with Java 11, packaged as a jar, preferably optimized for standard JVMs, and its dependencies declared externally but:
- None of the consumable configurations have attributes.
build.gradle:
dependencies {
implementation project(':vlc-shc')
}
setting.gradle:
rootProject.name = 'vlc-myProject'
include ('vlc-shc')

Spring Layered Fat Jar and Gradle Plugin layertools jarmode

Spring boot 2.3.0 has the layered fat jars which are optimized for container image builds. Now, the Spring documentation discusses only Maven build tool, and not Gradle.
Please, what do we need to do to the Gradle plugin to allow it to build a "layered jar" for spring boot?
For example: if extracting a jar with the command java -Djarmode=layertools -jar app.jar extract which has been made with Gradle Spring boot plugin id("org.springframework.boot") version "2.3.0.RELEASE" it fails with message: Unsupported jarmode 'layertools'. Basically if the jar has been built with ./gradlew bootJar it seems layered jar is not active by default.
Extracting a layered jar should result in the following directories, and these are then copied into another container image for execution, and only if lower layers have changed does the caching system have to re-read them. Layered jar extracted with -Djarmode=layertools flag:
./dependencies/
./snapshot-dependencies/
./resources/
./application/
The blog at spring Spring Blog 2.3.0.ML discusses Maven build tool and the configuration:
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>LAYERED_JAR</layout>
</configuration>
How do we enable the same configuration for Gradle plugin?
The documentation is available for how to activate the layered fat jar for spring boot applications with the spring-boot-gradle-plugin.
See the details at Packaging Layered Jars with Gradle.
In the build.gradle file:
// Groovy solution
bootJar {
layered()
}
// Kotlin solution
tasks.getByName<BootJar>("bootJar") {
layered()
}
Hope this helps.
You can also find more information in here:
https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/gradle-plugin/reference/html/#packaging-layered-jars
bootJar {
layered {
enabled = true
application {
intoLayer("spring-boot-loader") {
include "org/springframework/boot/loader/**"
}
intoLayer("application")
}
dependencies {
intoLayer("snapshot-dependencies") {
include "*:*:*SNAPSHOT"
}
intoLayer("dependencies")
}
layerOrder = ["dependencies", "spring-boot-loader", "snapshot-dependencies", "application"]
}
}
You will find corresponding layer names in layers.idx file inside you JAR.

Spring Boot DevTools being used inside docker container even after exclusion in gradle build

So we are using Spring boot to deliver our application. We use the Jib plugin to monitor create docker images and run them.
We use gradle to build the project and there dev tools is identified as a developmentOnly dependency.
Just as mentioned in the spring docs at https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-devtools .
However when it runs in the container in prod I still see it getting restarted now and then.
My question is does the gradle configuration not really exlude it from packaging.
Do i need to explicitly set the -Dspring.devtools.restart.enabled=false parameter ?
Solution :
So turns out it was the gradle jib plugin playing games.
While the spring documentation is spot on about how to go about removing the dependency from gradle spring boot project. The technique of specifying a developmentOnly only helps in telling gradle to ignore the dev tools. The jib gradle plugin has a mind of its own.
It includes all jars when building a docker image and there is no way to exclude any jar. The only reasonable way is to customize the gradle jib plugin in build.gradle to write this ,
jib {
from {
image 'gcr.io/distroless/java:11'
}
container {
jvmFlags = ['-Xms1G', '-Xmx1G', '-Dspring.devtools.restart.enabled = false']
}
}
This will make sure that even if the jar is included the container environment has the restart taken care of.
Reference : https://github.com/spring-projects/spring-boot/issues/15382
There's really a few problems here:
Springboot has its own custom definitions instead of using what would be the equivalent of profiles. Their approach is best for springboot users, but rather hard to integrate with given all their custom logic.
Jib can't know all the custom implementation of each framework.
I really think what you should be doing is something like this:
dependencies {
if (System.getProperty("development") == true) {
// include the springboot devtool dependency
}
}
When you want to run or build in dev mode, just do
./gradlew whateverTask -Ddevelopment=true
You can achieve that by setting spring.devtools.restart.enabled=false in your application.properties or your specific profile properties ex. application-cloud.properties. Let me know if this works.
Well, just faced the same problem recently and it seems there's already a very straight-forward way to solve it.
The problem
Jib is actually considering spring-boot-devtools as a runtime dependency and so, adding it to the image. In my case, this is also true for h2 database jar which is used only locally for development.
Also, I wouldn't like to deal with any extra custom parameter in my build, neither turn-off features by configuration if I don't really wanna them available at production.
Solution
GoogleContainerTools' team has released a jib-extension to deal with devtools problem directly. There's a Gradle and Maven version and it works flawlessly.
However, to my needs (also exclude h2) I've decided to use jib layer filter extension so I can keep my image as close to bootJar as possible.
Here goes the code snippet in gradle:
// should be at the top of build.gradle
buildscript {
dependencies {
classpath('com.google.cloud.tools:jib-layer-filter-extension-gradle:0.1.0')
}
}
jib {
// ...
pluginExtensions {
pluginExtension {
implementation = 'com.google.cloud.tools.jib.gradle.extension.layerfilter.JibLayerFilterExtension'
configuration {
filters {
filter {
glob = '**/h2-*.jar'
}
filter {
glob = '**/spring-boot-devtools-*.jar'
}
}
}
}
}
}
Check here the Gradle and Maven version for this extension.

How to build executable jar of a submodule with gradle + spring boot 1.5

I have a multi-module project:
Root project 'platform'
+--- Project ':api'
+--- Project ':common'
and in the :common module I include all the dependencies, in the :api module, I only have
apply(plugin = "org.springframework.boot")
dependencies {
implement(project(":common"))
}
The problem is that when I build the :api module, from the jar file I can't see any of the dependencies inside the jar file, there's no BOOT-INF/libs/ only BOOT-INF/classes/.
When I run the jar with java -jar, it says NoClassFound for one of the class in the :common module.
gradle :api:bootRun works fine.
Is there any other config I should do?
I'm using gradle 4.9 kotlin dsl and spring boot plugin 1.5.15.RELEASE
The reason is that the spring boot 1.5 gradle plugin is only targeted for gradle 2 & 3, which does not support the implementation configuration (introduced in gradle 4).
Based on this documentation, it describes that by default only compile and runtime configurations are included. That being said, it is possible to include a custom configuration to make it work.
If you look at the hierarchy of gradle configurations for gradle 5 Illustrated here, runtimeClasspath is the root of implementation, and therefore has all the dependencies you need for your runnable jar.
This means that for spring boot 1.5, you can point it to a custom configuration to get it to correctly build the runnable jar:
build.gradle:
bootRepackage {
customConfiguration = 'runtimeClasspath'
}
build.gradle.kts:
import org.springframework.boot.gradle.repackage.RepackageTask
// more of the build file
tasks {
"bootRepackage"(RepackageTask::class) {
setCustomConfiguration("runtimeClasspath")
}
}
Turns out it's because of spring-boot-plugin 1.5.x somehow doesn't recognize the implement(project(:common)), by changing to compile(project(:common)) it works fine.

Gradle spring boot with assembly module

I'm new to Gradle and I'm facing a problem ( using Gradle 2.14)
My project has 3 subprojects: assembly, sprinBootProject, E2E
The assembly module is responsible of zipping the jar of the spring boot application along with properties files.
The E2E module is an 'end to end' tests module for testing the spring boot application.
I'm using the distribution plugin in assembly/build.gradle like this:
apply plugin: 'distribution'
distributions {
main {
baseName "project-assembly"
contents{
from project(':springBootProject').fileTree(dir: 'build/libs', include: ['*.jar'])
from project(':springBootProject').fileTree(dir: 'build/resources/main', includes: ['*.properties', 'conf/*.properties'])
}
}
}
but I need to make sure that springBootProject will be evaluated before the assembly subproject, so I used dependsOn like that:
distZip.dependsOn(':springBootProject:build')
when I added this line I see that the E2E module is failing in compileTestJava task, so this line is effecting how the spring boot application is generated (I think)
E2E/build.gradle:
dependencies {
testCompile project(":springBootProject")
}
so my question is am I doing it right? why when I added the dependsOn line the E2E module is effected, and how can I force Gradle to evaluate the spring boot application before the assembly module?
I figure it out.
Gradle will evaluate modules by alphabetic order
so the evaluation will be: assembly -> E2E -> springBootProject
when adding this line to assembly/build.gradle:
distZip.dependsOn(':springBootProject:build')
the order of the evaluation changed to be: assembly -> springBootProject -> E2E
now in 'springBootProject' module I am using the spring boot plugin in order to generate an executable jar, this executable jar is generated by the task :springBootProject:bootRepackage which uses the jar generated by the :springBootProject:jar task and convert it to executable.
now because the 'E2E' module depends on 'springBootProject' it will use it's jar, but now this jar is executable, and because of that I had a compilation error in 'E2E'.
so I have to make sure that the 'E2E' module gets the original jar and not the executable one.
so I added this line in springBootProject/build.gradle:
bootRepackage.mustRunAfter(':E2E:test')
which seem to solve the problem

Categories

Resources