Gradle dependency platform in parent module - java

I have a gradle project with a lot of subprojects and I want a BOM file to apply to all the subprojects.
I tried to put this in some subproject and it works fine:
dependencies{
implementation enforcedPlatform('group:bom-artifact:version')
}
But when I put it a parent gradle.build, or wrap it like:
allprojects {
dependencies {
implementation enforcedPlatform('group:bom-artifact:version')
}
}
It ends with error:
> Could not find method implementation() for arguments [DefaultExternalModuleDependency{group='group', name='bom-artifact', version='version', configuration='default'}] on object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler.
Can't figure out what's wrong. How to fix this? Or is there a better way to apply one BOM to all subprojects and manage it from one place?

I don't think the issue is the platform itself here
The message you're getting usually appears if you have not (yet) applied the Java plugin.
Gradle's configuration scopes like implementation, api and compileOnly are initialized as part of the Java plugin's init phase.
So depending on the structure of your subproject you might have one or more subprojects that don't use the Java plugin that does not recorgnize the scope. I'm not quite sure about the execution order between subprojects, this might play also play a role.
A simple solution would be to apply the plugin also into the allprojects closure, like
allprojects {
apply plugin: 'java'
dependencies {
implementation enforcedPlatform('group:bom-artifact:version')
}
}

Related

using gradle plugin from flatDir source

How to amend Gradle plugins {} management repository for custom plugins? is not duplicated in this post, because it does not cover use of flatDir.
Question
How do I use a Gradle plugin defined in a local JAR, using the new plugin {} semantics, instead of the deprecated apply() semantics?
Current Status
Not having any resolution, after posting the question and searching at considerable length, I filed an issue, wondering whether this use, which ought to be common and straightforward, is unsupported, either by design or oversight, within Gradle's revised plugin semantics.
Unfortunately, my report was closed, with no useful information provided.
I requested clarification in a new issue, but am still waiting.
I am frustrated, having expected that the community would be interested in at least discussing this problem.
If you can contribute information, please do so.
First Update
Following the clarification about the new style for configuring plugin sources, I updated my settings.gradle file to open with the following block. However, I regret that I see no improvement by this change alone. (For the plugin id field referenced in the build.gradle file, I have tried both the global ID published in the JAR metadata, and the basename of the JAR fie. Both fail equally.)
pluginManagement {
repositories {
gradlePluginPortal()
jcenter()
flatDir {
dirs 'lib`'
}
}
}
The documentation explains how to use custom repositories, but appears to overlook the case of a trivial flat directory.
Second Update
I get some improvement if I add a version number to the JAR file and to the corresponding statement in the plugins {} block. In this case, the message becomes:
Plugin [id: 'plugin-id', version: '1.0.0'] was not found in any of the following sources:
- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Plugin Repositories (could not resolve plugin artifact 'plugin-id:plugin-id.gradle.plugin:1.0.0')
Searched in the following repositories:
Gradle Central Plugin Repository
BintrayJCenter
flatDir(/absolute/path/to/lib)
In this case, the directory is added to the list of sources searched.
It is strange that the .gradle.plugin suffix is being appended to my ID in the printed artifact. It is also strange that adding the version number to what is being searched for affects the list of places being searched.
So my project still cannot build. I appreciate any further help.
Original Background
I placed a JAR file containing a custom plugin definition in the lib directory of a project. With the build.gradle build file as below, the build runs successfully.
buildscript {
repositories {
flatDir {
dirs 'lib'
}
}
}
apply plugin: 'plugin-id'
However, the apply() semantics are deprecated, favoring a plugins {} block, so I tried updating the build file as below.
plugins {
id 'plugin-id'
}
repositories {
flatDir {
dirs 'lib'
}
}
I understand that the plugins {} contents can draw from the repositories {} definitions.
However, the change creates a failure:
* What went wrong:
Plugin [id: 'plugin-id'] was not found in any of the following sources:
- Gradle Core Plugins (plugin is not in 'org.gradle' namespace)
- Plugin Repositories (plugin dependency must include a version number for this source)
Keeping the plugin {} block but moving the repositories {} block back into a leading buildscript {} block does not resolve the error. That is, the error persists even if I revert to the earlier version only replacing the apply() statement with the plugin {} block.
Finally, it has no effect to add to the repositories {} block a dependencies { classpath: ':jarname' } block, which some sources suggest is necessary, though I don't know why it would be.
This works. Tested with gradle 6.3.
build.gradle:
plugins {
id 'plugin-id'
}
settings.gradle:
pluginManagement {
buildscript {
repositories {
flatDir {
dirs '/plugin-folder/build/libs'
}
}
dependencies {
classpath ':plugin-jar:0.0.1'
}
}
}
Update: I just found out today that it is possible to have your plugin jar resolved without using the dependencies block above. In that case you should name your plugin jar as [plugin-id].gradle.plugin[-version].jar. Note that the [-version] part is optional and plugin-id.gradle.plugin.jar will also work.
NB: Flat dir repositories are discouraged and local maven repo folder should be used instead. Especially in the case when you want to override locally an artifact which exists on a remote repo. See https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:flat_dir_resolver. It seems impossible to fully move away from maven in favour of gradle, considering that installing local artifacts in a maven repo folder is not supported by gradle itself.

