7

Is there an equivalent to Leiningen's "checkouts" feature in sbt?

Here is what I want to accomplish:

I have two projects, an application Foo and library "Bar". I want to publish each of these projects independently. Foo depends on Bar, and the sbt project will direct sbt to download the jar for "Bar" from a repository whenever a third-party tries to build "Foo" (which is typical behavior).

Now, say I want to hack on both Foo and Bar at the same time. For example, while working on Foo, I want to directly edit and debug some of the source for Bar so the edits affect Foo (and then later rebuild Bar when it is convenient).

How can I instruct sbt to satisfy its dependency on Bar from its source code on my machine (rather than my local repository) during this hack session?

(P.S. I asked a similar question for Clojure/Leiningen. Leiningen has the "checkouts" feature which accomplishes this. I am wondering if there is something similar in sbt...)

Community
  • 1
  • 1
kes
  • 5,983
  • 8
  • 41
  • 69

1 Answers1

9

You can declare a source dependency from Foo to Bar via a project reference:

import sbt._

object FooBuild extends Build {
  lazy val root = Project(
    id = "foo",
    base = file(".")
  ) dependsOn(theBarBuild)

  lazy val theBarBuild = ProjectRef(
    base = file("/path/to/bar"),
    id = "bar")
}

This should also recompile Bar (if it has changed) whenever you compile Foo. Please note that the id of the project reference must match the actual id of the Bar project, which might be something like e.g. default-edd2f8 if you use a simple build definition (.sbt files only).

This technique is especially useful for plug-ins (see my blog post about this topic).

Edit:

You can kind of re-code the checkout behaviour like this:

import sbt._

object FooBuild extends Build {
  lazy val root = addCheckouts(Project(id = "foo", base = file(".")))

  def addCheckouts(proj: Project): Project = {
    val checkouts = proj.base.getCanonicalFile / "checkouts"
    if (! checkouts.exists) proj
    else proj.dependsOn(IO.listFiles(DirectoryFilter)(checkouts).map { dir =>
      ProjectRef(base = dir, id = dir.name): ClasspathDep[ProjectReference]
    }:_*)
  }
}

This checks your project directory for a checkouts directory, and if it exists, adds the directories therein (which should be symlinks to other projects) as project references to the project. It expects the symlink to be named like the actual ID of the linked project (e.g. default-edd2f8 or bar). If the directory doesn't exist, the build just works as before.

When you add or remove a symlink in the checkouts directory (or the directory itself), you must reload the Foo project to pick up the changes.

Hope this helps.

  • 5
    You can also use `RootProject(dir)` to pick the root project without specifying the project ID. – Mark Harrah Jan 16 '13 at 12:51
  • Thanks. This is helpful. I tried it and it works. However, I would ideally like to be able to do this without altering the contents of the projects involved. Sounds like a job for a plugin? – kes Jan 16 '13 at 15:27
  • Also, per Mark Harrah's advice, I changed `ProjectRef(base = dir, id = dir.name)` to `RootProject(dir)`. Works great. – kes Jan 16 '13 at 15:30
  • Pretty old post but I give a try : @scrapdog Did you find a way to handle the use case of "if dependency is found locally then use it otherwise go to repo with this default version" without changing the sbt build conf manually? For now we manually changed our build.sbt manually. – Maxence Apr 23 '15 at 16:07