Search code examples
javaivy

Creating proper ivy configurations (and not messing with *)


I have a project, which is being used by other project as an Ivy dependency. Its dependencies.xml contains lots of dependencies, basically in 3 categories:

  • Dependencies needed to compile the project in Eclipse and run it in embedded Tomcat, and to create a complete auto-consistent WAR file
  • Dependencies needed to compile and run unit tests
  • Dependencies that are normally part of Tomcat classpath, including the below, and needed to compile a JAR of the web project via Ant
    • Servlet API
    • JDBC connectors (they are in fact not shipped with the application)

The third dependencies category is critcal to me. If I don't include servlet-api.jar in the javac classpath I can't compile the project. But if I include that package in the war classpath I fall into a bad practice, because server runtimes (mainly Tomcat, but also Websphere) contain their own servlet-api.jar.

And for JDBC connectors, I am only required to include them into Ant classpath for running unit tests from Bamboo, as I want to repeat the same tests with multiple databases.

Current code

Here is my current dependency.xml fragment:

<configurations>
    <conf name="test" visibility="public" extends="compile" />
    <conf name="compile" visibility="public" extends="runtime" />
    <conf name="runtime" visibility="public" />
    <conf name="provided" visibility="public" />
    <conf name="junit" visibility="public" />
</configurations>

    <!-- Build -->
    <dependency org="javax.servlet"        name="javax.servlet-api"         rev="3.0.1"    transitive="false" conf="provided->*" />
    <dependency org="javax.servlet.jsp"    name="javax.servlet.jsp-api"     rev="2.3.1"    transitive="false" conf="provided->*" />
    <dependency org="javax.el"             name="javax.el-api"              rev="3.0.0"    transitive="false" conf="provided->*" />
    <dependency org="mysql"                name="mysql-connector-java"      rev="5.1.38"   transitive="false" conf="provided->*" />
    <dependency org="ojdbc"                name="ojdbc"                     rev="14"       transitive="false" conf="provided->*" />
    <dependency org="org.hsqldb"           name="hsqldb"                    rev="2.3.3"    transitive="false" conf="provided->*" />
    <dependency org="org.postgresql"       name="postgresql"                rev="9.4.1207" transitive="false" conf="provided->*" />
    <dependency org="com.microsoft"        name="sqljdbc"                   rev="4.1"      transitive="false" conf="provided->*" />




    <dependency org="org.adrianwalker"     name="multiline-string"          rev="0.1.2"    transitive="false" conf="provided->*" />


    <!-- Spring -->
    <dependency org="org.springframework"               name="spring-core"                      rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-aop"                       rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-beans"                     rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-context"                   rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-context-support"           rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-expression"                rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-jdbc"                      rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-orm"                       rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-tx"                        rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-web"                       rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-webmvc"                    rev="4.2.4.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework"               name="spring-test"                      rev="4.2.4.RELEASE"     transitive="false"          conf="test->*"/>
    <dependency org="org.springframework.plugin"        name="spring-plugin-core"               rev="1.2.0.RELEASE"     transitive="false"          conf="runtime->*"/>
    <dependency org="org.springframework.plugin"        name="spring-plugin-metadata"           rev="1.2.0.RELEASE"     transitive="false"          conf="runtime->*"/>

In the above example, com.adrianwalker#multiline-string is required only for annotation parsing and no more

Ant

In my main-project, I ended up retrieving dependencies with the following task

<target name="ivy-retrieve">
    <ivy:configure override="true" file="${ivy.install.dir}/ivy-settings.xml" />
    <ivy:retrieve sync="true" conf="runtime,junit" type="jar,bundle" pattern="${project.local.lib}/[artifact]-[revision].[ext]" />

    <eclipse.refreshLocal depth="infinite" resource="/" if:set="eclipse.running" />
</target>

This will populate my WEB-INF/lib directory correctly with all the dependencies I need (including spring-test) but without the JDBC connectors that are already manually copied to Tomcat classpath

Ok...

The problem

The problem comes when I embed the main-project into a derived-project

<dependency org="com.example" name="main-project"          rev="${current.version}"         transitive="true"      conf="runtime->*"/>

And

<target name="ivy-retrieve-eclipse">
    <ivy:configure override="true" file="${ivy.install.dir}/ivy-settings.xml" />
    <ivy:retrieve sync="true" conf="runtime,junit" type="jar,bundle" pattern="${project.local.lib}/[artifact]-[revision].[ext]" />

    <eclipse.refreshLocal depth="infinite" resource="/" if:set="eclipse.running" />
</target>

The result is that I still get all the dependencies downloaded.

The question

I just need to tell Ivy to download all dependencies that are runtime and junit for my derived-project, including those runtime and junit dependencies included in main-project's ivy file.

How can I do that? What did I mess up?


Solution

  • There were two problems in the previous setup:

    1. As highlighted by @MarkOConnor, I had used conf="runtime->*" on the child project dependency, which will just remap runtime to all configurations
    2. Configurations for parent and child artifact did not coincide

    About point 2, I had the below on parent project

    <configurations>
        <conf name="test" visibility="public" extends="compile" />
        <conf name="compile" visibility="public" extends="runtime" />
        <conf name="runtime" visibility="public" />
        <conf name="provided" visibility="public" />
        <conf name="junit" visibility="public" />
    </configurations>
    

    And the below on child project

    <configurations>
        <conf name="test" visibility="public" extends="compile" />
        <conf name="compile" visibility="public" extends="runtime" />
        <conf name="runtime" visibility="public" />
        <conf name="provided" visibility="public" extends="compile" />
        <conf name="junit" visibility="public" extends="provided, test" />
    </configurations>
    

    So I had to first align child project to parent.

    The second step, although possibly to be avoided, was to remap the dependency of parent project multiple times for each configuration neeeded. Maybe I could optimize on this one, but at least I don't get the jdbc jars downloaded any more into runtime classpath

    So child dependency became

    <dependencies>  
        <dependency org="com.acme"              name="parent-project"          rev="${phoenix.version}"         transitive="true"      conf="runtime->runtime"/>
        <dependency org="com.acme"              name="parent-project"          rev="${phoenix.version}"         transitive="true"      conf="compile->compile"/>
        <dependency org="com.acme"              name="parent-project"          rev="${phoenix.version}"         transitive="true"      conf="provided->provided"/>
        <dependency org="com.acme"              name="parent-project"          rev="${phoenix.version}"         transitive="true"      conf="junit->junit"/>
        <dependency org="com.acme"              name="parent-project"          rev="${phoenix.version}"         transitive="true"      conf="test->test"/>
    </dependencies>