What's the difference between libs and implementation in Gradle? - 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.

Related

spring boot plugin and gradle dependency implementation

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.

What are Gradle dependency configurations?

Could somebody please explain in more details (preferably with some real-life examples) what dependency configurations are in Gradle?
For example, let's assume I have:
compile group: 'org.foo', name: 'commons-lang', version: '1.2.3', configuration: 'runtime'
testCompile group: 'org.foo', name: 'commons-io', version: '2.4.1', configuration: 'testing'
I was under the impression that compile and testCompile are the configurations. If so, then what's the configuration: 'testing' part for?
These are build configuration names of artifacts... they act just alike when passing a build configuration name to a referenced project, except that these packages always carry the one configuration name they were built with - while projects may have several, resulting in several artifacts.

How to include a module's jar as a dependency in another module in Gradle?

I have 2 modules, let's call them A, B.
module A .gradle is
dependencies {
compileInclude group: "Service1", name: "Service1", version: "$rootProject.ext.Service1"
compileInclude group: "Service2", name: "Service2", version: "$rootProject.ext.Service2"
compileInclude group: "Service3", name: "Service3", version: "$rootProject.ext.Service3"
}
module B .gradle is
dependencies {
compileOnly project(":modules:ModuleA")
}
I would like to make use of module A's Service1.Jar in module B, without having to remake another one in module B.
is this possible?
What you ask is about transitive dependency management.
Gradle supports that out of the box when using the java-library plugin:
Anything placed in the api configuration is exposed transitively to the compile classpath of consumers,
Anything placed in the implementation configuration is only exposed to the runtime classpath of consumers.
Now, it is also considered best practice to promote to first level dependencies anything that a module requires for compilation. This prevents dependencies from disappearing if the module that brought it transitively no longer does so.
I have no idea however where the compileInclude configuration comes from and how it participates into what the module exposes or not.
And you should no longer use compile but instead implementation in module B.

"Unresolved requirement: Import-Package" for a module not in my build.gradle

I want to use Elasticsearch's Client Java class within a Liferay 7 SP4 FP30 module, so I wrote this build.gradle:
dependencies {
compileOnly group: "com.liferay", name: "com.liferay.portal.search.elasticsearch", version: "2.1.14"
compileOnly group: "com.liferay", name: "org.elasticsearch", version: "2.2.0.LIFERAY-PATCHED-1"
compileOnly group: "biz.aQute.bnd", name: "biz.aQute.bndlib", version: "3.1.0"
compileOnly group: "com.liferay", name: "com.liferay.osgi.util", version: "3.0.0"
compileOnly group: "com.liferay", name: "com.liferay.portal.spring.extender", version: "2.0.0"
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel", version: "2.0.0"
compileOnly group: "com.liferay", name: "com.liferay.portal.security.audit.api", version: "2.0.0"
compileOnly group: "com.liferay", name: "com.liferay.portal.configuration.metatype", version: "2.0.0"
compileOnly group: "org.osgi", name: "org.osgi.compendium", version: "5.0.0"
}
... and a Java class containing code such as import com.liferay.portal.search.elasticsearch.connection.ElasticsearchConnectionManager; and Client client = elasticsearchConnectionManager.getClient();
It builds fine.
But when I try to start the module, this error happens:
org.osgi.framework.BundleException: Could not resolve module: mymodule [548]
Unresolved requirement: Import-Package: com.liferay.portal.search.elasticsearch.connection
Why is this happening? My build.gradle does not mention this module ending in .connection, and Maven does not seem to have any such module.
#gjoranv is correct, just because you in is on your gradle.build it does not mean it will be in your environment.
First things first, the error is due to the lack of a used package, in Java's conventional sense. So you will need a module, as represented by a jar file, that makes this package public.
As liferay is pretty version dependent when it comes to Elastic Search, and relies on accident versions, you might get away with using not exposed packages, and forcing the exposure, normally through a Uber module.
If you are feeling lucky, you can also use compileInclude, instead of compileOnly. Including the library this way will possibly make a mess, as it will embed the jar inside your jar and expose all packages.
Another possibility, which normally is way less aggressive is to embed the jar, and set the classpath inside your bundle. To do this you just need to declare your dependency as compile, and add the classpath in your bnd.bnd file. (it sounds harder than it is, it should be a trivial process)
Another issue to have in mind is the alignment with your ElasticSearch and you liferay deployment:2.2-2.4.x but this is just because you might fall into class conversion exceptions and API mismatch if your objects are used by other bundles or when interfacing with an old ES.
Embedding example:
gradle.build
compile "org.apache.httpcomponents:httpclient"
compile "org.apache.httpcomponents:httpcore"
bnd.bnd
-includeresource: lib/httpclient.jar=httpclient-4.5.3.jar,\
lib/httpcore.jar=httpcore-4.4.6.jar
Bundle-ClassPath: ., lib/httpclient.jar, lib/httpcore.jar
I'm not familiar with Liferay and gradle, but I've been working with OSGi (apache felix) and maven for a long time. The error message indicates that your bundle uses the package com.liferay.portal.search.elasticsearch.connection, but the runtime environment does not have a bundle that exports that package. The package in question is contained in the first dependency mentioned in your build.gradle, but it's not exported. If you like, you can open the bundle jar and peek into its manifest.mf by downloading it from the maven central repo.
Since the package is not exported (only com.liferay.portal.search.elasticsearch.settings is), I assume it's a signal that it's not intended for external use. So maybe you should check if there's another way of doing what you want.
From looking at the Liferay docs for using 3rd party libraries, it seems you are trying to expand the library into your module. Maybe you could try the embedding strategy instead, if you still need to use the .connection package.

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.

Resources