Chris Stewart
Chris Stewart

Reputation: 1679

Circular project dependencies with a testkit sbt

I maintain a open source bitcoin library called bitcoin-s. If you look at the build.sbt file you will see that the testkit project depends on the rpc project, and the rpc project depends on the testkit project as a publish dependency inside of our Deps.scala file.

This is unfortunate because if we change the api in the rpc project at all, we have to publish a new testkit snapshot to be able to reflect the changes in the rpc api, and then run tests in the rpc project. You can see a more detailed guide of the build process here

I would like to make it so that we can just have each project depend on each other in build.sbt with something like this:

lazy val rpc = project
  .in(file("rpc"))
  .enablePlugins()
  .settings(commonSettings: _*)
  .dependsOn(
    core,
    testkit % "test->test"
  )
  .settings(
    testOptions in Test += Tests.Argument("-oF")
  )

lazy val bench = project
  .in(file("bench"))
  .enablePlugins()
  .settings(assemblyOption in assembly := (assemblyOption in assembly).value
    .copy(includeScala = true))
  .settings(commonSettings: _*)
  .settings(
    libraryDependencies ++= Deps.bench,
    name := "bitcoin-s-bench"
  )
  .dependsOn(core)

lazy val eclairRpc = project
  .in(file("eclair-rpc"))
  .enablePlugins()
  .settings(commonSettings: _*)
  .dependsOn(
    core,
    rpc
    testkit % "test->test"
  )

lazy val testkit = project
  .in(file("testkit"))
  .enablePlugins()
  .settings(commonSettings: _*)
  .dependsOn(
    core,
    rpc,
    eclairRpc
  )

However this creates a circular dependency between the projects which leads to a stackoverflow when loading build.sbt.

Is there any way to avoid this? We have a very complicated process of publishing the dependency currently which ends up depending on SNAPSHOTS of the project (not full releases) as the bitcoinsV

Upvotes: 2

Views: 374

Answers (2)

Richard Sitze
Richard Sitze

Reputation: 8463

Break the dependency cycle.

One way to do that might be to break rpc into separate rpc and rpc-test projects, so your dependency graph is

rpc-test >--+--> testkit >--+
             \               \
              +---------------+--> rpc

Upvotes: 2

Rex
Rex

Reputation: 568

Did you try something like this but I'm not really sure if this will do the work.

lazy val middleProject = (project in file("middle-project"))
    .settings(
    name := "middle-project",
    libraryDependencies ++= Seq(
        // Dependencies here..
    )
  )
  .aggregate(project1, project2)
  .dependsOn(project1, project2)


lazy val project1 = (project in file("project-1"))
    .settings(
    name := "project-1",
    libraryDependencies ++= Seq(
        // Dependencies here...
    )
  )

 lazy val project2 = (project in file("project-2"))
    .settings(
    name := "project-2",
    libraryDependencies ++= Seq(
        // Dependencies here...
    )
  )
  • Aggregation means that running a task on the aggregate project will also run it on the aggregated projects.
  • The middle project will do the process and serve as connector to those two projects.

Upvotes: 0

Related Questions