Search code examples
scalasbtcyclic-dependency

How to use SBT for interdependent projects in different configurations


I would like to have the following SBT build setup:

object MyBuild extends Build {

  lazy val core = Project("core", file("core"))
    .dependsOn(testkit % "test")

  lazy val testkit = Project("testkit", file("testkit"))
    .dependsOn(core % "compile")
}

When core is the main module, including domain objects, and testkit is a module for testing support code (builders, matchers, test drivers, etc.; not the tests themselves) that depends on the domain objects and other classes/utils in core.

For this setup SBT gives a Cyclic reference error, although there isn't really a cyclic dependency because of the use of different configurations (core compiles, then testkit compiles depending on core, then core test is compiled depending on both).

I found a dirty way to get around this problem by replacing one of the dependsOn use unmanagedClasspath, for example:

.settings(unmanagedClasspath in Compile <+= (packageBin in (LocalProject("core"), Compile)))

This feels like a hack, and also makes sbt-idea generate incorrect IntelliJ projects (among other things).

Any idea for a better solution? Does SBT support such a structure?


Solution

  • Sbt only checks projects when it looks for cyclic dependencies. It does not take configurations into account. The dependency check is executed in multiple places. One of the most important ones is in the constructor of LoadedBuild.

    It would require changes in a few places and probably some extensive testing. If you really want this feature I think it could theoretically be added.

    The closest you can get to the way sbt itself adds the dependency:

    lazy val core = project.in( file("core") )
      .settings(
        internalDependencyClasspath in Test <++= 
          exportedProducts in Compile in LocalProject("testkit")
      )
    
    lazy val testkit = project.in( file("testkit") )
      .settings(
        internalDependencyClasspath in Compile <++= 
          exportedProducts in Compile in LocalProject("core")
    )