I have a multi-project SBT build. There is a root which does not have anything, it just aggregates all sub projects.
lazy val aaRoot = (project in file(".")).settings(commonSettings: _*).settings(
libraryDependencies ++= appDependencies
).enablePlugins(PlayJava).aggregate(foo, bar)
lazy val foo: Project = (project in file("modules/foo")).settings(commonSettings: _*).settings(
libraryDependencies ++= appDependencies
).enablePlugins(PlayJava).dependsOn(bar)
lazy val bar = (project in file("modules/bar")).settings(commonSettings: _*).settings(
libraryDependencies ++= appDependencies
).enablePlugins(PlayJava).dependsOn(foo)
It is clearly a cyclic dependency here (foo depends on bar and bar depends on foo). What are the possible approaches to avoid these kinds of dependencies or is there an SBT way of handling this.
None of the build tools I know allow for cyclic dependencies... and in my experience that is a symptom of a issue with the design of the application or modules, rather than a 'missing' feature from the tools. It's even seen as something bad when this happens at the package level in the same module/jar.
Can you merge those 2 modules? or reshufle the classes so the cyclic dependency disappears?
As #Augusto suggested, I have re-organized my classes into three different sub-modules and using my Dependency Injection little more wisely. This solved my problem and gave much more abstraction than what I initially had.
Three sub-projects:
api (Just interfaces)
foo (depends on api)
bar (depends on api)
aaRoot (which aggregates all of the above)
In FooModule (in my case Guice module), I am binding FooInterface from api module to FooImplementation (foo module). While calling the bar module, I just use BarInterface (from api) by passing FooInterface.
Same as the case with bar module.
At runtime they all will be available and it can easily resolve.
Related
In my build.sbt (nothing fancy) ....
val common: Project =
project
.in(file("common"))
.enablePlugins(...)
.settings(libraryDependencies ++= ...)
val root =
project
.in(file("."))
.enablePlugins(...)
.dependsOn(common)
.aggregate(
common,
....
)
.settings(...)
Problem
common does not compile before root, and so the compilation fails complaining that it cannot find those classes (in common)
FYI
I have tried a multitude of things that I came across when searching some information on this problem (google, documentation, github issues etc.) No luck.
sbt v1.4.9 (Play project sbt-play v2.7.9)
The build.sbt is much bigger than what you see above (dependencies, tasks etc.). Otherwise, I don't think if there's anything special or tricky about it.
Help much appreciated!
To avoid initialisation problems try declaring projects as lazy vals
lazy val common = ...
lazy val root = ...
instead of strict vals
val common = ...
val root = ...
As a side note use dependsOn to establish ordering between subprojects, and not aggregate, because aggregate will not modify classpaths.
I'd agree with Mario Galic on using lazy val. In fact, I'd recommend using lazy val at all times in build.sbt.
If there is a cycle, like common referring back to root, one technique you can use is to use LocalProject("project name"), like LocalProject("root"):
lazy val common = (project in file("common"))
.settings(
Test / test := (LocalProject("root") / Test / test).value
)
I'm trying to make what looks like a "circular dependency" in sbt (I know there has to be another way to structure the project but for the set of
constraints I'm dealing with right now, it seems like a reasonable trade-off).
I'm not 100% these dependencies should be considered "circular" given we only need core/test to know about impl. Anyways, here is what my sbt.build looks like now:
lazy val core = (project in file("core"))
.dependsOn(LocalProject("impl") % "test")
lazy val impl = (project in file("impl"))
.dependsOn(core)
Obviously, sbt complains about that in terms of Cyclic reference involving.
FWIW, I quite seamlessly expressed this kind of build in Pants. Is there a way to do that in sbt as well?
I'm working with a huge project with lots of subprojects, some of them with subprojects of their own. On top of that, I'd like some of them to be dynamic - given a List somewhere in the project build, I'd like to create one project for each of the elements.
For those reasons, having to define a lazy val for each project in build.sbt is very cumbersome. Is there other way to declare projects, like a addProject-like method we could call anywhere? Is there some SBT plugin that helps with that?
Sbt uses macros to turns top level vals into projects, so I don't think you will be able to escape that part. However, you can define all you build in Project => Project functions: (note that you also composability "for free" with function composition)
def myConf: Project => Project =
_.enablePlugins(ScalaJSPlugin)
.settings(scalaVersion := "2.12.0")
Then simply use project.configure(myConf) for single line project definitions:
lazy val subProject1 = project.configure(myConf)
lazy val subProject2 = project.configure(myConf)
lazy val subProject3 = project.configure(myConf)
lazy val subProject4 = project.configure(myConf)
...
I have a bunch of sub-project that I define as follows (actually generated by a project/meta.sbt):
lazy val Top = (project in file("."))
.aggregate(common, p1, p2, tests, scripts)
.dependsOn(common, p1, p2, tests, scripts)
lazy val common = project
lazy val p1 = project.dependsOn(common % "compile->compile;test->test")
lazy val p2 = project.dependsOn(common % "compile->compile;test->test")
lazy val tests = project.dependsOn(common % "compile->compile;test->test")
lazy val scripts = project
AFAICT (so maybe that's wrong), both the aggregate and the dependsOn at the top make sense: the first to run tasks in all sub-projects, and the second to make it convenient to debug stuff.
Now, the thing is that tests contains just a bunch of tests, and scripts is a script that is used as a publishing step -- so I need to somehow make it avoid making jar files only for them. I still want compile/test/etc to be aggregated including them.
My understanding from the docs is that this should be done by setting the aggregate key in Top, and indeed adding something like
.settings(aggregate in clean:= false)
prevents clean from being aggregated -- but I want to do that for package and I don't want to drop it completely, just not package those two sub-projects. Using something like aggregate in (tests, packageBin) is my best guess, but that doesn't seem to make an impression on SBT: looks like packageBin is wrong, and also combining it with tests doesn't work.
Is that possible? Actually, given that SBT is just a tiny bit overengineered, it's always possible, so rephrase: How can I do that?
Update:
Thanks to this answer I found a workaround -- add:
.settings(Keys.`package` := file(""))
to the definitions of subprojects that shouldn't be packaged. That works for avoiding generation of jar files, but it's obviously not an answer for the actual question (which I'm probably going to run into again).
I am building compound API docs using the sbt-unidoc plugin. I am linking to various projects on GitHub using sbt's RootProject constructor with URIs.
One of these projects has mutually exclusive sub-projects:
val foo = RootProject(uri("git://github.com/Foo/Bar.git#v1.0.0"))
That is, in this project foo, there are two sub-modules foo-db1 and foo-db2, which contain the same types but are built against different dependency library versions.
Now when I try to run unidoc, it fails because a type is defined twice from unidoc's perspective. From the documentation, I can see there is a filter function:
unidocProjectFilter in (ScalaUnidoc, unidoc) := inAnyProject -- inProjects(app)
But how do I create an identifier for a sub-module from my RootProject?
In other words, how would I do this:
unidocProjectFilter in (ScalaUnidoc, unidoc) := inAnyProject --
inProjects(foo).SELECT_SUB_PROJECT(foo-v1)
?
The answer is found in this entry: Instead of using RootProject, one creates a number of ProjectRef instances.
import UnidocKeys._
val fooURI = uri("git://github.com/Foo/Bar.git#v1.0.0")
val fooDb1 = ProjectRef(fooURI, "foo-db1") // sub-module I don't want
val fooDb2 = ProjectRef(fooURI, "foo-db2") // sub-module I want
val root = (project in file("."))
.settings( ... )
.settings(unidocSettings: _*)
.settings(site.settings ++ ghpages.settings: _*)
.settings(
unidocProjectFilter in (ScalaUnidoc, unidoc) :=
inAnyProject -- inProjects(fooDb1)
)
.aggregate(fooDb2)
Note that putting only fooDb2 in the aggregate and leaving fooDb1 out, does not have any effect. It seems that unidoc always includes all sub-modules, no matter, so one has to use the unidocProjectFilter to explicitly remove projects, even if they don't appear in the aggregate.