Reviewing Play 1.2’s dependency management

30 June 2011

Sietse de Kaper

by Sietse de Kaper

Play version 1.2 introduced dependency management to the framework. I’m a big fan of dependency management, and I think it’s a necessary tool for any software project. Play’s approach is pretty elegant and easy to use, but there’s a few catches and things I think they didn’t quite get right. This article is a short introduction and my opinion of what could be better. I’ll repeat some of the information in the manual, but no more than needed to illustrate a few points. I’ll also deliberately ignore a few points (such as module dependencies), so it’s still a good idea to read the manual if you don’t know anything about Play dependency management yet.

Dependency management, Play-style

With Play being a Java framework, it only makes sense that its dependency management makes use of Maven repositories. Maven has excellent dependency management, but it’s much more than dependency management, it’s a complete build tool. Because Play is perfectly capable of building its own applications, thank you very much, it uses the Ivy library instead.

Maven dependencies are identified by three properties: groupId, artifiactId and version. For example, version 2.1 of the flexjson library is identified as artifactId flexjson, version 2.1 under the net.sf.flexjson groupId. In Play, this is represented as: net.sf.flexjson → flexjson 2.1. The list of artifacts your project depends on are listed in the dependencies.yml file of your projects conf directory. Just add them to the require property of the YAML file.

Once you’ve specified what you need, Play needs to know where to find it. In an ideal world, every library would be in the Maven central repository, and Play would just grab it from there. However, there are plenty of reasons for a library not to be in Maven central. The library may not meet the requirements, you may need a bleeding-edge ("snapshot") version, or the library’s maintainer may just not care about Maven. That’s just the most common reasons for a library not to be in the central repository. Where Maven and Ivy made it pretty cumbersome to configure an additional repository to search for dependencies, Play makes it easy: just list them under the repositories property of your dependencies.yml file.

With Play, unlike Maven and Ivy, resolving dependencies is not part of the build process. Instead, you need to instruct Play to start the process yourself. It’s easy enough; just run play dependencies, and wait. Oh, the whole ‘download the internet’ thing isn’t as much of an issue as it is with Maven, mainly because Play itself doesn’t have as many dependencies as Maven. Play will still need to download transitive dependencies (that’s a fancy way of saying ‘dependencies of dependencies’) though, so depending on the complexity of the dependencies it included, the whole process may still take a while.

Features introduced by Play

Play wouldn’t be Play if it didn’t introduce some features to make your life easier.

One of the things Play brings to the table is more flexible version definitions. Where other tools only allow (and force) you to specify one specific version, Play also allows you to specify
a minimum and/or maximum version to use. This not only allows you to automatically use the most up-to-date version of a library, it also means you’re less likely to run into version conflicts, since the version requirement is less rigid.

Another nifty feature is the ability to specify which dependencies a custom repository provides. Just list the artifacts under the repository’s contains property, and Play will search that repository, and only that repository for the listed dependencies. This can save a lot of time, since it means Play won’t have to scour each and every repository it knows about for the artifacts you already know only exist in a single custom repository. It’s a pretty cool concept, but unfortunately, the implementation of this feature leaves a lot to be desired.

What went wrong (imho)

Like I said in the introduction, dependency management is a must for me. Unfortunately, I haven’t been able to convince all of my colleagues just yet. The main reasons not everyone is using dependency management on all of their project yet are probably the complexity of setting it up (Maven pom files are a nightmare, Ivy is only slightly better) and the pain of finding the magic combination of repositories that make all unresolved dependency errors go away. Play obviously fixes the first obstacle, and makes a good attempt at making repositories easier to use, but I think it could have done a better job.

As stated before, all you need to do to tell Play about a repository is list it under the repositories property. However, instead of using the repositories listed there to resolve any dependencies it couldn’t resolve using the default repository, you have to tell it what dependencies are there using the contains property. It won’t use the repository for anything else. The contains property is flexible enough to allow wildcards for artifacts under a certain groupId, but you’re SoL if the dependency you want has transitive dependencies outside its own groupID (unless those dependencies happen to be in the default repositories). It’s a real shame, as the contains property for a repository would have made an excellent optional optimization feature, since you could tell Play exactly where to find given dependency. However, as it stands contains is a required property in order to have Play even use the repository, making it a lot less useful, and managing repositories is now a lot harder than it has to be.

If you’re unlucky enough to require a library with lots of transitive dependencies not in Maven Central, you have to figure out exactly what transitive dependencies it uses, and instruct Play where to get them (usually the same repository).

Example situation

The Play manual uses the jbpm-persistence-jpa dependency to illustrate how to specify a Maven 2 repository. The JBoss Maven repository happens to be one of the major repositories that has loads of artifacts that are not in Maven Central. So I’ll use the same example to illustrate the problem. I actually wanted to use Sweble as an example, as that was the library I wanted to use when I first ran into the issue, but that’s actually gotten into Maven Central since then, making the problem go away.

