Cyclic dependencies between main-test modules in sbt - scala

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?

Related

sbt: Dependent project does not compile first

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
)

SBT multi-project build without using lazy vals

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)
...

SBT: how to exclude one sub-project from one aggregated task

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).

SBT sub projects depending on each other

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.

Run custom task automatically before/after standard task

I often want to do some customization before one of the standard tasks are run. I realize I can make new tasks that executes existing tasks in the order I want, but I find that cumbersome and the chance that a developer misses that he is supposed to run my-compile instead of compile is big and leads to hard to fix errors.
So I want to define a custom task (say prepare-app) and inject it into the dependency tree of the existing tasks (say package-bin) so that every time someone invokes package-bin my custom tasks is run right before it.
I tried doing this
def mySettings = {
inConfig(Compile)(Seq(prepareAppTask <<= packageBin in Compile map { (pkg: File) =>
// fiddle with the /target folder before package-bin makes it into a jar
})) ++
Seq(name := "my project", version := "1.0")
}
lazy val prepareAppTask = TaskKey[Unit]("prepare-app")
but it's not executed automatically by package-bin right before it packages the compile output into a jar. So how do I alter the above code to be run at the right time ?
More generally where do I find info about hooking into other tasks like compile and is there a general way to ensure that your own tasks are run before and after a standard tasks are invoked ?.
Extending an existing task is documented the SBT documentation for Tasks (look at the section Modifying an Existing Task).
Something like this:
compile in Compile <<= (compile in Compile) map { _ =>
// what you want to happen after compile goes here
}
Actually, there is another way - define your task to depend on compile
prepareAppTask := (whatever you want to do) dependsOn compile
and then modify packageBin to depend on that:
packageBin <<= packageBin dependsOn prepareAppTask
(all of the above non-tested, but the general thrust should work, I hope).
As an update for the previous answer by #Paul Butcher, this could be done in a bit different way in SBT 1.x versions since <<== is no longer supported. I took an example of a sample task to run before the compilation that I use in one of my projects:
lazy val wsdlImport = TaskKey[Unit]("wsdlImport", "Generates Java classes from WSDL")
wsdlImport := {
import sys.process._
"./wsdl/bin/wsdl_import.sh" !
// or do whatever stuff you need
}
(compile in Compile) := ((compile in Compile) dependsOn wsdlImport).value
This is very similar to how it was done before 1.x.
Also, there is another way suggested by official SBT docs, which is basically a composition of tasks (instead of dependencies hierarchy). Taking the same example as above:
(compile in Compile) := {
val w = wsdlImport.value
val c = (compile in Compile).value
// you can add more tasks to composition or perform some actions with them
c
}
It feels like giving more flexibility in some cases, though the first example looks a bit neater, as for me.
Tested on SBT 1.2.3 but should work with other 1.x as well.