Local external projects in sbt
One of the best features added in sbt 0.10 is the ability to depend on external projects. This is similar to the way you might have published a local snapshot with sbt 0.7, except vastly better. You don’t have to manually publish, update, or even compile anything when the library source changes.
Say that project Appy depends on unfiltered-netty-server. You
want to test Appy against the current master branch, or perhaps
you have your own fork and want to test changes before sending a
pull request. All you have to do is add a file like this
as project/build.scala
import sbt._
object Appy extends Build
{
lazy val root =
Project("", file(".")) dependsOn(unfiltered)
lazy val unfiltered =
ProjectRef(uri("../unfiltered"), "unfiltered-netty-server")
}
This assumes that Unfiltered is cloned into a directory
“unfiltered” that is adjacent to Appy’s base directory. If you’re
using the usual build.sbt to define Appy’s build, it is still
effect; the only change you need to make is to remove the library
from libraryDependencies.
The second parameter of ProjectRef is a subproject name. If you
are depending on a library that does not use subprojects, you can
leave that off.
Because sbt is so smart, you can even specify a git URI in the
ProjectRef. But most of the time I prefer to use a local
project ref, so I know exactly what code I’m depending on and can
easily switch branches and test changes without committing
them. And if you’ve already got the project cloned and built locally,
sbt won’t have to do all that work again.
Did I mention this is really good for pull requests?
Update from Michael Bayne in the comments
I use a similar approach but have Build.scala autodetect whether or not to use an SBT dependency or a traditional artifact dependency based on whether a symlink to the project in question exists in the dependee’s directory:
class Local (locals :(String, String, ModuleID)*) {
def addDeps (p :Project) = (locals collect {
case (id, subp, dep) if (file(id).exists) => symproj(file(id), subp)
}).foldLeft(p) { _ dependsOn _ }
def libDeps = locals collect {
case (id, subp, dep) if (!file(id).exists) => dep
}
private def symproj (dir :File, subproj :String = null) =
if (subproj == null) RootProject(dir) else ProjectRef(dir, subproj)
}
object FooBuild extends Build {
val locals = new Local(
("barproj", null, "com.foo" % "barproj" % "1.0-SNAPSHOT"),
("bazproj", "subproj", "com.foo" % "bazproj-subproj" % "1.0-SNAPSHOT")
)
lazy val foo = locals.addDeps(Project(
"foo", file("."), settings = Defaults.defaultSettings ++ Seq(
// ... libraryDependencies ++= locals.libDeps ++ Seq(
// ... "junit" % "junit" % "4.+" % "test",
"com.novocode" % "junit-interface" % "0.7" % "test->default"
)
)
)
}
Future plugin?