mpeltonen/sbt-idea — pull request #183 by jroper (2012-11-23 01:45:25+00:00)
Title: Allow test dependency versions to override runtime dependencies

IDEA and SBT treat dependency scopes fundamentally differently. In IDEA, there is a single classpath, and dependencies are either included or not included in it, depending on the current scope. In SBT, each scope may "evict" dependencies out of the classpath.

For example, if I have dependency in the runtime scope on com.foo:bar:1.1, and then I have a test dependency that transitively depends on com.foo:bar:1.2, then in my runtime scope in SBT, I will have com.foo:bar:1.1, while in test, that will be evicted, and I'll have com.foo:bar:1.2. For a comparison with other build tools, in Maven for example, the behaviour here is that the test transitive dependency will result in the runtime dependency being upgraded to 1.2, while maintaining its runtime scope.

The way that SBT IDEA currently handles this is that it puts both dependencies on the classpath in the order declared in the build file. If the test scoped dependency appears later in the buid file, this will cause issues, because 1.1 will come before 1.2, and so it will be found first, so if the transitive test dependency actually uses new features from 1.2, you'll get linkage errors when you run the tests in IDEA, where you won't in SBT.

A naive approach to solving this (that I tried) is to just put all test dependencies before runtime dependencies. However, this causes another issue, it means the classpath is now in a completely different order to what SBT uses. In my specific case, another test dependency was bringing a dependency that had changed its groupId, and my runtime scope was depending on the newer version with a different groupId. In SBT, both dependencies were on the classpath, but since the test dependency was declared later in the classpath, the correct, newer dependency was being used, so it wasn't a problem. But with in IDEA it was.

So, I've implemented an approach that as closely as possible simulates what SBT does. It detects when SBT would evict a runtime dependency and replace it with a test dependency, and in that case, moves just that dependency to be just before the runtime dependency in the classpath. The result is that the classpath order is almost the same as what SBT uses, but not quite in the case of these "evicted" dependencies. There are still some edge cases where this could cause problems, but I think they would be very rare, if they ever did happen, and I don't think it's possible to 100% emulate SBTs behaviour in IDEA since they have fundamentally different notions of the capabilities of scopes.

Also included in this pull request is a nicer format for the name of the dependencies. Instead of being named:

com_foo_bar_bar_1.2.3_test

They are now named:

SBT: com.foo.bar:bar:1.2.3

This matches how IDEA handles Maven dependencies, and looks much cleaner. The old semantics for the filenames are still used.