fail gradle build if transitive dependency is used directly? - java

im looking for a way to fail my gradle build if my code directly uses (so imports) a transitive dependency.
what i mean is if my project has a (compile) dependency on module A, and module A depends on B (so B is in my transitive dependencies and available on my runtime classpath) and my code directly imports and uses classes from B, i want my build to fail.
here's a maven plugin that does what i want - https://github.com/Scout24/illegal-transitive-dependency-check - but i cant find a gradle one?

I think the 2 plugins below may be what you're looking for.
https://github.com/wfhartford/gradle-dependency-analyze
From the README:
This plugin attempts to replicate the functionality of the maven dependency plugin's analyze goals which fail the build if dependencies are declared but not used or used but not declared.
https://github.com/nebula-plugins/gradle-lint-plugin
On the Unused Dependency Rule wiki page:
Promotes transitive dependencies that are used directly by your code to explicit first order dependencies
I have to say I haven't used either one myself, but they seem to address your concern.

I think that you want to use the java library plugin and the api/implementation configurations.
For the legacy java plugin you could do
dependencies {
compile('group:module-b:1.0') { transitive = false }
}
This would force you to explicitly declare module-b's transitive dependencies if you need to use them.
Note: You'll probably get lots of ClassNotFoundException using module-b at runtime since there's now jars missing from your classpath. So this might be better
dependencies {
compile('group:module-b:1.0') { transitive = false }
runtime 'group:module-b:1.0'
}

Related

What scope should i apply for the libraries used in my jar ,?

I am developing an application which will be published to nexus repository used as a dependency by other applications.
I am using gradle for the build, and have applied the maven plugin.
When i do a gradle install, the dependency scope in the generated pom is automatically applied as 'runtime' for the libraries that i have added as dependencies.
I am using "implementation" for adding my dependencies.
What is the recommended scope for the libraries i my case ?
I see compile would have been useful , but is now deprecated. Should i use compileClasspath ?
Thanks !
The scope of the dependencies is controlled by the configuration you define them in in the dependencies { } block.
Gradle has great table with descriptions as to when you should use them.
Most commonly, you'll use these two:
implementation - This is where you should declare dependencies which are purely internal and not meant to be exposed to consumers.
api - This is where you should declare dependencies which are transitively exported to consumers, for compile.

How to find if I need to exclude dependencies in a maven java project?

I use both Intellij IDEA (2018.3.5) & Eclipse IDEs, but I prefer Intellij. I have a maven based Java project with multiple poms. I added some dependencies to one of the pom files. I need to find out if there are any dependency conflicts which could prevent the build from running when its deployed, and then exclude them. I tried the steps given below to find conflicts which could cause problems. Are they enough or do I need to do more ?
Check if there are any compile time dependency conflicts with mvn clean install -DskipTests. Build was successful with no errors.
Check if Intellij shows no problems under File > Project Structure > Problems. There are no problems.
I also saw the dependency tree with mvn dependency:tree -Dverbose. It has a lot of "omitted for duplicate" and "omitted for conflict with" items, but the build was successful. I don't see any errors though. Does this mean that everything is okay or do I have to do something more about these conflicts ?
The best way to tell if everything is fine with your application is to have good tests.
However normally one doesn't exclude transitive dependencies from project's <dependency> libraries. Doing it can potentially break the dependency in a subtle and hard to notice way. It's usually safer to remove the whole <dependency>.
There are few scenario when one should use <exclude>:
Dealing with incompatible transitive dependencies between different libraries e.g. A requires library C-1.0 but library B requires library C-2.0 while C-1.0 and C-2.0 can't coexist on the classpath.
Having transitive dependencies already provided by system e.g. deploying to Tomcat with additional JARs in the TOMCAT_HOME/lib directory.
If you decide to exclude a dependency it's important that you check the final artifact because sometimes plugins do weird things e.g. there were versions of maven-assembly-plugin affected by a bug that resulted in different dependencies being resolved during shaded JAR creation than maven-dependency-plugin used for compilation.

