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.
Related
I have projects with dependencies shown below:
WebApp > Level1 Dependency > Level2 dependency
Lets say that
WebApp code uses only classes in Level1 dependency jar
Both level1 and level2 dependencies are included with compile scope.
Level1 jar in local repository. I deleted Level2 from local repository.
mvn compile fails with error
Could not find artifact com.example:mydeplevel2:jar:1.0-SNAPSHOT
I understand that jar/war packaging plugins need to traverse the whole hierarchy to be able to determine the transitive dependencies.
But why does compiler plugin error out? After all, even if all level2 dependencies were satisfied, I only see MyWebApp.class built in target folder. I could build MyWebApp.class with javac -cp MyWebApp.java.
You state that, for your project:
WebApp code uses only classes in Level1 dependency jar
Maven doesn't make that assumption, and it doesn't hold for many real projects. Maven ensures the transitive closure of the dependencies are present when compiling, and doesn't attempt to identify any unnecessary dependencies.
mvn dependency:analyze is a good way to confirm whether or not all direct dependencies are listed, but Maven doesn't assume that your project passes this check.
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.
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'
}
I am not very much experienced with Maven and it's compilation and packaging logic gets me confused.
I have some dependencies declares as :
<dependency>
<groupId>com.dependency_group</groupId>
<artifactId>dependency_1</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.dependency_group</groupId>
<artifactId>dependency_2</artifactId>
<version>1.0.0</version>
<scope>provided</scope>
</dependency>
So as far as I understand, dependency_1 will be added to the classpath of my program as something that comes along with my jar, and dependency_2 on the other hand, will be added to the classpath as something that the system runtime will provide upon deployment.
Then I run the package goal of Maven and none of my dependencies are packed with my code (I am using the shade plugin, but even without it nothing changes).
I expected that when some dependency is set as compile scope, it will be exported with my compiled code, since AFAICS, there's no point in setting the classpath saying a dependency will come along with my code, and Maven just don't package that dependency with it. It looks to me as if Maven is not obeying it's contract.
So:
1 - What's the logic behind this?
2 - Do I have to always use the Assembly plugin?
3 - Are there cases where people will define a dependency as compile and will not want it packaged within a jar?
Let me shed some light on the main point here. There are fundamentally two kinds of java artifacts:
Applications, i.e. ears, wars, executable jars
Libraries, i.e. jars that are meant to be used as dependencies for other artifacts.
For Applications your reasoning makes perfectly sense. Wars and Ears automatically package all their compile dependencies and you need no assembly plugin for that. For libraries, you do not pack the dependencies into the library. Maven handles transitive dependency resolution and would be confused if you put a fat jar on the classpath.
The thing is that packaging jar can be both a libary or an application. If you want a standalone application, you need to tell Maven to package everything, e.g. by using the assembly plugin or shade plugin.
You use compile scope when you want some dependencies to come along with your code. For example you want Jackson to be a part of your application if you are using it for json serialization.
You use provided scope, if you want dependency to be on the classpath during the compilation but wont be included within your application. It must be provided by running environment. For example you want Lombok as it is compile only library, or you want to have Servlet Api dependency as provided when you are writing servlet application because such app will be ran on servlet container thus there is no need to pack it within your application (it will be available in container runtime)
Do I have to always use the Assembly plugin
Nobody forces you to do so.
I have a webapp that consists of multiple projects. We assemble using Ant and we suspect that some of the jars in /java directory are unneeded.
To find unneeded jars I ran
mvn dependency:analyze -DignoreNonCompile
to get a list of unused declared jars for each project. However it is possible that a jar unused by one project is still used by another. To check this, I ran
mvn dependency:tree
to get the dependency structure of all projects.
Using information from these commands, I will now use a script to check if a jar exists such that it is unused in all projects that declare it. Is this a reasonable approach for compile-scoped jars? What about jars in other scopes?
Thanks.
However it is possible that a jar unused by one project is still used
by another.
I recommend to declare all needed dependencies as direct dependencies and not rely on transitive dependencies which might get removed in a newer version.
Define the versions of the dependencies in the DependencyManagement section of the common parent POM and omit the versions later when declaring the dependcies. Like this you can make sure you're using the same version of the dependencies in all your projects.