Resolving gradle plugin dependency conflicts

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.

Execute commons-math Java class from a gradle task

I need to execute from a gradle task classes included on a jar file.
For instance I would like to create a gradle task able to execute the class FastMath(http://commons.apache.org/proper/commons-math/download_math.cgi).
The current build.gradle script is the following:
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile files('/pathToJars/commons-math3-3.6.1.jar')
}
task t1 {
doLast {
println FastMath.abs(3)
}
}
I get this error message:
What went wrong:
Execution failed for task ':t1'.
Could not get unknown property 'FastMath' for task ':t1' of type org.gradle.api.DefaultTask.
I understand I am missing the class import, for instance adding the following statement but I do not know how or where:
import org.apache.commons.math3.util.FastMath;
I am wondering what I am doing wrong or how the script has to be configured. Any suggestion will be appreciated.
EDIT 1: The code that actually works is the following:
apply plugin:'java'
import org.apache.commons.math3.util.FastMath;
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'org.apache.commons:commons-math3:3.6.1'
}
}
task t1 {
doLast {
println FastMath.abs(3)
}
}
Well, you have multiple issues in your buildscript.
If you use mavenCentral() as repository, use the dependencies from there, you will gain transitive dependency resolution and automatic conflict resolution and you do not have to keep the libs in your VCS or wherever, so compile files('/pathToJars/commons-math3-3.6.1.jar') should actually be compile 'org.apache.commons:commons-math3:3.6.1'.
As you correctly noted, you either have to use fully-qualified class names or imports, so either, replace println FastMath.abs(3) by println org.apache.commons.math3.util.FastMath.abs(3), or add import org.apache.commons.math3.util.FastMath anywhere before. Typically this is done top-most in a file, just like for Java too.
Even if you would do both, it would not work, because you add the dependency to the compile classpath of your actual project. Instead you need it in the classpath of your buildscript, so you need to move the dependencies block inside a buildscript block and if you followed advice 1, then of course also the repositories block. You also need classpath instead of compile in the dependency declaration.

Switching dependencies in Gradle depending on platform

