Strips Play 2.x build process down to the essentials, reducing build times to absolute minimum. Provides basic guidelines for reducing build times via your own code, hardware, and jvm settings.
Isn't it obvious? The freedom of expression that Scala provides is not free, and some aspects of this mostly fantastic web framework leave something to be desired (e.g. routing and assets compilation)
Play 2 Scala applications on *nix systems, modify accordingly for Windows.
- Preserves sbt dependencies cache
moves sbt cache config & update dependencies directories out of clean's reach -- play> clean by default removes sbt deps dirs, which is why on EVERY clean/compile a deps update check is made, needlessly slowing down the build, particularly for sub projects where the deps check is made for each project.
- Moves sbt compile target into RAM (optional*)
moves sbt compile target from project directory to tmpfs mounted directory (I use /tmp/sbt). Only shaves off a few seconds from the build time, but more importantly, it offloads I/O thrashing from your precious SSD into RAM.
- Integrates sbteclipse
generates sbteclipse settings that allow for the above 2 steps to seamlessly occur with a simple play> eclipse
- Disables built-in assets compilation (optional)
using a 3rd party assets build system (e.g. Bower + GruntJS) allows for rapid fire code-change/browser-refresh cycles, something that as of Play 2 is simply not happening (assets compilation continues to be very slow). To re-enable built-in assets compilation just comment/remove the "lessEntryPoints" line from included Build.scala.
* to keep compile target default, comment out "eclipseSettings" in Settings.scala; i.e.
protected def _settings: Seq[Setting[_]] = {
superSettings ++ ivySettings //++ eclipseSettings
}
Copy project/ Transformers.scala and Settings.scala to your main project/ directory. If moving sbt compile target to RAM is desired, modify "tmpfs" val in Transformers.scala with whatever variable name you set for the linked resource in Eclipse*, or keep "tmpfs" default value, "TARGET_TMPFS"
Compile target in RAM approach assumes that you have a tmpfs mounted directory; if not, add to your /etc/fstab (save, reboot):
tmpfs /dev/shm tmpfs defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
Run a play> update to seed sbt deps cache (will be presevered across future play> clean-s); then do a clean/compile to populate tmpfs (or default) compile target. Finally, generate eclipse project settings: play> eclipse (skipParents=false if you want aggregator project included). Done, enjoy ;-)
* to create a linked resource in Eclipse do the following:
-
create path variable:
preferences > (in search field) type "linked" > click Linked Resources > click New > type a name (i.e. the value you provided for Transformers.scala "tmpfs" val) then browse to your tmpfs mounted directory (e.g. /tmp/sbt)
-
create linked resource:
right click any folder in your main (aggregator project) and do: file > new > advanced > check Link to filesystem > click Variables > select path tmpfs variable you created in step 1.
- Use sub projects (this is a huge WIN)
- No need to .aggregate everything; for not-often-changing sub projects .dependsOn is enough
- Consider replacing cake pattern with design by contract; i.e. cake if you really need it.
- Avoid excessive use of self types
- Specifying return type means less work for scalac to do
- Implicits are quite useful, but don't go overboard
- Be secretive: use private def and friends to hide dependent code from scalac
- Generally adhere to source file per class/type convention
- Local method hacking: if method params & return type of a method remain unchanged, you can do anything in the method body and scalac will be none the wiser (i.e. will not trigger recompilation of dependent code).
For my setup (Dell Precision M4700 | 3840QM 3.6ghz 8mb cache | 256gb SSD | 32gb RAM) have the following java opts in ~/.bash_profile:
JAVA_OPTS=-Xss8m -Xms512m -Xmx2048m -XX:MaxPermSize=512m -XX:ReservedCodeCacheSize=128m -XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC
These settings (again, for my setup) result in optimal build times. -Xss option set to 1m,2m,4m were 10-20 seconds slower (4m, for some reason being the slowest).
Setting a large -XX:ReservedCodeCacheSize also slowed down the build. Experiment, find out what works best for you.
- Fast CPU (i7 extreme if you have the $$)
- Fast disk
- Enough RAM
After refactoring into a sub project enabled build and removing some fairly static core sub projects from play.Project .aggregate(...), cold clean/compile build times have been reduced from 140s to reproducable sub 60s (often in the low 50s).
The big win in sub projects are of course incremental builds, where routes are dispersed across sub projects so you don't get hammered with the dreaded, compiling 1 scala source...followed by compiling 3/4 of entire project.
For warmed up jvm getting 13s clean/compile -- liking that ;-)