spring boot plugin and gradle dependency implementation - spring-boot

We have been using gradle spring boot plugin version 1.5.14 and gradle 4.10.3 for our builds. After an upgrade of gradle to 6.2.2, we've also changed dependency-definition from compile group to implementation group i.e.:
compile group: 'org.springframework.boot', name: 'spring-boot-starter-integration'
to
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-integration'.
The fat jar created via gradle assemble does to my surprise not contain the required libs under BOOT-INF/lib anymore? If I replace "implementation" with "compile" it works again as expected.
Is there something which needs to be configured so that spring-boot-plugin adds them? Or do I need to upgrade the project to spring boot 2 beforehand?

Implementation will bring in the dependency only for that module it is declared in.
Compile will allow a module that depends on the module to use the dependency as well.
Say you have Module A depending on Module B.
Module A and B both need the dependency:
com.font:font1:1.1.1
If B uses:
implementation 'com.font:font1:1.1.1'
A will not see font1, and will need to bring it into its own build.gradle file as well.
compile 'com.font:font1:1.1.1'
will make it available to the entire classpath (which should be avoided anyway, and the new equivalent is for libraries which uses 'api' instead of 'compile').
Without seeing your project directory, I'm assuming that some dependency is not being pulled into a place where it used to be grabbed from a lower dependency in the hierarchy. You should be able to find the missing dependencies easily by changing compile to implementation one at a time, unless you have a huge multi-module project.

Related

What's the difference between libs and implementation in Gradle?

I have already seen in some project, that in some of them is using libs and libs group: instead of implementation or deprecated compile. After local switch to implementation everything looks fine and works correctly.
Example:
libs group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
instead of
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
Is there any difference between them?
libs, implementation, compile are known as dependency configurations (configurations for short) in Gradle: https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:what-are-dependency-configurations
They are essentially a "bucket" to place dependency in. The Java plugin defines quite a few configurations: https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_plugin_and_dependency_management
The libs configuration you mentioned is not part of any standard/provided Gradle plugin. So, it is provided/created by some other plugin you have applied to your project. Or you have defined the configuration yourself in your project somewhere.
The implementation configuration, provided by the Java plugin, are for implementation details of your application or library. In other words, these are "private" to your application/library and will not be available to consumers' classpath.

Gradle transient dependencies

