Sunday, March 27, 2011

Manage Dependency

Most of the programmers must be a way ahead of me in this issue. I have to admit that I do not have a stable knowledge on Maven. I have tried several times to understand it, but for me the xml is to hard to maintain, the one-artifact-per-project paradigm is to restrictive and the web of plugins that should be learned is too complex. I prefer Ant which is a bit old-fashioned, but well documented and readable. (Should I replace it Gradle seems to be a good choice.)
What I envy from Maven is the archetype and the dependency management. I really hate to collect all the dependent jars into the new project's lib directory. Fortunately there are some other ways to get along and that is the point of this post.

What is the adventage of using some kind of dependency management?
  • no need to store jars in VCS - lib directory becomes a first class citizen in .gitignore
  • no need to keep in mind transitive dependencies
  • in-house libraries can be stored in the same shared repository as the other libraries
  • easy to check upgrade possibilities
OK. So if it is so nice, how can I get there? As I considered Gradle as a replacement for Ant I saw that it uses Ivy to manage libraries, so it came naturally to take a look at it. Let us gather the main points that should be achieved:
  • test project that gets libs through declaration
  • shared repository for enterprise
  • publish in-house libs to shared repository
Declarative dependency
Setting up Ivy is easy: the ivy-[version].jar should be placed in the lib dir of ant. The dependency declaration goes to the ivy.xml file in the root directory. It could be something like this:
<ivy-module version="2.0">
    <info organisation="hu.progmatx" module="test-ivy"/>
    <dependencies>
        <dependency org="org.apache.velocity" 
                       name="velocity" rev="1.5"/>
    </dependencies>
</ivy-module>
(A nice repository of dependency descriptions is available on the net.)
So we have the definition how to get the files? Let us put together a very simple build.xml:
<project xmlns:ivy="antlib:org.apache.ivy.ant" 
         name="test-ivy" 
         default="resolve">
    <target name="resolve" 
            description="--> retrieve dependencies with ivy">
        <ivy:retrieve sync="true" 
                      symlink="true" 
                      refresh="true"/>
    </target>

    <target name="report" 
            description="--> report dependencies with ivy" 
            depends="resolve">
        <ivy:report />
    </target>
</project>
The main points are
  1. the namespace declaration that makes easy to call Ivy targets,
  2. resolve target which calls the retrieve task
  3. report task can display the dependencies in html or graphml
Some explanation on retrieve task might come in handy. First it implicitly calls the resolve task to compute the dependency graph. Then it downloads the files into the local cache. Then it would copy the files into the lib directory, but I prefer using symlinks (as you can see from the attribute of the task). The sync attribute ensures that unused dependencies will be removed.

Repository
Our next step is to establish a shared place of libraries. The structure of the repository can be customized and the way Maven does it can also be suitable. Take a look at the following structure:
shared-repository/
└── no-namespace
    └── ant
        └── ant
            ├── ivys
            │   ├── ivy-1.6.xml
            │   ├── ivy-1.6.xml.md5
            │   └── ivy-1.6.xml.sha1
            └── jars
                ├── ant-1.6.jar
                ├── ant-1.6.jar.md5
                └── ant-1.6.jar.sha1
This can be accessed through the configuration of the following properties in the build.xml:
...
<property name="ivy.shared.default.root" value="/media/shared-repository"/>
<property name="ivy.shared.default.ivy.pattern" value="no-namespace/[organisation]/[module]/ivys/ivy-[revision].xml"/>
<property name="ivy.shared.default.artifact.pattern" value="no-namespace/[organisation]/[module]/[type]s/[artifact]-[revision].[ext]"/>
...
Of course with shared repository I have to say Good bye! to symlinks.

Publish libraries
To maintain a repository like that by hand could be hard. Fortunately we have ivy:publish and ivy:install tasks to the rescue. One can be used to load up our own homebrew jars and the other is suitable to get a copy of the dependencies from the default Maven repo. Let us do it one by one!
To publish your jar just put the following in the build.xml:
<target name="publish" depends="resolve"
        description="--> publish module to shared repository">
    <ivy:publish resolver="shared" pubrevision="1.0">
         <artifacts pattern="build/jars/[artifact].[ext]" />
    </ivy:publish>
</target>
OK, it was too easy, so let's look at the other problem! That seems to be more complex, because we have to edit two files. Append this to build:
<property name="ivy.cache.dir" value="${basedir}/cache"/>
<property name="dest.repo.dir" value="/media/shared-repository"/>

<target name="maven2"
        description="--> install module from maven 2 repository">
    <ivy:settings id="copy.settings" file="${basedir}/ivysettings.xml"/>
    <ivy:install settingsRef="copy.settings" 
          organisation="org.apache.velocity" module="velocity" revision="1.5" 
          from="libraries" to="my-repository"
                    overwrite="true" transitive="true"/>
</target>
And let's create the ivysettings.xml!
<ivysettings>
    <settings defaultCache="${ivy.cache.dir}/no-namespace" 
                 defaultResolver="libraries"
                 defaultConflictManager="all" />  <!-- in order to get all revisions without any eviction -->
    <resolvers>
        <ibiblio name="libraries" m2compatible="true" />
        <filesystem name="my-repository">
            <ivy pattern="${dest.repo.dir}/no-namespace/[organisation]/[module]/ivys/ivy-[revision].xml"/>
            <artifact pattern="${dest.repo.dir}/no-namespace/[organisation]/[module]/[type]s/[artifact]-[revision].[ext]"/>
        </filesystem>
    </resolvers>
</ivysettings>
Here the point is that we create a so called resolver, that is connected to the standard Maven repository and to our shared repository. In the install task the dependent lib is named and with the attributes set up this way we gather the transitive dependecies as well.

Conclusion
Wasn't it easy? I did not dare to think of it before - it seemed so many things to do and all these things seemed to be so complex. Fortunately Ivy is gentle, it just adopts to your pace and you do not have to customize more than what necessary is. I was only scratching its coat, but it was completly working already. It was worth to invest some time and gather some Experience by Doing!

1 comment:

  1. hello,

    ok, the maven is more complex and hard to understood, but I think there are many option which are not exists in this solution. And if you have methodology for using Maven at your company or in your project, most of the developers do not have to take care of maven.

    Best Regards
    Gabor Blasko

    ReplyDelete