Search code examples
scalaplayframeworksbt

How to setup sbt/scala/play multi-module project which will work fine with Intellij scala plugin


I'm setting up a new multi-module project (sbt/scala/play/IntejjiJ) and I would like to have two things:

  1. A multi-module project with one build.sbt file with this layout
project
 |_Dependencies.scala
 |_plugin.sbt
modules
 |_adapters
   |_api (Play REST API)
     |_app
     |_conf
   |_infrastructure (bare bone scala)
     |-src/main/scala
 |_application (bare bone scala)
   |_src/main/scala
 |_domain (bare bone scala)
   |_src/main/scala
 |_query (bare bone scala)
   |_src/main/scala 
build.sbt
  1. I would like to be able to use IntelliJ Play2 plugin (Run/Debug Configuration)

So far, I'm getting the following error when I run the application using Play2 Run/Debug Configuration settings:

[error] java.lang.RuntimeException: No main class detected.
[error]     at scala.sys.package$.error(package.scala:26)
[error] (Compile / bgRun) No main class detected.
[error] Total time: 2 s, completed Jun 1, 2019 11:21:31 PM

Here is what I have so far:

build.sbt

import Dependencies._

lazy val commonSettings = Seq(
  organization := "com.borkke.rally",
  version := "0.1.0-SNAPSHOT",
  scalaVersion := "2.12.8",
  scalacOptions := Seq(
    "-deprecation",
    "-feature"
  ),
  libraryDependencies ++= CommonDependencies
)


//PROJECTS
lazy val rally = project
  .in(file("."))
  .aggregate(domain,application,query,api,infrastructure)
  .settings(
    name := "rally",
    commonSettings,
    publishArtifact := false
  )

lazy val api = project
  .in(file("modules/adapter/api"))
  .enablePlugins(PlayScala)
  .dependsOn(domain,application,query,infrastructure)
  .settings(
    name := "api",
    commonSettings,
    libraryDependencies ++= ApiDependencies
  )

lazy val domain = project
  .in(file("modules/domain"))
  .settings(
    name := "domain",
    commonSettings
  )

lazy val application = project
  .in(file("modules/application"))
  .dependsOn(domain)
  .settings(
    name := "application",
    commonSettings
  )

lazy val query = project
  .in(file("modules/query"))
  .settings(
    name := "query",
    commonSettings
  )

lazy val infrastructure = project
  .in(file("modules/adapter/infrastructure"))
  .dependsOn(domain)
  .settings(
    name := "infrastructure",
    commonSettings,
    libraryDependencies ++= InfrastructureDependencies
  )

Dependencies.scala

import sbt._
import play.sbt.PlayImport._

object Dependencies {
  private val scalatest_version = "3.0.5"
  private val v2Db_version = "1.4.198"
  private val logback_version = "5.3"
  private val play_version = "2.7.2"
  private val cassandra_driver_version = "3.7.1"
  private val postgresql_driver_version = "42.2.5"
  private val kafka_client_version = "2.2.0"

  private val scalatest = "org.scalatest" %% "scalatest" % scalatest_version
  private val scalatic = "org.scalactic" %% "scalactic" % scalatest_version
  private val h2Db = "com.h2database" %% "h2" % v2Db_version
  private val logback = "net.logstash.logback" % "logstash-logback-encoder" % logback_version
  private val play = "com.typesafe.play" %% "play" % play_version
  private val cassandra_driver = "com.datastax.cassandra" % "cassandra-driver-extras" % cassandra_driver_version
  private val postgresql_driver = "org.postgresql" % "postgresql" % postgresql_driver_version
  private val kafka_client = "org.apache.kafka" %% "kafka" % kafka_client_version

  lazy val CommonDependencies = Seq(scalatic, scalatest % "test", logback, guice)

  lazy val InfrastructureDependencies = Seq(cassandra_driver, postgresql_driver, kafka_client)

  lazy val ApiDependencies = Seq(play)
}

plugin.sbt

logLevel := Level.Warn

//wrapper around play console.
addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.7.2")

//dependency resolver. parallel downloads
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.1.0-M11")

//shows available updates. dependencyUpdates || dependencyUpdatesReport
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.4.0")

//create one jar for application.
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.5")

//linter
addSbtPlugin("org.wartremover" % "sbt-wartremover" % "2.2.1")

Run Configuration enter image description here


Solution

  • Consider changing the build structure such that api is moved to become the root project like so:

    lazy val api = project
      .in(file("."))
      .enablePlugins(PlayScala)
      .aggregate(domain,application,query,infrastructure)
      .dependsOn(domain,application,query,infrastructure)
      .settings(
        name := "api",
        commonSettings,
        publishArtifact := false,
        libraryDependencies ++= ApiDependencies
      )
    

    This means modules/adapters/api is moved to the project root directory, and lazy val root = ... is deleted from build.sbt, such that the directory structure becomes

    .
    ├── app
    │   ├── controllers
    │   ├── filters
    │   ├── services
    │   └── views
    ├── build.sbt
    ├── conf
    │   ├── application.conf
    │   ├── logback.xml
    │   └── routes
    ├── modules
    │   ├── adapters
    │   │   └── infrastructure
    │   ├── application
    │   │   ├── src
    │   ├── domain
    │   │   ├── src
    │   └── query
    │       ├── src
    ├── project
    │   ├── Dependencies.scala
    │   ├── build.properties
    │   ├── plugins.sbt
    │   ├── project
    │   └── target
    

    This should make the Play 2 App run configuration work again, although make sure Play compiler is enabled by checking:

    Preferences | Languages & Frameworks | Play2 | Compiler | Use Play 2 compiler for this project
    

    If preferring to keep the original structure, then as a workaround, try defining sbt Task run configuration instead of Play 2 App, as documented in official docs:

    1. Run | Edit Configurations
    2. Click on the + to add a new configuration
    3. Choose sbt Task
    4. In the tasks input box put api/run
    5. Apply changes and select OK.