Maven and Gradle nested dependencies - maven

I have a library A that uses a library B. These two libraries are then used by application C.
Both library A and B can be found in a maven repository.
I have tried to add B as a dependency to A by adding it into A's POM file.
I'm not sure if this is the correct approach or there is a standard way to do this.
I am looking for either the standard way of doing this or a reference guide to point me into the right direction.
Please let me know if there is any other information I can provide.

The term for such relationship is called a transitive dependency. In your application, you define just the direct dependencies, the transitive ones are handled by a particular build system (Gradle, Maven, Ant + Ivy).
For example, considering following Gradle build script:
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.0.2'
}
You can list this project dependencies with the command:
$ gradle dependencies
This should provide result similar to (depends on the Gradle version, this one is 4.4.1):
testCompileClasspath - Compile classpath for source set 'test'.
\--- org.junit.jupiter:junit-jupiter-api:5.0.2
+--- org.opentest4j:opentest4j:1.0.0
\--- org.junit.platform:junit-platform-commons:1.0.2
Therefore, opentest4j and junit-platform-commons are transitive dependencies of the junit-jupiter-api library, which is the only direct dependency of the project.
It's equivalent for Maven. E.g. you can list Maven dependencies with:
$ mvn dependency:tree

Related

Gradle does not display all plugins in dependency tree

