Prevent dependency from being imported transitively in Gradle? - spring

Background
I am creating a library for internal use in Spring-Boot projects. The library I am writing, using Gradle, has an unavoidable dependency on log4j2 since it is a logging library. Here is the dependency in question:
implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.14.1'
Spring-Boot has its own implicit dependency on Logback. When importing my library into a Spring project, it creates the following error:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/alexandre.meddin/.gradle/caches/modules-2/files-2.1/org.apache.logging.log4j/log4j-slf4j-impl/2.14.1/9a40554b8dab7ac9606089c87ae8a5ba914ec932/log4j-slf4j-impl-2.14.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/alexandre.meddin/.gradle/caches/modules-2/files-2.1/ch.qos.logback/logback-classic/1.2.3/7c4f3c474fb2c041d8028740440937705ebb473a/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]
It is possible to subdue this error with the following Gradle configuration in my Spring project's build.gradle:
configurations.all {
resolutionStrategy.dependencySubstitution {
substitute module('org.apache.logging.log4j:log4j-slf4j-impl') using module('org.slf4j:slf4j-api:0.0.0')
}
}
Question
Is it possible to solve this conflict inside the library? That is, use the required dependency but not expose it to the importing project? This way the conflict will be solved for all future users of the library, instead of needing to be debugged anew every time someone unfamiliar uses it.

compileOnly
Use compileOnly instead of implementation. This would have been solved in a heartbeat if I'd read the documentation more closesly. Problem solved.
https://blog.gradle.org/introducing-compile-only-dependencies

Related

Protobuf marshallers not registering when using Gradle?

I have a project where I'm trying to get protobuf to work with infinispan, quarkus and gradle. The problem is that although I'm following the instructions given in the Quarkus homepage: https://quarkus.io/guides/infinispan-client, it seems that when I'm using gradle, the marshallers are not generated and registered as they should be. To me it seems like this comes down to the org.infinispan.protostream:protostream-processor not executing when gradle is the build tool. Is this a conscious decision that only Maven is supported, or am I missing something obvious in my gradle setup?
Replications of the most simple cases can be found here: https://github.com/radiosphere/gradle-java-protobuf for gradle and here for maven: https://github.com/radiosphere/mvn-java-protobuf. These projects are extremely basic, basically trying to run a simple code on startup:
public void onStartup(#Observes StartupEvent startupEvent) {
RemoteCache<String, CounterState> cache = cacheManager.administration().getOrCreateCache("default", DefaultTemplate.DIST_SYNC);
cache.put("a", new CounterState(2L));
CounterState state = cache.get("a");
logger.infof("State: %s", state);
}
In the maven project this works, in the gradle project an exception is thrown saying that no marshaller can be found. Apart from build tool choice the projects should be identical.
The annotation processor runs in the Maven build because io.quarkus:quarkus-infinispan-client has a compile dependency on org.infinispan.protostream:protostream-processor.
Looks like Gradle made a decision to not use annotation processors found in the compile classpath:
Since implementation details matter for annotation processors, they must be declared separately on the annotation processor path. Gradle ignores annotation processors on the compile classpath.
That means you have to add an explicit annotationProcessor dependency:
annotationProcessor 'org.infinispan.protostream:protostream-processor:4.4.0.Final'

The transitive reference don't works in gradle when use implementation project

I have a Core project where it has dependencies to other library.
Core build.gradle
dependencies {
implementation 'com.jakewharton:butterknife:9.0.0-rc2'
annotationProcessor 'com.jakewharton:butterknife-compiler:9.0.0-rc2'
}
And I have other User project that reference to Core.
When the User project, does reference to Core library:
User build.gradle
dependencies {
implementation 'com.example: core: 1.0'
}
In User project I can acces at butterknife classes but when I reference at Core project
User build.gradle
dependecies{
implementation project(":core")
}
I can't access to butterknife classes (Compilation failed; see the compiler error output for details.)
Is there any way to reference core and be able to use the dependencies referenced by core?
This is the way implementation configuration works.
The plugin exposes two configurations that can be used to declare dependencies: api and implementation. The api configuration should be used to declare dependencies which are exported by the library API, whereas the implementation configuration should be used to declare dependencies which are internal to the component.
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.
Either declare Butter Knife as an api dependency, or depend on it explicitly in user/build.gradle.

