Reviewing Play 1.2’s dependency management
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.