[SBT 0.13.5] A hopefully constructive short story about SBT

225 views
Skip to first unread message

Mar

unread,
Sep 11, 2014, 8:11:15 PM9/11/14
to simple-b...@googlegroups.com

Let me start by saying that I quite like SBT, in theory.

In practice, the threshold is steep, or maybe the documentation simply not suffucient to explain or ratify why things work as they do. Anyway, after having sunk 10+ hours into trying to set up a simple project in a manner that to me would seem intuitive, I’m left feeling frustrated and frankly quite stupid.

I will use my project as an example. What I am trying to achieve is the following structure

root-project
 '- core
 '- admin-web (Play-project, depends on core)
 '- user-web (Play-project, depends on core)

I create this structure by issuing

mkdir root-project
cd root-project
mkdir core
activator new admin-web play-scala
activator new user-web play-scala

My reason for having this structure is I want the two web-artifacts to be individually deployable. Seems reasonable enough, I think.

Running sbt projects gives the following output

[info] Loading global plugins from /Users/martin/.sbt/0.13/plugins
[info] Set current project to root-project (in build file:/Users/martin/dev/scala/root-project/)
[info] In file:/Users/martin/dev/scala/root-project/
[info]      * root-project

SBT doesn’t look for sub-projects on its own. That’s ok I guess; I need to define the web project’s dependency on core anyhow. Still, I would expect it to consider sub-projects part of the build by default.

I add a build.sbt-definition at root-project/build.sbt:

name := "my project"

scalaVersion := "2.11.2"

lazy val root = project in file(".")

lazy val core = project

lazy val `admin-web`, `user-web` = project.dependsOn(core)

And running SBT:

martin@mbp ~/dev/scala/root-project]$ sbt
[info] Loading global plugins from /Users/martin/.sbt/0.13/plugins
/Users/martin/dev/scala/root-project/user-web/build.sbt:5: error: not found: value PlayScala
lazy val root = (project in file(".")).enablePlugins(PlayScala)
                                                     ^
sbt.compiler.EvalException: Type error in expression
    at [stacktrace of death]

This is totally unintuitive to me. Both Play-projects have their own plugin dependencies declared in their corresponding projects/-directories but for some reason I am forced to pull it out to the root-project level. Why? Shouldn’t it be turtles all the way down?

So I copy the projects-folder up one level from one of the web projects (cp -R admin-web/project .) and add the dependencies to my build.sbt

lazy val `admin-web`, `user-web` = project.dependsOn(core).enablePlugins(PlayScala)

Let’s try it again

[martin@mbp ~/dev/scala/root-project]$ sbt projects
[info] Loading global plugins from /Users/martin/.sbt/0.13/plugins
[info] Loading project definition from /Users/martin/dev/scala/root-project/project
[info] Set current project to my project (in build file:/Users/martin/dev/scala/root-project/)
[info] In file:/Users/martin/dev/scala/root-project/
[info]        admin-web
[info]        core
[info]      * root
[info]        user-web

Looks good! Let’s try running one of the projects

[martin@mbp ~/dev/scala/root-project]$ sbt 'admin-web/run'
[info] Loading global plugins from /Users/martin/.sbt/0.13/plugins
[info] Loading project definition from /Users/martin/dev/scala/root-project/project
[info] Set current project to my project (in build file:/Users/martin/dev/scala/root-project/)
[info] Updating {file:/Users/martin/dev/scala/root-project/}admin-web...
[info] Resolving core#core_2.11;0.1-SNAPSHOT ...
[warn]     module not found: core#core_2.11;0.1-SNAPSHOT
[warn] ==== local: tried
[warn]   /Users/martin/.ivy2/local/core/core_2.11/0.1-SNAPSHOT/ivys/ivy.xml
[warn] ==== public: tried
[warn]   http://repo1.maven.org/maven2/core/core_2.11/0.1-SNAPSHOT/core_2.11-0.1-SNAPSHOT.pom
[warn] ==== Typesafe Releases Repository: tried
[warn]   http://repo.typesafe.com/typesafe/releases/core/core_2.11/0.1-SNAPSHOT/core_2.11-0.1-SNAPSHOT.pom
[info] Resolving jline#jline;2.11 ...
[warn]     ::::::::::::::::::::::::::::::::::::::::::::::
[warn]     ::          UNRESOLVED DEPENDENCIES         ::
[warn]     ::::::::::::::::::::::::::::::::::::::::::::::
[warn]     :: core#core_2.11;0.1-SNAPSHOT: not found
[warn]     ::::::::::::::::::::::::::::::::::::::::::::::
sbt.ResolveException: unresolved dependency: core#core_2.11;0.1-SNAPSHOT: not found

Say what? Ok, so the core-directory is empty but the declaration is right there in my build.sbt. Why is SBT looking for this artifact on Maven and Ivy?

To get the ball rolling I decide to remove the dependency on the core-project for now, then

[martin@mbp ~/dev/scala/root-project]$ sbt 'admin-web/run'
[info] Loading global plugins from /Users/martin/.sbt/0.13/plugins
[info] Loading project definition from /Users/martin/dev/scala/root-project/project
[info] Set current project to my project (in build file:/Users/martin/dev/scala/root-project/)
[info] Updating {file:/Users/martin/dev/scala/root-project/}admin-web...
[info] Resolving jline#jline;2.11 ...
[info] Done updating.

--- (Running the application from SBT, auto-reloading is enabled) ---