I am trying to get Gradle to select different dependencies in my multiproject build based on whether I am building for desktop or for Android. I have a common subproject (a library) I am trying to reuse. However, I cannot get Gradle to correctly switch dependency configurations.
My main settings.gradle simply includes all the dependencies:
// /settings.gradle
rootProject.name = 'myProject'
include 'androidUI'
include 'reusableLibrary'
include 'desktopUI'
Now both androidUI and desktopUI specify reusableLibrary as a dependency:
// /androidUI/build.gradle and /desktopUI/build.gradle
apply plugin: 'java'
dependencies {
compile project(path: ':reusableLibrary', configuration: 'desktop')
}
reusableLibrary itself specifies two configurations, because its dependencies are different whether it's building on desktop or Android:
// /reusableLibrary/build.gradle
apply plugin: 'java'
configurations {
desktop {
extendsFrom compile
}
android {
extendsFrom compile
}
}
dependencies {
// Just examples, the real list is longer.
// The point is that h2database is only included on desktop,
// and ormlite is only included on Android.
android 'com.j256.ormlite:ormlite-jdbc:5.0'
desktop 'com.h2database:h2:1.4.192'
}
This looks fine to me. But when I compile either desktopUI or androidUI, I can see that although the dependencies of reusableLibrary are being included on the classpath in the manner I desire, the actual JAR provided by reusableLibrary itself is not included. This of course causes the build to fail. I suspect I'm not setting up reusableLibrary correctly; I'm not clear on what the configurations {} blocks do.
Why aren't the compiled items in reusableLibrary being included on the classpaths of the UI projects? And what is the canonical way to include platform-specific dependencies in this manner?
The original configuration is pretty close to right. The key is to understand this dependency graph from the Gradle Java plugin's documentation:
This is a visualization of the Java plugin's various dependency configurations, which is Gradle-ese for "list of dependencies." When you add compile lines to a dependencies {...} block, you're adding Dependency elements to the compile dependency list.
The default dependency configuration is special; it is the one included by a compile project("path") line unless a different one is chosen with the configuration: argument. This means that when you build the library, the runtime dependency list (which includes the compiled jar from the library itself) is added to the classpath of the client project.
The original configuration creates two new nodes, desktop and android in this graph, and couples them both to compile by using extendsFrom. They are not otherwise connected to the graph! Now the problem with the original configuration is apparent: by switching the upstream project to either of these, it is missing the compiled code from runtime. This explains the classpath omission.
The solution is a bit more subtle than just aiming desktop and android at runtime. In order to ensure that everything is correctly decoupled when we add tests, we need one extra layer of dependency configurations to keep testCompile from indirectly depending on runtime. Additionally, the library's source code itself may need things on its classpath just to typecheck; we can use compileOnly for this. The end solution looks like this:
configurations {
desktopCompile
androidCompile
compileOnly.extendsFrom desktopCompile
testCompile.extendsFrom desktopCompile // Assuming tests run on the desktop
desktop {
extendsFrom desktopCompile
extendsFrom runtime
}
android {
extendsFrom androidCompile
extendsFrom runtime
}
}
dependencies {
androidCompile "some.android:dependency"
desktopCompile "other.desktop:dependency"
}

Unable to resolve generated classes

I'm using Dagger 2 with Gradle and have everything setup and code generation is working properly.
My build.gradle:
task wrapper(type: Wrapper) {
gradleVersion = '2.11'
}
subprojects {
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
... omitted as irrelevant to question ...
compile 'com.google.dagger:dagger:2.0.2'
compile 'com.google.dagger:dagger-compiler:2.0.2'
compile 'javax.inject:javax.inject:1'
}
}
My problem is that I am unable to resolve the classes and use them in my source, any solutions I've found are targeted towards Android which I am not using. How would I be able to resolve these generated classes as dependencies?
I had a similar issue some time ago. In your case I would say that you need the apt plugin. Check this question where I explained how I resolved it
I fixed this issue by enabled annotation processing in my IDE as well as adding Dr. Pelocho's answer.
Apply this answer: https://stackoverflow.com/a/33445767/1787084 to your build.gradle
Add apply plugin: 'eclipse' in your build.gradle
Enable annotation processing to the apt directory created by the ltgt gradle apt plugin in Eclipse by navigating to project properties -> Java compiler -> Annotation processing -> Enable project specific settings -> Enable annotation processing
Change generated source directory to build/generated/source/apt/main to match the ltgt default directory
Click "OK" or "Apply"
This added the Dagger generated classes to my build path and classpath

Categories