Use Case
I am trying to generate a dependency tree containing all plugins and dependencies for all configurations, but org.sonarqube is not included in the tree. I am working with a basic, single-module project and am using Gradle v7.5.1.
Examples
Running the following command outputs most (but not all) dependencies and plugins.
gradlew dependencies > dependency-tree.txt
Specify SonarQube plugin within build.gradle
plugins {
id 'org.sonarqube' version '3.2.0'
}
Specify SonarQube plugin within settings.gradle
buildscript {
repositories {
maven {
url "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.2.0"
}
}
apply plugin: "org.sonarqube" // set in build.gradle, not in settings.gradle
Results
Neither approach includes the org.sonarqube plugin in the dependency graph. Is there a way to get this plugin to show up in the generated dependency tree? If yes, what changes need to be made?
The dependencies task provides the projet dependencies in each configuration, this does not include the projet script classpath (plugins).
You have another similar task available, buildEnvironment (see BuildEnvironmentReportTask) which will list the project build script dependencies.
you could combine both tasks outputs if you need a aggredated report of all project/plugin dependencies
> Task :buildEnvironment
------------------------------------------------------------
Root project 'demo'
------------------------------------------------------------
classpath
\--- org.sonarqube:org.sonarqube.gradle.plugin:3.2.0
\--- org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.2.0
\--- org.sonarsource.scanner.api:sonar-scanner-api:2.16.1.361
A web-based, searchable dependency report is available by adding the --scan option.
BUILD SUCCESSFUL in 452ms

Gradle plugins' transitive dependencies interfering and breaking the build

I am using Gradle 6.1 in a multimodule project. I am also using two plugins: kotlin("jvm") and id("com.google.cloud.tools.jib"), and they are loaded in the following modules:
root/
build.gradle.kts loads kotlin("jvm")
services/
my-service/
rest/
build.gradle.kts loads id("com.google.cloud.tools.jib")
(There are more modules, files etc. but these are the relevant ones.)
The build fails:
$ ./gradlew clean jibDockerBuild
...
* What went wrong:
Execution failed for task ':services:driver:rest:jibDockerBuild'.
> com.google.cloud.tools.jib.plugins.common.BuildStepsExecutionException: 'org.apache.http.client.config.RequestConfig$Builder
org.apache.http.client.config.RequestConfig$Builder.setNormalizeUri(boolean)'
I identified the issue: both the Kotlin and JIB plugins have a transitive dependency on org.apache.httpcomponents:httpclient: Kotlin requires 4.5.3 and JIB 4.5.10. The problem is, in this project setup only 4.5.3 is loaded, and JIB fails as the new method is not available. This can be checked with ./gradlew buildEnv.
I've found a workaround, I need to load both plugins at the root level (which one is first seems to be irrelevant) in the main Gradle file; now ./gradlew buildEnv shows that the higher dependency version is used, also for Kotlin (output shortened and incomplete):
classpath
+--- org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:1.3.61
| \--- org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61
| +--- de.undercouch:gradle-download-task:3.4.3
| | \--- org.apache.httpcomponents:httpclient:4.5.3 -> 4.5.10
It works in this case, but it could be that the new library version breaks the Kotlin plugin. The problem is that the plugins and their dependencies are on the classpath without separation, something that was normal on Java before Jigsaw etc. Is there any way for Gradle to be able to separate the dependencies so that each plugin uses exactly the version it declares? I am building on Java 11, so the module system could be utilized, but does Gradle have an option to turn it on?
EDIT: updating to Kotlin 1.3.70 also fixes the issue as it doesn't depend on the library any longer. The general question is still valid, though.
Is there any way for Gradle to be able to separate the dependencies so that each plugin uses exactly the version it declares
No.
All plugins share the same build script configuration: classpath
It follows the same dependency resolution that application dependencies follow. So you can enforce that for this particular dependency only use a specific version always:
buildscript {
configurations {
classpath {
resolutionStrategy {
force("org.apache.httpcomponents:httpclient:4.5.10")
}
}
}
}
That's just one of many ways you can take control of dependency resolution for build script dependencies. You could also use a platform to advise on the dependency versions:
buildscript {
dependencies {
classpath(enforcedPlatform("org.springframework.boot:spring-boot-dependencies:2.2.5.RELEASE"))
}
}
Refer to the docs for more info:
https://docs.gradle.org/current/userguide/resolution_rules.html
https://docs.gradle.org/current/userguide/platforms.html

Why implementation gradle dependecies are exposed to consumers

The java-library plugin documentation says:
Dependencies appearing in the api configurations will be transitively exposed to consumers of the library, and as such will appear on the compile classpath of consumers.
Dependencies found in the implementation configuration will, on the other hand, not be exposed to consumers, and therefore not leak into the consumers' compile classpath
However, it doesn't work for me. I see that the implementation dependencies are exposed to consumers too.
Here one example:
project_a -> build.gradle
...
dependencies {
// Dependency supposedly not exposed to consumers in their own classpath compilation
implementation 'com.google.guava:guava:23.0'
}
...
project_b -> build.gradle
...
dependencies {
implementation 'my-company:project_a:1.0'
}
...
I expected that guava doesn't appear in the project_b' classpath. However I see guava and all their dependencies on the project_b compile classpath.
project_b>> gradlew dependencies:
...
compileClasspath - Compile classpath for source set 'main'.
\--- my-company:project_a:1.0
\--- com.google.guava:guava:23.0
+--- com.google.code.findbugs:jsr305:1.3.9
+--- com.google.errorprone:error_prone_annotations:2.0.18
+--- com.google.j2objc:j2objc-annotations:1.1
\--- org.codehaus.mojo:animal-sniffer-annotations:1.14
...
The reason you do not experience this behaviour is because your dependency goes through a Maven POM.
The separation of the Maven compile and runtime scopes is only supported as of Gradle 4.6 and is currently behind a feature flag.
In short, if you add the following to settings.gradle of project_b, you will get the expected behaviour:
enableFeaturePreview("IMPROVED_POM_SUPPORT")
I believe the documentation refers to multi-project builds, and that a consumer is considered to be a project (not a jar) that depends on another project in the build.
For example, consider this project_b/build.gradle:
dependencies {
implementation project(':project_a')
}
With this (and the appropriate settings.gradle), I can observe the compile classpath changes as the doc describes (for use of api versus implementation). I have placed a working example here.

gradle earlib not pulling transitive libraries

I got gradle 3.5.1 and using ear plugin. The documentation says that deploy configuration is not transitive but earlib actually is (https://docs.gradle.org/3.3/userguide/ear_plugin.html). My configuration is a below
dependencies {
earlib(
"org.mybatis:mybatis:3.2.8"
)
}
It was supposed to get a few other transitive libraries but here is all I get when I run gradle dependencies
earlib - Classpath for module dependencies.
\--- org.mybatis:mybatis:3.2.8
What am I doing wrong here?
Actually, you are doing nothing wrong. Your module dependency org.mybatis:mybatis:3.2.8 simply does not define any (mandatory) transitive dependency, since every compile or provided dependency is marked as optional.
According to the Maven docs,
If a user wants to use functionality related to an optional dependency, they will have to redeclare that optional dependency in their own project.

How do I define a simple Gradle project with only a single jar file?

I have a Gradle project that depends on an external jar file. Currently I'm defining the dependency like this:
dependencies {
compile files('/path/to/my/jar/library.jar')
}
However I want to include it as a project dependency instead, like this:
dependencies {
compile project(':whatGoesHere?')
}
I assume I need to define a new Gradle project that contains the jar file but I don't know how to do this. I'm wondering about things like:
Do I just need to create a new build.gradle or are there more steps?
What would go in the build.gradle file?
Assume the new project contains nothing but the jar file (since it does). Also assume I know almost nothing about Gradle (because I don't!).
P.S. If it matters, this is an Android Gradle project.
As a roundup for our discussion, I'll bring simple example of "build.gradle" file, using maven local and maven central repositories:
apply plugin: 'maven'
apply plugin: 'java'
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile 'commons-io:commons-io:2.4'
testCompile 'junit:junit:4.11'
}
Explanation:
"apply plugin: 'maven'" enables maven plugin, which is needed for dependency download.
"apply plugin: 'java'" enables java compilation tasks for your project.
"repositories" declares one or more repositories (maven or ivy), from where artifacts (jar libraries) will be downloaded.
"mavenLocal" refers to so-called local maven repository, which is located in "~/.m2/repository" folder on your computer. local maven repository effectively caches external repositories, but it also allows installation of local-only artifacts.
"mavenCentral" refers to maven central.
"dependencies" lists your project dependencies, either other projects or artifacts (jars).
"compile" is a configuration supported by "java" and "groovy" plugins, it tells gradle: "add these libraries to the classpath of the application during compilation phase".
"testCompile" is another configuration supported by "java" and "groovy" plugins, it tells gradle: "add these libraries to the classpath of the application during test phase".
'commons-io:commons-io:2.4' is "coordinates" of the artifact within maven repository, in form group:name:version.
You can search for well-known java libraries at address: http://mvnrepository.com/ and then include their coordinates in "build.gradle". You don't need to download anything - gradle does it for you automatically.

Resources