[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)

Looks good! A quick curl to make sure it’s up and running..

> curl localhost:9000

[error] application -

! Internal server error, for (GET) [/] ->

java.util.NoSuchElementException: None.get
    at scala.None$.get(Option.scala:313) ~[scala-library-2.11.1.jar:na]
    at scala.None$.get(Option.scala:311) ~[scala-library-2.11.1.jar:na]
    at play.PlayReloader$$anon$1.reload(PlayReloader.scala:88) ~[na:na]
    at play.core.ReloadableApplication$$anonfun$get$1.apply(ApplicationProvider.scala:122) ~[play_2.11-2.3.4.jar:2.3.4]
    at play.core.ReloadableApplication$$anonfun$get$1.apply(ApplicationProvider.scala:120) ~[play_2.11-2.3.4.jar:2.3.4]
[warn] play - No application found at invoker init

In all fairness this is probably a Play oddity… but why does it have to be so complicated?


Now, I’ve thought about this post for a couple of days. I’m sorry if it’s written in an unpalatable manner but I’m not quite sure how to present the issue in a friendly manner while conveying my frustration and experience of new beginner’s intuition-failing-repeatedly.

I have delved deeper than this into SBT (and Play Framework) trying to get everything set up like I want, but it seems I’m always touching on a corner-case or plain oddity. Maybe I’m just not cut out to set up build definitions..

Finally, I have a list of a few humble questions/suggestions that may be totally outrageous according to the grander scheme of things SBT. If they are I would be happy to have someone explain why they are. I have been googling for weeks trying to figure this stuff out.

  • It is stated that SBT is recursive in nature. This seems like a sane thing;
  • Based on previous bullet point, it would seem intuitive that sub-projects should be automatically collected by their parent-project without having to spell it out using lazy val declarations; inter-project dependencies would be specified using paths of project rather than name ((project in file("a")) dependsOn (project in file("b")) (but it should maybe be called path rather than file);
  • The previous bullet point basically removes the distinction between a root-project and a sub-project; a project with no parent is a root-project;
  • This also means that with the hierarchy Foo > Bar > Baz, compiling Foo will compile Foo, Bar and Baz, while compiling Bar would compile Bar + Baz;
  • As sub-projects have no knowledge of their parent project[s], they should pull plugins from their own project/-folder (really!);
  • Sub-projects should inherit their parent project’s settings where sane, like scalaVersion/organization/scalac-/javacOptions, removing the need to specify every other key as in ThisBuild (maybe these should indeed be considered build settings and be separated from the project settings?)
  • ThisBuild seems superfluous anyway since settings can still be overridden by sub-projects. If defined in a sub-project, the setting should overrule/extend the parent’s setting just like you'd expect.
  • It would seem intuitive to stow all things build-related into a local .sbt-directory, much like git stores its references. This would get them out of the way of the actual code while still keeping them around.

Josh Suereth

unread,
Sep 13, 2014, 1:12:00 PM9/13/14
to simple-b...@googlegroups.com
So here is where intuition fails a bit:


Sbt has a notion of "builds" and "projects".  A "build" may consist of multiple "project"s.   When you do "activator new" you are creating a "build" which may contain one or more projects.

You can refernece other builds from within your build by a "URI".   This is where sbt significantly deviates from other build tools.  If you want sbt to pull in the `project` directory of a sub-directory, you need to reference it as a *build* not as a project.

You can reference any project via a "ProjectRef" in sbt.   If you want ot aggregate a bunch of `activator new`'d projects, you can do the following:


-- build.sbt ---
def subBuildProjectRef(name: String): ProjectRef = RootProject(file(name).toURI) // reference to a root project in the build located at "file/"

lazy val core = subBuildProjectRef("core")

lazy val adminWeb = subBuildProjectRef("admin-web")

lazy val userWeb =  subBuildProjectRef("admin-web")

lazy val root = project.in(file(".")).aggregate(core, adminWeb, userWeb)
--- end build.sbt ---


If you put that in the root directory things will basically work the way you want.   Sbt provides two mechanism to reference projects in other "builds":

RootProject(<uri>) // Reference the defualt/root project for another build.
ProjectRef(<uri>, <project name>) // Reference a non-root/default project for another build.


The issue with this mechanism of joining builds is actually sharing settings from a common parent.  I recommend if you need to share settings, creating an "AutoPlugin" which will inject the necessary settings into each sub-build.


I'll talk with Eugene about clarifying the documentation, but I think additionally, we may need to re-evaluate this structure after the sbt-server is out.   We're hoping, eventually, that this layout will be more of a first class citizen in sbt, with great support and easy to use, but that's the future, not the present.

- Josh





--
You received this message because you are subscribed to the Google Groups "simple-build-tool" group.
To unsubscribe from this group and stop receiving emails from it, send an email to simple-build-t...@googlegroups.com.
To post to this group, send email to simple-b...@googlegroups.com.
Visit this group at http://groups.google.com/group/simple-build-tool.
For more options, visit https://groups.google.com/d/optout.

Mar

unread,
Sep 15, 2014, 6:39:51 PM9/15/14
to simple-b...@googlegroups.com
Thank you for replying! I'll experiment and see what comes out of it :)

I actually — unknowingly of this answer — just created a stackoverflow concerning the matter, in case you (or anyone else for that matter) wants some SO-juice.

Reply all
Reply to author
Forward
0 new messages