I have 3 projects of the following structure:
App
| |
...
| |
| +--lib
| | |
| | +--...
| |
| +--dist
|
Lib
| |
...
| |
| +--lib
| | |
| | +--sublib-1.0.jar
| |
| +--dist
| |
| +--lib-1.0.jar
|
SubLib
|
...
|
+--dist
|
+--sublib-1.0.jar
Which have the following relation:
App <-- Lib <-- SubLib
I am using apache ivy to retrieve the dependencies for both App
and Lib
. The dependencies are described as follows:
ivy.xml
of Lib
:
<ivy-module version = "2.0">
<info organisation = "com.test.lib" module = "lib"/>
<dependencies>
<dependency org = "com.test.sub.lib" name = "sublib" rev = "1.0" conf = "compile->default"/>
</dependencies>
</ivy-module>
ivy.xml
of App
:
<ivy-module version = "2.0">
<info organisation = "com.test.app" module = "App"/>
<dependencies>
<dependency org = "com.test.lib" name = "lib" rev = "1.0" conf = "compile->default"/>
</dependencies>
</ivy-module>
ivysettings.xml
:
<ivysettings>
<settings defaultResolver = "local"/>
<resolvers>
<filesystem name = "local">
<artifact pattern = "${ivy.settings.dir}/SubLib/dist/[artifact]-[revision].[ext]"/>
<artifact pattern = "${ivy.settings.dir}/Lib/dist/[artifact]-[revision].[ext]"/>
</filesystem>
</resolvers>
<modules>
<module organisation = "com.test.ivytest" resolver = "local"/>
</modules>
</ivysettings>
Expected result: after executing ivy:retrieve
, both sublib-1.0.jar
and lib-1.0.jar
to be present in App/lib
Actual result: only lib-1.0.jar
is present in App/lib
. The generated ivy-report for App
does not contain any mention of sublib
being a dependency of lib
. Nothing of a sort is in ant + ivy logs during build as well.
Note: lib-1.0.jar
is not being built as a fat-jar.
What am I missing in this configuration?
Update
I've done some thinking, and the only conclusion I came with is that this problem is indeed misconfiguration. Judging by the fact, that transitive dependency is not retrieved, we can positively say that ivy does not have any information of a sort when it resolves lib
. And that makes sense, because Lib/dist
folder could be anywhere in the file system. The only way to have information about transitive dependency would be having respective ivy.xml
somewhere close to that jar. Which is not. This is slightly confirmed by the message in logs [ivy:retrieve] local: no ivy file found for com.test.lib#lib;1.0: using default data
. The only way that information is preserved is cache data in %user%/.ivy/cache
. There the generated [org]-[artifact]-[conf].xml
files do contain the dependency information. So I'm guessing for that to work properly, I will have to use cache on App's resolution level.
Does that make any sense or am I plainly wrong again?
We can use mvn dependency:tree command to see the structure of the dependencies we included in our project. A transitive dependency, simply put, is a dependency that a child dependency depends on. If A depends on B and B depends on C, C is the transitive dependency of A.
Ivy is a dependency manager -- it manages and controls the JAR files that your project depends on. If you don't have the JARs, it will pull them down for you by default (from the Maven 2 repository), which can make project setup a lot easier.
Just go the console. Navigate to E: > ivy folder and run the ant command. Ivy will come into action, resolving the dependencies, you will see the following result. You can verify the downloaded files in ivy cache's default local repository location ${ivy.
Ok, so the problem was indeed the bad configuration and my lack of understanding it. Here is the detailed explanation how to make it work. I'm not really good with terminology, so I may have misused some words here. Let's look at project configuration and what's what.
Sublib
is going to be a runtime dependency for Lib
that has a compile-time dependency guava
.
SubLib
| `lib
| `guava-19.0.jar
|
`dist
| `--sublib-1.0.jar
|
`src
`...
So, we need to make appropriate configurations in SubLib's ivy.xml
:
<ivy-module version="2.0">
<info organisation="com.test.sub.lib" module="sublib"/>
<configurations>
<conf name="runtime" visibility="public"/>
</configurations>
<dependencies>
<dependency org="com.google" name="guava" rev="19.0" conf="runtime->default"/>
</dependencies>
</ivy-module>
Here, by declaring a runtime
configuration we state that this ivy.xml
describes a module that is a runtime-time dependency. And since guava has no such file we describe it as default. Pretty standard here. Now, for others to know that sublib-1.0.jar
actually has a dependency from guava-19.0.jar
we need to publish it to a repository, so that such information exists in form of file ivy-[version].xml
next to the jar. I chose to publish into the build
folder. To do so, ivysettings.xml
needs to contain a resolver that helps to match file patterns for publishing and later on on retrieval when we'll resolve from Lib
.
<ivysettings>
<settings defaultResolver="filesystem-resolver"/>
<resolvers>
<filesystem name="sublib-resolver">
<ivy pattern="${ivy.settings.dir}/SubLib/dist/repo/ivy-[revision].xml"/>
<artifact pattern="${ivy.settings.dir}/SubLib/dist/repo/[artifact]-[revision].[ext]"/>
</filesystem>
<filesystem name="filesystem-resolver">
<artifact pattern="${ivy.settings.dir}/SubLib/lib/[artifact]-[revision].[ext]"/>
</filesystem>
</resolvers>
<modules>
<module name="sublib" organisation="com.test.sub.lib" resolver="sublib-resolver"/>
</modules>
</ivysettings>
sublib-resolver
will allow to find the corresponding ivy-[revision].xml
that has information about the dependencies and the location of jar
. Whereas filesystem-resolver
will find our guava
dependency. Now we just publish sublib
with ant
by invoking our resolver:
<target name="publish">
<ivy:publish artifactspattern="${dist.dir}/[artifact]-[revision].[ext]"
resolver="sublib-resolver"
overwrite="true"
pubrevision="${revision}"
/>
</target>
Now to Lib
. Lib is going to be a compile-time dependency for App
and we describe it as such in ivy.xml
and declare SubLib
as a runtime dependency for it:
<ivy-module version="2.0">
<info organisation="com.test.lib" module="lib"/>
<configurations>
<conf name="compile" visibility="public"/>
<conf name="runtime" extends="compile" visibility="public"/>
</configurations>
<dependencies>
<dependency org="com.test.sub.lib" name="sublib" rev="2.0" conf="runtime->compile"/>
</dependencies>
</ivy-module>
Here's where configuration comes in play and what I didn't understand at first. runtime->compile
Left hand side is understandable: sublib
was declared as a runtime dependency and we assigned it to runtime
conf in it's ivy file. On the arrow's right hand side we state that we want sublib
's compile-time dependencies as well. And the one that was configured as such is guava
. It will be found by the resolver and retrieved as well. So, we need a resolver for Lib
as well, so complete ivysettings.xml
file will look like this:
<ivysettings>
<properties file="${ivy.settings.dir}/ivysettings.properties"/>
<settings defaultResolver="filesystem-resolver"/>
<resolvers>
<filesystem name="sublib-resolver">
<ivy pattern="${ivy.settings.dir}/SubLib/dist/repo/ivy-[revision].xml"/>
<artifact pattern="${ivy.settings.dir}/SubLib/dist/repo/[artifact]-[revision].[ext]"/>
</filesystem>
<filesystem name="lib-resolver">
<ivy pattern="${ivy.settings.dir}/Lib/dist/repo/ivy-[revision].xml"/>
<artifact pattern="${ivy.settings.dir}/Lib/dist/repo/[artifact]-[revision].[ext]"/>
</filesystem>
<filesystem name="filesystem-resolver">
<artifact pattern="${ivy.settings.dir}/SubLib/lib/[artifact]-[revision].[ext]"/>
</filesystem>
</resolvers>
<modules>
<module name="sublib" organisation="com.test.sub.lib" resolver="sublib-resolver"/>
<module name="lib" organisation="com.test.lib" resolver="lib-resolver"/>
</modules>
</ivysettings>
And publish of Lib
in Lib
's build.xml
:
<target name="publish">
<ivy:publish artifactspattern="${dist.dir}/[artifact]-[revision].[ext]"
resolver="lib-resolver"
overwrite="true"
pubrevision="${revision}"
/>
</target>
Now to the main problem: transitive retrieval. Configurations. In App
's ivy.xml
we need to specify exactly that we want transitive dependencies. The information that they exist stored in repos is not enough. One must specify it in App
's ivy.xml
:
<configurations>
<conf name="compile" visibility="public"/>
</configurations>
<dependencies>
<dependency org="com.test.lib" name="lib" rev="1.0" conf="compile->compile; compile->runtime"/>
</dependencies>
What happens here is the following: by declaring compile
conf we state that App
has a compile configuration. The first arrow chain, like before, states that we (compile-configured module App
) want to get compile-configured dependency called lib
. Left and right arrow sides respectively. And the second arrow set states that we (compile-configured module App
) want to get runtime-configured dependencies of lib
! Which is sublib
. And since it comes together with guava
it is retrieved as well.
A little messy explanation, and may very not be the most elegant one for a solutioin, but it's the only way I managed to make this work properly at all. If anyone knows any better ways of doing so, any help would be appreciated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With