So, for some reason you want to use the jbpm-persistence-jpa library in your Play application. Since you’re a naive optimist, you start by simply specifying the dependency in dependencies.yml:

# Application dependencies

require:
    - play
    - org.jbpm -> jbpm-persistence-jpa 5.0.0>

After running play dependencies, you’ll get the following warning:

~ *****************************************************************************
~ WARNING: These dependencies are missing, your application may not work properly (use --verbose for details),
~
~   org.jbpm->jbpm-persistence-jpa 5.0.0
~ *****************************************************************************
~
~ Some dependencies are still missing.>

Ok, fair enough. It’s not in Maven Central. Let’s list the JBoss repo:

# Application dependencies

require:
    - play
    - org.jbpm -> jbpm-persistence-jpa 5.0.0

repositories:
    - jboss:
        type: iBiblio
        root: "http://repository.jboss.org/nexus/content/groups/public-jboss/"

This time, play dependencies takes a little longer to throw the same error. Ugh. Have a look at the output for play dependencies --verbose. You’ll notice it never even tries to get anything from the JBoss repository. Okay, this is what I warned you about: you actually need to tell Play what artifacts you want from the JBoss repo:

# Application dependencies

require:
    - play
    - org.jbpm -> jbpm-persistence-jpa 5.0.0

repositories:
    - jboss:
        type: iBiblio
        root: "http://repository.jboss.org/nexus/content/groups/public-jboss/"
        contains:
            - org.jbpm -> jbpm-persistence-jpa>

Go ahead, I dare you: run play dependencies. Yup. Same error. By now you probably want to run play dependencies --verbose by default. Somewhere in the output, you’ll see something like this:

:::: WARNINGS
    io problem while parsing ivy file: http://repository.jboss.org/nexus/content/groups/public-jboss/org/jbpm/jbpm-persistence-jpa/5.0.0/jbpm-persistence-jpa-5.0.0.pom: Impossible to load parent for file:/Users/sietse/.ivy2/cache/org.jbpm/jbpm-persistence-jpa/ivy-5.0.0.xml.original. Parent=org.jbpm#jbpm;5.0.0]]>

In other words, Ivy has problems reading the parent module for jbpm-persistence-jpa. This had me scratching my head for a while. I tried to see if it was there (it was), if it was malformed (it wasn’t), or if Ivy has problems reading this type of pom (it doesn’t). In the end, it turns out it has problems because it never even tries to load it. This just seems brain-dead to me. Thankfully, this transitive dependency is in the same groupId, so we can fix this with a wildcard:

# Application dependencies

require:
    - play
    - org.jbpm -> jbpm-persistence-jpa 5.0.0

repositories:
    - jboss:
        type: iBiblio
        root: "http://repository.jboss.org/nexus/content/groups/public-jboss/"
        contains:
            - org.jbpm -> *>

This time, after waiting for Ivy to download the new transitive deps, we get a slightly different error:

~ *****************************************************************************
~ WARNING: These dependencies are missing, your application may not work properly (use --verbose for details),
~
~   org.drools->drools-api 5.2.0.M1
~   org.drools->drools-compiler 5.2.0.M1
~   org.drools->drools-persistence-jpa 5.2.0.M1
~   org.drools->drools-core 5.2.0.M1
~ *****************************************************************************>

Turns out that our dependency has transitive dependencies (that are not in Maven Central) that don’t fall in the org.jbpm groupId. We have to list them separately. We got lucky this time: they’re all in a single groupId, org.drools, so we can just add the whole groupId with a wildcard:

# Application dependencies

require:
    - play
    - org.jbpm -> jbpm-persistence-jpa 5.0.0

repositories:
    - jboss:
        type: iBiblio
        root: "http://repository.jboss.org/nexus/content/groups/public-jboss/"
        contains:
            - org.jbpm -> *
            - org.drools -> *>

Now, finally, play dependencies no longer complains, and you can start using your new library.

Conclusion

Overall, I still like the Play dependency management mechanism. It doesn’t mix building and dependency management, and the YAML configuration file is clear and concise. The one thing it got wrong is the repository management. The required contains property really messes everything up. Because of it, the complex problem area that is transitive dependencies is exposed, while it could have been nicely hidden from me. I don’t want to know what a certain library need to get stuff done, I just want to use the library. That’s the whole goal of dependency management – figuring that stuff out for me.

My suggestion to the play devs (ticket here): please make the contains property an optional performance improvement. When I list an additional repository, just use it to try and resolve artifacts that couldn’t be resolved using any of the other repositories. It would make life so much easier.