NoClassDefFoundError when Maven test-scoped dependency overrides transitive compile-scoped dependency

We have several deployables, and have extracted common code into several libraries. Deployable app A has a (default-scoped) Maven dependency on library B, and a test-scoped dependency on library C.
I made a change to library B, which used a method from, and so created a dependency on, library C. This caused app A to fail at run-time with NoClassDefFoundErrors. App A's unit tests all passed, of course, so we only found this in our staging environment. (Other apps either had no direct dependency on library C, or had a default-scoped dependency on it, and so continued to work.)
The obvious fix worked, of course: I edited A's pom.xml to make the dependency on C default scope.
My question is: Are we doing something wrong? Or is this how things are supposed to work? Is anyone aware of an automated way of detecting the potential problem at build-time and failing the build?
You are not doing something wrong. Maven made a peculiar design choice here. See also
Maven: test vs. transitive compile, especially Tunakis comment on my question.
and
Maven dependency within dependency with different scope
My advice: Eliminate C from A's pom.xml. Only add test dependencies if these dependencies are not already on the dependency tree.

Do transitive dependencies need to be on the compile-time path?

I am using Maven as my build tool for my java application, but this question might be applicable to other build tools.
I declared a dependency on A.jar in my pom.xml
A.jar also had a pom.xml and declared a dependency on B.jar
I used classes from B.jar in my code, and maven compiled my code just fine.
I'm confused because I would have expected to have to explicitly declare a dependency on B.jar in my pom.xml in order to use stuff from it.
Is this normal behavior for other build tools (adding transitive dependencies to the compile time path)?
Why do you need transitive dependencies in order to compile? I understand that they are needed when the code is packaged and/or at runtime. But I can't seem to think of a case where transitive dependencies are needed for compiling.
You should have a dependency if you have an import.
The mvn dependency:analyze can help.
i believe the thought is
...the jar is going to be packaged to be used at runtime ( by the dependency A.jar). So why not let the project have access to them.

Conditional dependencies

On Gradle (more specifically with Android) how can I have conditional dependency so that they get loaded either from the parent project like this:
compile project(':lib')
or from a repository is not present in the parent project:
compile 'com.sample:lib:+'
one possible solution I thought of is to do this:
compile allprojects.find({ it.name.equal('lib')}) != null ? project(':lib') : 'com.sample:lib:+'
but the allProjects property is not available in the dependencies task.
Update:
The reason I'm searching for this because we have two setups, one with our sub-projects imported from different git repositories at the development machines and the second one importing the dependencies from our dependencies server for CI.
An alternative I've tried is to set this in the parent project:
ext.projectNames = allprojects.collect{ it.name }
and in the subproject I'm doing this:
compile projectNames.contains('lib') ? project(':lib') : 'com.sample:lib:+'
This works, but however since in the current setup we run just one of the subproject in the CI server this property won't exist and will fail so may have to extract this behaviour to a plugin and maybe play with getParent()
I had a need for the same feature. Getting it to work with gradle was a cinch. The hard part was figuring out how Android Studio syncs the gradle files. Without a successful sync, the IDE will complain it can't find any dependencies. At any rate, I figured it out, here is my solution to make it work with gradle and Android Studio.
https://gist.github.com/vangorra/c1383c355ce8fe56adf8
It essentially boils down to defining the project in settings.gradle:
include 'library'
project(':library').projectDir = file('../Library/library')
Then you have to use a one-liner with options closure for your dependency:
compile ( project(':library').projectDir.exists() ? project(':library'): 'Library:library:unspecified#aar') {
transitive = true
}
Dynamically resolving to either project dependency or external dependency isn't yet a first-class Gradle feature, and it takes some effort (and may incur some limitations) to implement it in a build. You can find a proof-of-concept here: https://github.com/pniederw/elastic-deps
PS: It's allprojects, not allProjects.

Categories