Play Framework: Multi Module Project - scala

I have created a multi module project with the following structure:
my_app
|__app
|__conf
|__public
|__project
| |__CommonBuild.scala
|__core
| |__src/main/scala
| |__src/test/scala
| |__build.sbt
|__build.sbt
I notice 2 issues:
In development mode when I do activator run and I make changes to my core module I see that it recompiles but when running it is picking up published core-[ver].jar dependency instead of the module locally.
In IntelliJ IDEA, I see the same issue. Under project structure of my_app I do see core added as both a module and a jar.
CommonBuild.scala:
object CommonBuild {
val settings: Seq[Setting[_]] = Seq(
organization = "com.myorg",
scalaVersion = "2.11.8",
transitiveClassifiers in Global := Seq(Artifact.SourceClassifier),
resolvers ++= Seq(
"scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"
),
aggregate in update := false,
parallelExecution in Test := false,
Keys.fork in Test := false,
libraryDependencies ++= Seq(/*all dependencies here*/)
)
}
core/build.sbt:
name := "core"
CommonBuild.settings
unmanagedResourceDirectories in Compile += baseDirectory.value / "src" / "main" / "resources"
unmanagedResourceDirectories in Test += baseDirectory.value / "src" / "test" / "resources"
publishMavenStyle := true
publishArtifact in Test := true
my_app/build.sbt:
CommonBuild.settings
lazy val my_app =
(project in file(".")).
enablePlugins(PlayScala)
.aggregate(core)
.dependsOn(core % "test->test;compile->compile")
lazy val core = project
name := "my-app"
routesGenerator := InjectedRoutesGenerator
If I do not publish or publishLocal the core artifact, my_app does not pick up the core module to compile and throws a bunch of compilation errors.
How do I make sure that both activator and IntelliJ IDEA use core module itself as the dependency instead of the artifact produced by it?

Related

sbt - deep child modules

I'm new to sbt and I want to reproduce a complex project structure with many nested modules.
For example, I have the following structure:
.
build.sbt
|_web
|_api
|_dto
|_domain
build.sbt is as follows:
name := "myProject"
version := "1.0"
scalaVersion := "2.12.4"
resolvers += Resolver.sonatypeRepo("public")
libraryDependencies += "com.typesafe.play" %% "play" % "2.6.10"
lazy val commonSettings = Seq(
organization := "com.example",
version := "0.1",
scalaVersion := "2.12.4"
)
// root module
lazy val root = (project in file("."))
.aggregate(domain, web)
// domain module
lazy val domain = project.settings(commonSettings)
// web module
lazy val web = project.settings(
commonSettings,
libraryDependencies := Seq("com.typesafe.play" %% "play" % "2.6.10"),
name := "myproj-web"
).dependsOn(domain)
// web api module
lazy val webApi = (project in file("./web/api")).settings(
commonSettings,
libraryDependencies := Seq("com.typesafe.play" %% "play" % "2.6.10"),
name := "myproj-web-api"
).dependsOn(domain)
First problem I have is I can't access my libraries in web/api, though I can in web/.
Second problem is that I don't like file("./web/api"). Is it possible to make sbt understand nested folders as it understands plain folders (like web or domain).
Also, is it possible then to have build.sbt for each module. For example, for web to contain build file for api and dto, but preserving aggregations and ability to call build only on root project and have all the rest projects be built.

Intellij SBT project: How to change the content roots?

