Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 812
Aleksandar Borkovac Avatar asked Jun 01 '19 15:06

Aleksandar Borkovac


2 Answers

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.
like image 169
Mario Galic Avatar answered Nov 15 '22 20:11

Mario Galic


Background context, feel free to skip:

The most recent version of Intellij runs into route compile issues when using sbt task run configs as defined on the Play2 documentation. So I wanted to use the Play 2 App run config as an alternative.

While Mario's solution works it isn't an option for those who cannot move base directory. Trying to use the Play 2 App run config results in a No main class detected similar to what Aleksandar experienced in the comments of the opening question.

Solution

To use Play 2 App run configs you have to:

  1. Specify projects in build.sbt as you would normally.

  2. Specify the Play2 module and the project uri to the project with your build.sbt. Unless altered in step 1, this will be the Play2 default: Project name and project root respectively.

    Setting in question found here:

    File | Settings | Languages & Frameworks | Play2

    As seen in the screenshot below, I also check Use Play 2 compiler for this project

    Project uri defined in intellij settings

  3. Create a Play 2 App run configuration and specify the Play 2 module as the module with your main class method.

    Debug port should be the one specified in your settings for your breakpoint listener. No need to change by default.

    Run config definition

  4. Run your newly created run configuration. Log output should look similar to that of running sbt via bash. Example below.

    Server starting from play 2 app

If it's failing to start, ensure that you don't have duplicate declarations of JVM params across default settings in Scala and JVM.

like image 20
L D-Williams Avatar answered Nov 15 '22 21:11

L D-Williams