I have a project "lib" with declares dependency on third party library (lets say, redis).
dependencies{
implementation group: 'redis.clients', name: 'jedis', version: '2.9.0'
}
I have another project, "application", which is declares dependency on "lib" project in very similar way.
The problem: unless I adding redis dependency to the "application" project as well, it fails at runtime due to missing redis dependency (despite that it itself doesn't make any direct use of redis).
I want to declare "redis" dependency inside "lib" project in such way, so "lib" will be already "bundled" with "redis" inside, so everyone using "lib" will have to declare only "lib" dependency.
How to do that?
Assuming you apply the java or java-library plugins, you should not have to do anything.
A dependency added to the implementation scope in Gradle is visible to consumers of that project for their runtime classpath. It is however not visible to the compile classpath.
In order to understand better what's happening, you can check the different classpath by running ./gradlew <project>:dependencies --configuration runtimeClasspath on both projects and see what is the output, of course replacing <project> with the project name or leaving empty for the root project.

How does Gradle handle redundant dependencies

If I have two dependencies in my build.gradle file that refer to group artifacts and both of these groups contain references to the same jar files, how does Gradle handle them? Does Gradle recognize the version difference between the individual jar files that make up the group and only select the one with the newest version when a compile is made or does it pick the one that is listed last in the build.gradle file? Example:
dependencies {
compile group: 'some-sdk-1', name: 'sdk1', version: '2.5'
compile group: 'some-sdk-2', name: 'sdk2', version: '1.0'
}
In this example, some-sdk-1 might contain a jar called lib1-1.0.jar
In some-sdk-2, the same library is present but has a different version. For example lib1-2.0.jar
Which jar file is used?
It depends on how the jar file is put inside the library (sdk).
You should manage the jar files as transitive dependencies.
It means that inside the pom file related to the sdk you have a dependency of the jar file.
In this case you can check the doc:
Gradle offers the following conflict resolution strategies:
Newest: The newest version of the dependency is used. This is Gradle’s default strategy, and is often an appropriate choice as long as versions are backwards-compatible.
Fail: A version conflict results in a build failure. This strategy requires all version conflicts to be resolved explicitly in the build script. See ResolutionStrategy for details on how to explicitly choose a particular version.
Instead if you are just putting the jar inside the sdk without using a transitive dependency (just the jar file inside):
If you don’t use transitive dependency management, version conflicts are undetected and the often accidental order of the classpath will determine what version of a dependency will win.

How does Gradle's configurations hierarchy work?

I know there are four basic configurations, compile, runtime, testCompile, and testRuntime. If I put in a dependency like this:
runtime group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.3'
This means this dependency is available under runtime and compile, correct? But what about testCompile and testRuntime? Is it available for these configurations as well? If I add my own configuration, do I have to specify where it exists in the hierarchy? What happens if I don't? The documentation didn't really make this clear.
The definition for those 4 configuration are as follow for the java plugin :
compile
The dependencies required to compile the production source of the project.
runtime
The dependencies required by the production classes at runtime. By default, also includes the compile time dependencies.
testCompile
The dependencies required to compile the test source of the project. By default, also includes the compiled production classes and the compile time dependencies.
testRuntime
The dependencies required to run the tests. By default, also includes the compile, runtime and test compile dependencies.
you can also check https://docs.gradle.org/current/userguide/java_plugin.html#tab:configurations, it has pretty graph and table:
When you declare a new configuration you can define what other configuration it extends, for example Gradle In Action takes the example with Geb, you would define new configuration as
configurations {
functTestCompile.extendsFrom testCompile
functTestRuntime.extendsFrom testRuntime
}
If you dont, you assume those configuration do not need to benefit from another one and its standalone, you will need to define all dependencies this configuration requires.

Gradle trump providedCompile's exclusion?

I've got a project that I want to use providedCompile with to avoid pulling it's libs. However, that prevents me from pulling in another project's libs that I do need. The docs say: If you don't want this transitive behavior, simply declare your provided dependencies, but it doesn't give an example of how to do this.
Here's basically what my dependencies look like:
dependencies {
compile(project(':common'))
providedCompile(project(':projA')) // <-- also depends on :common
}
My war file correctly excludes all of transitive libs from projA, but I need to trump that for the common.jar and I can't figure out how to make that happen. But the docs seem to indicate it's possible...
Edit: Here's a hacky configuration that seems to work. The combination of lines for "projA" gives me projA.jar as a dependency, but not its children. And since "common" is a compile dependency, but "projA" is only considered provided at runtime, I still get the common.jar due to the compile time dependency. I'm not sure it's supposed to work this way, but it generates the war I need.
dependencies {
compile(project(':projA')) { transitive = false }
providedRuntime(project(':projA')) { transitive = false }
compile(project(':common'))
}
If you don't want this transitive behavior, simply declare your provided dependencies
This means that if you don't want all dependencies of projA to be declared provided you need to list them as provided one by one.
From the Gradle 1.8 Userguide '26.4. Dependency management'
The War plugin adds two dependency configurations: providedCompile and
providedRuntime. Those configurations have the same scope as the
respective compile and runtime configurations, except that they are
not added to the WAR archive. It is important to note that those
provided configurations work transitively.
Let's say you add commons-httpclient:commons-httpclient:3.0 to any of
the provided configurations. This dependency has a dependency on
commons-codec. This means neither httpclient nor commons-codec is
added to your WAR, even if commons-codec were an explicit dependency
of your compile configuration. If you don't want this transitive
behavior, simply declare your provided dependencies like
commons-httpclient:commons-httpclient:3.0#jar.
There is another better solution that seems to be working. With transitive=false you exclude all the transitive dependencies that are then missing during compilation time and you have to declare all of them manually (again as provided) which is pain if they also include your desired library.
So for the providedCompile statement exclude not all transitive dependencies but only the one you want to include in war by separate compile statement.
Real example where I need the commons-codec included in the war file but that one is placed also in the keycloak-services and keycloak-model-jpa:
providedCompile ("org.keycloak:keycloak-model-jpa:6.0.1") {
exclude group: 'commons-codec', module: 'commons-codec'
}
providedCompile ("org.keycloak:keycloak-services:6.0.1") {
exclude group: 'commons-codec', module: 'commons-codec'
}
providedCompile "org.keycloak:keycloak-core:6.0.1"
providedCompile "org.keycloak:keycloak-server-spi:6.0.1"
providedCompile "org.keycloak:keycloak-server-spi-private:6.0.1"
compile "commons-codec:commons-codec:1.10"

Resources