IntelliJ IDEA with Kotlin cannot access class required by another module

I'm creating a new Kotlin project with multiple modules and there are issues when I try to run one of the modules from IntelliJ IDEA.
To leave the structure simple, I got module A and module B.
module B configures and returns log4j2 Logger instance.
module A is calling module B method to get logger and uses Logger directly after this.
When I create a fat jar and launch methods from module A everything works fine and logging is working but when I try to launch module A method from IntelliJ IDEA I'm receiving an error from Kotlin:
Error:(6, 29) Kotlin: Cannot access class 'org.apache.logging.log4j.Logger'. Check your module classpath for missing or conflicting dependencies
Both modules are configured using Gradle and module A got module B as dependency:
dependencies {
implementation(project(":moduleB"))
}
From my understanding, IDEA should be able to understand the requirements of module B and load them automatically when executing the code that uses module B, no?
Am I missing something?
The problem turned out to be with log4j declaration in module B.
Initially, it was declared as:
implementation("org.apache.logging.log4j:log4j-api:2.11.1")
So, this dependency was not exposed to module A.
After changing to:
compile("org.apache.logging.log4j:log4j-api:2.11.1")
No more error is produced.
Strange thing is that using api configuration instead of compile doesn't work (though, according to documentation, these are the same configurations). Looks like current IDEA Gradle plugin duoesn't support new configuration naming.

prevent my library from 'exporting' a dependency in maven, but still have it in runtime classpath

My library, call it my-util-lib, uses slf4j as the logging facade. I need a concrete slf4j binding at runtime when I use my library directly, but I don't want the binding jar to be 'inherited' when an application imports my-util-lib. I know I can declare the dependency scope as 'test' in the POM of my-util-lib to accomplish this, but then I cannot run my library directly, other than in JUnit tests. Like if I want to run a main() method from one of my util classes for some quick-n-dirty purpose or quick test.
Is there a way to accomplish this? Or to force the scope of a run to 'test' when not running a JUnit test?
Set
<scope>provided</scope
on the <dependency> to slf4j.
For a discussion of the values of scope, see this question: What's the difference between these Maven dependency scopes: provided/compile/system/

HBase 0.92 warnings about SLF4J bindings

I installed HBase 0.92 on Hadoop 1.0.0 and it works fine in full-distributed mode, but an annoying warning keeps appearing. How can I get rid of it?
.......
hbase(main):001:0> status
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in
[jar:file:/opt/hbase-0.92.0/lib/slf4j-log4j12-1.5.8.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in
[jar:file:/opt/hadoop-1.0.0/lib/slf4j-log4j12-1.4.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
: See http://www.slf4j.org/codes.html#multiple_bindings for an
explanation.
3 servers, 0 dead, 0.6667 average load
.......
P.S. I did not set the $CLASSPATH variable in hbase-env.sh. I run Hadoop with start-all.sh, then start HBase with start-hbase.sh.
I removed the slf4j-log4j12-1.5.8.jar in ${hase}/lib/ then the warning never shown again. It should be due to a duplicated class loaded, both hadoop and hbase use the same jar in the same jvm.
You can give it a try.
The warning emitted by SLF4J is just that, a warning. Even when multiple bindings are present, SLF4J will pick one logging framework/implementation and bind with it. The way SLF4J picks a binding is determined by the JVM and for all practical purposes should be considered random. As of version 1.6.6, SLF4J will name the framework/implementation class it is actually bound to.
Embedded components such as libraries or frameworks should not declare a dependency on any SLF4J binding but only depend on slf4j-api. When a library declares a compile-time dependency on a SLF4J binding, it imposes that binding on the end-user, thus negating SLF4J's purpose. When you come across an embedded component declaring a compile-time dependency on any SLF4J binding, please take the time to contact the authors of said component/library and kindly ask them to mend their ways.

Resources