23
votes

I am trying to create a Scala application consisting of a library project (let's call this common), a Thrift server project (let's call this server) and a Play web application project (hereinafter known as web). All three are written in Scala and built with sbt.

My project structure looks like this:

myproject/
-common/
  ...
-server/
  ...
-web/
  -app/
  -conf/
  ...
-project/
  -Build.scala
  -build.properties
-build.sbt

My build.sbt file (simplified a bit) looks like this:

import play.Project._

name := "myproject"

version := "1.0-SNAPSHOT"

lazy val common = project

lazy val web = project
    .settings(playScalaSettings: _*)
    .dependsOn(common)

lazy val server = project
    .dependsOn(common)

lazy val root = project.in(file("."))
    .aggregate(common, web, server)

The problem with this is that the root project is not a Play project, so the play command does not work (it errors out with

java.lang.RuntimeException: */*:playRunHooks is undefined.
at scala.sys.package$.error(package.scala:27)

I can fix this by making the root project look like a Play project if I insert the line playScalaSettings after the version line in the SBT file, but then I have another problem: the play run command attempts to run the root project, not the web subproject. Obviously the play run command does not work when run in the web subdirectory since there is no SBT file there to describe the project and its dependencies.

I am looking for a solution that allows me to retain this project structure (meaning the Play project is one of many sub-projects in my application), while retaining all the Play framework hotness like hot updates when code changes (even code in dependent libraries like common).

I thought I found the solution by running play to get the interactive console, and then

 project web
 run

That works, but it doesn't work on the command line. play web/run for some reason runs the root level run command, which as noted above does not work because the root application is not a Play application.

Note: A similar question was asked before in the context of Play 2.0 at Play Framework as SBT Non-Root Module, but the answer is not satisfactory, nor do I believe it is still correct as of Play 2.2.

2

2 Answers

15
votes

if

 play (entering shell)
 project web
 run

works, then you can make it work from the command line:

 play "project web" "run"

You can do in command line whatever you can do in the shell.

I have the same project structure and it is the way to do that works fine for me.

By the way, I don't think the hot reload stuff is related to Play. It is incremental compilation that is provided by SBT which is used by Play. The play command is just some hacked SBT launcher I think.

The following command works fine for me:

 sbt "project web" "run"

And it starts the Play project with hot reload.


I think you can even use

 sbt "project web" "~run"

Which will try to recompile each time you change a source file, instead of waiting for a browser refresh, and will make win some time.

0
votes

I think this is a bug in Play 2.2, so I reported it as Error "/:playRunHooks is undefined" on running as web/run. Also it seems to have been fixed in 2.3.x, so likely it won't be fixed for 2.2. Here's a bit nerdy workaround I came up with:

lazy val root = (project in file(".")).
  settings(
    playRunHooks := Nil,
    playInteractionMode := play.PlayConsoleInteractionMode,
    playDefaultPort := 9000,
    playMonitoredFiles := (Def.taskDyn {
      val s = state.value
      s.history.current.split("/").headOption match {
        case Some(ref) =>
          Def.task {
            (playMonitoredFiles in LocalProject(ref)).value
          }
        case _ =>
          Def.task {
            playMonitoredFiles.value
          }
      } 
    }).value
  )