This is the current content root configuration in my project:
However, I want the "scala" directory to be the actual test content root, and not the directory named "test". If I modify it, I get the warning that "Module is imported from Sbt. Any changes in its configuration will may be lost after re-importing." (and, indeed, they are).
Unfortunately, I couldn't find where in my Build.scala file (or any other file) this configuration is declared. What I can do to once and for all convince IntelliJ that "scala" is the correct test content root?
This is my Build.scala file (this is a Play 2.5.4 project if it matters):
import play.routes.compiler.StaticRoutesGenerator
import play.sbt.PlayScala
import play.sbt.routes.RoutesKeys._
import sbt.Keys._
import sbt._
object Build extends Build {
val main = Project("Mp3Streamer", file(".")).enablePlugins(PlayScala).settings(
scalaVersion := "2.11.8",
version := "1.0-SNAPSHOT",
addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.0" cross CrossVersion.full),
libraryDependencies ++= Seq(
// a bunch of dependencies
),
resolvers += Resolver.mavenLocal,
javaOptions ++= Seq("-Xmx4000M", "-Xms2024M", "-XX:MaxPermSize=2000M"),
routesGenerator := StaticRoutesGenerator
)
}
By adding scalaSource in Test := baseDirectory.value / "test" "/scala", to my Build.scala file, I've been able to make the "scala" folder a test source, but the parent "test" folder was still also a test source:
As far as I could tell, this is a setting inherited from Play, since if I removed the .enablePlugins(PlayScala) code, the "test" folder stops being a test source. Following the instructions in https://www.playframework.com/documentation/2.5.x/Anatomy#Default-SBT-layout, I disabled the play layout, and then manually added the source and resource directories, which I copied from https://github.com/playframework/playframework/blob/master/framework/src/sbt-plugin/src/main/scala/play/sbt/PlayLayoutPlugin.scala#L9, only modifying the test source, and adding my own resource folders. My modified Build.scala file is now:
val main = Project("Mp3Streamer", file("."))
.enablePlugins(PlayScala)
.disablePlugins(PlayLayoutPlugin)
.settings(
target := baseDirectory.value / "target",
sourceDirectory in Compile := baseDirectory.value / "app",
// My change
sourceDirectory in Test := baseDirectory.value / "test" / "scala",
resourceDirectory in Compile := baseDirectory.value / "conf",
scalaSource in Compile := baseDirectory.value / "app",
// My change
scalaSource in Test := baseDirectory.value / "test" / "scala",
// I've added this resource
resourceDirectory in Test := baseDirectory.value / "test" / "resources",
javaSource in Compile := baseDirectory.value / "app",
sourceDirectories in(Compile, TwirlKeys.compileTemplates) := Seq((sourceDirectory in Compile).value),
sourceDirectories in(Test, TwirlKeys.compileTemplates) := Seq((sourceDirectory in Test).value),
// sbt-web
sourceDirectory in Assets := (sourceDirectory in Compile).value / "assets",
sourceDirectory in TestAssets := (sourceDirectory in Test).value / "assets",
resourceDirectory in Assets := baseDirectory.value / "public",
// Native packager
sourceDirectory in Universal := baseDirectory.value / "dist",
// Everything else is the same as the original Build.scala file
Honestly, this feels so hacky that I'll probably end up modifying my directory structure to match Play's default... But it's the principle that counts!

Can't resolve docker related sbt tags

I'm trying to add sbt-docker to my sbt build of my play website but I'm running into an issue. For some reason none of the docker related stuff on the bottom can resolve.
project/plugins.sbt
logLevel := Level.Warn
resolvers ++= Seq(
"Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
)
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.5.9")
build.sbt
name := "personal_site"
version := "1.1"
lazy val `personal_site` = (project in file(".")).enablePlugins(PlayScala,DockerPlugin)
scalaVersion := "2.11.7"
libraryDependencies ++= Seq( jdbc , cache , ws , specs2 % Test )
unmanagedResourceDirectories in Test <+= baseDirectory ( _ /"target/web/public/test" )
resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases"
dockerfile in docker := {
val targetDir = "/usr/src"
new Dockerfile {
from("flurdy/activator")
//More goes here
}
}
imageNames in docker := Seq(
// Sets the latest tag
ImageName(s"${name.value}:latest"),
// Sets a name with a tag that contains the project version
ImageName(
namespace = None,
repository = name.value,
tag = Some("v" + version.value)
)
)
Here's an image of what it looks like in IntelliJ
I've also tried adding addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.4.0") to my project/plugins.sbt but I get this error about DockerPlugin being imported twice.
~/Sync/Projects/Programming/Personal_Site (master ✘)✹ ᐅ sbt clean
[info] Loading project definition from /home/ryan/Sync/Projects/Programming/Personal_Site/project
/home/ryan/Sync/Projects/Programming/Personal_Site/build.sbt:5: error: reference to DockerPlugin is ambiguous;
it is imported twice in the same scope by
import _root_.sbtdocker.DockerPlugin
and import _root_.com.typesafe.sbt.packager.docker.DockerPlugin
lazy val `personal_site` = (project in file(".")).enablePlugins(PlayScala,DockerPlugin)
Try changing your build.sbt config to this.
lazy val root = (project in file(".")).enablePlugins(sbtdocker.DockerPlugin, PlayScala)
It removes the ambiguity by using the full name to DockerPlugin, since sbt-native-packager uses the same name for its Docker plugin I believe.
Maybe worth raising a Github issue with the author's repo so they can document it in the project docs.

Trigger SBT assembly from other subproject

I have a root project containing 3 subprojects plus sbt config files and nothing else. 2 main subprojects are called server and backend, the other is called common and is dependency of both main projects. server is PlayFramework project. backed project is configured to generate assembly jar into resources directory of server.
The jar is generated correctly and server is able to see it, but I don't know how to run assembly task from backend when server is compiled(i.e. I want the server to depend on assembly of backend.jar)
/* [...] */
lazy val commonSettings = Seq(
version := "0.1",
organization := "org.example",
scalaVersion := "2.11.7"
)
lazy val server = (project in file("server")).enablePlugins(PlayJava).settings(commonSettings: _*).settings(
name := """example""",
libraryDependencies ++= Seq(
/* [...] */
),
/* [...] */
unmanagedResourceDirectories in Compile += { baseDirectory.value / "resources" }
).dependsOn(common)
lazy val backend = (project in file("backend")).settings(commonSettings: _*).settings(
assemblyJarName in assembly := "backend.jar",
assemblyOutputPath in assembly := server.base / "resources/backend.jar",
libraryDependencies := Seq(
)
).dependsOn(common)
lazy val common = (project in file("common")).settings(commonSettings: _*)
onLoad in Global := (Command.process("project server", _: State)) compose (onLoad in Global).value
Thanks to comment by #pfn I got it working. One thing I needed to do was to insert this line in server subproject settings and change server to Compile, so it is now:
(compile in Compile) <<= (compile in Compile) dependsOn (assembly in backend)

How to set up jacoco4sbt to process classes in main and submodules in Play?

I'm having some problems to make jacoco4sbt working with my Play 2.3.4 project.
My project is composed of 3 submodules: common, api and frontend and has no code in the app root folder. Now when I run Jacoco it does not find the submodules classes.
Inspecting target/scala-VERSION/classes I only find some routing classes (which in fact is the only code I have in my "root" project, but I was expecting that because I aggregate all those projects the classes would be there).
If I copy the classes from MODULE_NAME/target/scala-VERSION/classes to target/scala-VERSION/classes and then run Jacoco I get the expected result.
So what is the best way to make it work? I can't find any config in jacoco4sbt to specify additional classes locations.
My build.sbt file
import Keys._
// Dummy value to deal with bug in sbt 0.13.5
val k = 0
name := "PlayApp"
version := "0.5.0"
// omitted resolvers part
scalaVersion := "2.10.4"
libraryDependencies ++= Seq(
"com.edulify" %% "play-hikaricp" % "1.5.0" exclude("com.jolbox", "bonecp"),
"com.novocode" % "junit-interface" % "0.11" % "test"
)
lazy val common = project.in(file("common")).enablePlugins(PlayJava)
lazy val frontend = project.in(file("frontend")).enablePlugins(PlayJava).dependsOn(common)
lazy val api = project.in(file("api")).enablePlugins(PlayJava).dependsOn(common)
lazy val main = project.in(file(".")).enablePlugins(PlayJava)
.aggregate(frontend, api).dependsOn(frontend, api)
parallelExecution in Test := false
javaOptions in Test += "-Dconfig.resource=test.conf"
jacoco.sbt
import de.johoop.jacoco4sbt._
import JacocoPlugin._
jacoco.settings
Keys.fork in jacoco.Config := true
parallelExecution in jacoco.Config := false
jacoco.outputDirectory in jacoco.Config := file("target/jacoco")
jacoco.reportFormats in jacoco.Config := Seq(XMLReport("utf-8"), HTMLReport("utf-8"))
jacoco.excludes in jacoco.Config := Seq("views*", "*Routes*", "controllers*routes*", "controllers*Reverse*", "controllers*javascript*", "controller*ref*")
javaOptions in jacoco.Config += "-Dconfig.resource=test.conf"
Add jacoco.sbt to every subproject with the following content:
jacoco.settings
p.s. I've been looking for ways to convince sbt to have jacoco.settings applied to every subproject in the top-level root build.sbt, but to no avail.