PageRenderTime 58ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/framework/play/src/main/scala/play/sbt/SbtPlugin.scala

https://gitlab.com/kernelmadness/Play20
Scala | 1009 lines | 121 code | 38 blank | 850 comment | 2 complexity | 8937d8e2d1158a26f9f47e65b588a2ef MD5 | raw file
  1. package sbt
  2. import Keys._
  3. import jline._
  4. import play.api._
  5. import play.core._
  6. import play.console.Colors
  7. object PlayProject extends Plugin {
  8. // ----- We need this later
  9. private val consoleReader = new jline.ConsoleReader
  10. private def waitForKey() = {
  11. consoleReader.getTerminal.disableEcho()
  12. def waitEOF {
  13. consoleReader.readVirtualKey() match {
  14. case 4 => // STOP
  15. case 11 => consoleReader.clearScreen(); waitEOF
  16. case 10 => println(); waitEOF
  17. case _ => waitEOF
  18. }
  19. }
  20. waitEOF
  21. consoleReader.getTerminal.enableEcho()
  22. }
  23. // ----- Exceptions
  24. case class CompilationException(problem: xsbti.Problem) extends PlayException(
  25. "Compilation error", problem.message) with PlayException.ExceptionSource {
  26. def line = problem.position.line.map(m => m.asInstanceOf[Int])
  27. def position = problem.position.pointer.map(m => m.asInstanceOf[Int])
  28. def input = problem.position.sourceFile.map(scalax.file.Path(_))
  29. def sourceName = problem.position.sourceFile.map(_.getAbsolutePath)
  30. }
  31. case class TemplateCompilationException(source: File, message: String, atLine: Int, column: Int) extends PlayException(
  32. "Compilation error", message) with PlayException.ExceptionSource {
  33. def line = Some(atLine)
  34. def position = Some(column)
  35. def input = Some(scalax.file.Path(source))
  36. def sourceName = Some(source.getAbsolutePath)
  37. }
  38. case class RoutesCompilationException(source: File, message: String, atLine: Option[Int], column: Option[Int]) extends PlayException(
  39. "Compilation error", message) with PlayException.ExceptionSource {
  40. def line = atLine
  41. def position = column
  42. def input = Some(scalax.file.Path(source))
  43. def sourceName = Some(source.getAbsolutePath)
  44. }
  45. // ----- Keys
  46. val distDirectory = SettingKey[File]("play-dist")
  47. val playResourceDirectories = SettingKey[Seq[File]]("play-resource-directories")
  48. val confDirectory = SettingKey[File]("play-conf")
  49. val templatesImport = SettingKey[Seq[String]]("play-templates-imports")
  50. val templatesTypes = SettingKey[PartialFunction[String, (String, String)]]("play-templates-formats")
  51. val minify = SettingKey[Boolean]("minify", "Whether assets (Javascript and CSS) should be minified or not")
  52. // -- Utility methods for 0.10-> 0.11 migration
  53. def inAllDeps[T](base: ProjectRef, deps: ProjectRef => Seq[ProjectRef], key: ScopedSetting[T], data: Settings[Scope]): Seq[T] =
  54. inAllProjects(Dag.topologicalSort(base)(deps), key, data)
  55. def inAllProjects[T](allProjects: Seq[Reference], key: ScopedSetting[T], data: Settings[Scope]): Seq[T] =
  56. allProjects.flatMap { p => key in p get data }
  57. def inAllDependencies[T](base: ProjectRef, key: ScopedSetting[T], structure: Load.BuildStructure): Seq[T] = {
  58. def deps(ref: ProjectRef): Seq[ProjectRef] =
  59. Project.getProject(ref, structure).toList.flatMap { p =>
  60. p.dependencies.map(_.project) ++ p.aggregate
  61. }
  62. inAllDeps(base, deps, key, structure.data)
  63. }
  64. // ----- Play specific tasks
  65. val playCompileEverything = TaskKey[Seq[sbt.inc.Analysis]]("play-compile-everything")
  66. val playCompileEverythingTask = (state, thisProjectRef) flatMap { (s, r) =>
  67. inAllDependencies(r, (compile in Compile).task, Project structure s).join
  68. }
  69. val playPackageEverything = TaskKey[Seq[File]]("play-package-everything")
  70. val playPackageEverythingTask = (state, thisProjectRef) flatMap { (s, r) =>
  71. inAllDependencies(r, (packageBin in Compile).task, Project structure s).join
  72. }
  73. val playCopyResources = TaskKey[Seq[(File, File)]]("play-copy-resources")
  74. val playCopyResourcesTask = (baseDirectory, managedResources in Compile, resourceManaged in Compile, playResourceDirectories, classDirectory in Compile, cacheDirectory, streams) map { (b, resources, resourcesDirectories, r, t, c, s) =>
  75. val cacheFile = c / "copy-resources"
  76. val mappings = (r.map(_ ***).reduceLeft(_ +++ _) x rebase(b, t)) ++ (resources x rebase(resourcesDirectories, t))
  77. s.log.debug("Copy play resource mappings: " + mappings.mkString("\n\t", "\n\t", ""))
  78. Sync(cacheFile)(mappings)
  79. mappings
  80. }
  81. val playReload = TaskKey[sbt.inc.Analysis]("play-reload")
  82. val playReloadTask = (playCopyResources, playCompileEverything) map { (_, analysises) =>
  83. analysises.reduceLeft(_ ++ _)
  84. }
  85. val dist = TaskKey[File]("dist", "Build the standalone application package")
  86. val distTask = (baseDirectory, playPackageEverything, dependencyClasspath in Runtime, target, normalizedName, version) map { (root, packaged, dependencies, target, id, version) =>
  87. import sbt.NameFilter._
  88. val dist = root / "dist"
  89. val packageName = id + "-" + version
  90. val zip = dist / (packageName + ".zip")
  91. IO.delete(dist)
  92. IO.createDirectory(dist)
  93. val libs = {
  94. dependencies.filter(_.data.ext == "jar").map { dependency =>
  95. dependency.data -> (packageName + "/lib/" + (dependency.metadata.get(AttributeKey[ModuleID]("module")).map { module =>
  96. module.organization + "." + module.name + "-" + module.revision + ".jar"
  97. }.getOrElse(dependency.data.getName)))
  98. } ++ packaged.map(jar => jar -> (packageName + "/lib/" + jar.getName))
  99. }
  100. val start = target / "start"
  101. IO.write(start,
  102. """java "$@" -cp "`dirname $0`/lib/*" play.core.server.NettyServer `dirname $0`""" /* */ )
  103. val scripts = Seq(start -> (packageName + "/start"))
  104. val conf = Seq((root / "conf" / "application.conf") -> (packageName + "/conf/application.conf"))
  105. IO.zip(libs ++ scripts ++ conf, zip)
  106. IO.delete(start)
  107. println()
  108. println("Your application is ready in " + zip.getCanonicalPath)
  109. println()
  110. zip
  111. }
  112. val playStage = TaskKey[Unit]("stage")
  113. val playStageTask = (baseDirectory, playPackageEverything, dependencyClasspath in Runtime, target) map { (root, packaged, dependencies, target) =>
  114. import sbt.NameFilter._
  115. val staged = target / "staged"
  116. IO.delete(staged)
  117. IO.createDirectory(staged)
  118. val libs = dependencies.filter(_.data.ext == "jar").map(_.data) ++ packaged
  119. libs.foreach { jar =>
  120. IO.copyFile(jar, new File(staged, jar.getName))
  121. }
  122. val start = target / "start"
  123. IO.write(start,
  124. """|#! /usr/bin/env sh
  125. |
  126. |java "$@" -cp "`dirname $0`/staged/*" play.core.server.NettyServer `dirname $0`/..
  127. |""".stripMargin)
  128. "chmod a+x %s".format(start.getAbsolutePath) !
  129. ()
  130. }
  131. // ----- Assets
  132. def AssetsCompiler(name: String, files: (File) => PathFinder, naming: (String) => String, compile: (File, Boolean) => (String, Seq[File])) =
  133. (sourceDirectory in Compile, resourceManaged in Compile, cacheDirectory, minify) map { (src, resources, cache, min) =>
  134. import java.io._
  135. val cacheFile = cache / name
  136. val sourceFiles = files(src / "assets")
  137. val currentInfos = sourceFiles.get.map(f => f -> FileInfo.lastModified(f)).toMap
  138. val (previousRelation, previousInfo) = Sync.readInfo(cacheFile)(FileInfo.lastModified.format)
  139. if (previousInfo != currentInfos) {
  140. // Delete previous generated files
  141. previousRelation._2s.foreach(IO.delete)
  142. val generated = ((sourceFiles --- ((src / "assets") ** "_*")) x relativeTo(Seq(src / "assets"))).map {
  143. case (sourceFile, name) => sourceFile -> ("public/" + naming(name))
  144. }.flatMap {
  145. case (sourceFile, name) => {
  146. val ((css, dependencies), out) = compile(sourceFile, min) -> new File(resources, name)
  147. IO.write(out, css)
  148. dependencies.map(_ -> out)
  149. }
  150. }
  151. Sync.writeInfo(cacheFile,
  152. Relation.empty[File, File] ++ generated,
  153. currentInfos)(FileInfo.lastModified.format)
  154. // Return new files
  155. generated.toMap.values.toSeq
  156. } else {
  157. // Return previously generated files
  158. previousRelation._2s.toSeq
  159. }
  160. }
  161. val LessCompiler = AssetsCompiler("less",
  162. { assets => (assets ** "*.less") },
  163. { name => name.replace(".less", ".css") },
  164. { (lessFile, minify) => play.core.less.LessCompiler.compile(lessFile, minify) })
  165. val JavascriptCompiler = AssetsCompiler("javascripts",
  166. { assets => (assets ** "*.js") },
  167. identity,
  168. { (jsFile, minify) =>
  169. val (fullSource, minified, dependencies) = play.core.jscompile.JavascriptCompiler.compile(jsFile)
  170. (if (minify) minified else fullSource, dependencies)
  171. })
  172. val CoffeescriptCompiler = AssetsCompiler("coffeescript",
  173. { assets => (assets ** "*.coffee") },
  174. { name => name.replace(".coffee", ".js") },
  175. { (coffeeFile, minify) => (play.core.coffeescript.CoffeescriptCompiler.compile(coffeeFile), Seq(coffeeFile)) })
  176. // ----- Post compile (need to be refactored and fully configurable)
  177. val PostCompile = (sourceDirectory in Compile, dependencyClasspath in Compile, compile in Compile, javaSource in Compile, sourceManaged in Compile, classDirectory in Compile) map { (src, deps, analysis, javaSrc, srcManaged, classes) =>
  178. // Properties
  179. val classpath = (deps.map(_.data.getAbsolutePath).toArray :+ classes.getAbsolutePath).mkString(java.io.File.pathSeparator)
  180. val javaClasses = (javaSrc ** "*.java").get.map { sourceFile =>
  181. analysis.relations.products(sourceFile)
  182. }.flatten.distinct
  183. javaClasses.foreach(play.core.enhancers.PropertiesEnhancer.generateAccessors(classpath, _))
  184. javaClasses.foreach(play.core.enhancers.PropertiesEnhancer.rewriteAccess(classpath, _))
  185. // EBean
  186. try {
  187. val cp = deps.map(_.data.toURL).toArray :+ classes.toURL
  188. import com.avaje.ebean.enhance.agent._
  189. import com.avaje.ebean.enhance.ant._
  190. val cl = ClassLoader.getSystemClassLoader
  191. val t = new Transformer(cp, "debug=-1")
  192. val ft = new OfflineFileTransform(t, cl, classes.getAbsolutePath, classes.getAbsolutePath)
  193. ft.process("models/**")
  194. } catch {
  195. case _ =>
  196. }
  197. // Copy managed classes
  198. val managedClassesDirectory = classes.getParentFile / (classes.getName + "_managed")
  199. val managedClasses = (srcManaged ** "*.scala").get.map { managedSourceFile =>
  200. analysis.relations.products(managedSourceFile)
  201. }.flatten x rebase(classes, managedClassesDirectory)
  202. // Copy modified class files
  203. val managedSet = IO.copy(managedClasses)
  204. // Remove deleted class files
  205. (managedClassesDirectory ** "*.class").get.filterNot(managedSet.contains(_)).foreach(_.delete())
  206. analysis
  207. }
  208. // ----- Source generators
  209. val RouteFiles = (confDirectory: File, generatedDir: File) => {
  210. import play.core.Router.RoutesCompiler._
  211. ((generatedDir ** "routes.java").get ++ (generatedDir ** "routes_*.scala").get).map(GeneratedSource(_)).foreach(_.sync())
  212. try {
  213. (confDirectory * "routes").get.foreach { routesFile =>
  214. compile(routesFile, generatedDir)
  215. }
  216. } catch {
  217. case RoutesCompilationError(source, message, line, column) => {
  218. throw RoutesCompilationException(source, message, line, column.map(_ - 1))
  219. }
  220. case e => throw e
  221. }
  222. ((generatedDir ** "routes_*.scala").get ++ (generatedDir ** "routes.java").get).map(_.getAbsoluteFile)
  223. }
  224. val ScalaTemplates = (sourceDirectory: File, generatedDir: File, templateTypes: PartialFunction[String, (String, String)], additionalImports: Seq[String]) => {
  225. import play.templates._
  226. val templateExt: PartialFunction[File, (File, String, String, String)] = {
  227. case p if templateTypes.isDefinedAt(p.name.split('.').last) =>
  228. val extension = p.name.split('.').last
  229. val exts = templateTypes(extension)
  230. (p, extension, exts._1, exts._2)
  231. }
  232. (generatedDir ** "*.template.scala").get.map(GeneratedSource(_)).foreach(_.sync())
  233. try {
  234. (sourceDirectory ** "*.scala.*").get.collect(templateExt).foreach {
  235. case (template, extension, t, format) =>
  236. ScalaTemplateCompiler.compile(
  237. template,
  238. sourceDirectory,
  239. generatedDir,
  240. t,
  241. format,
  242. additionalImports.map("import " + _.replace("%format%", extension)).mkString("\n"))
  243. }
  244. } catch {
  245. case TemplateCompilationError(source, message, line, column) => {
  246. throw TemplateCompilationException(source, message, line, column - 1)
  247. }
  248. case e => throw e
  249. }
  250. (generatedDir ** "*.template.scala").get.map(_.getAbsoluteFile)
  251. }
  252. // ----- Play prompt
  253. val playPrompt = { state: State =>
  254. val extracted = Project.extract(state)
  255. import extracted._
  256. (name in currentRef get structure.data).map { name =>
  257. "[" + Colors.cyan(name) + "] $ "
  258. }.getOrElse("> ")
  259. }
  260. // ----- Reloader
  261. def newReloader(state: State) = {
  262. val extracted = Project.extract(state)
  263. new ReloadableApplication(extracted.currentProject.base) {
  264. // ----- Internal state used for reloading is kept here
  265. val watchFiles = {
  266. ((extracted.currentProject.base / "db" / "evolutions") ** "*.sql").get ++ ((extracted.currentProject.base / "conf") ** "*").get
  267. }
  268. var forceReload = false
  269. var currentProducts = Map.empty[java.io.File, Long]
  270. var currentAnalysis = Option.empty[sbt.inc.Analysis]
  271. def forceReloadNextTime() { forceReload = true }
  272. def updateAnalysis(newAnalysis: sbt.inc.Analysis) = {
  273. val classFiles = newAnalysis.stamps.allProducts ++ watchFiles
  274. val newProducts = classFiles.map { classFile =>
  275. classFile -> classFile.lastModified
  276. }.toMap
  277. val updated = if (newProducts != currentProducts || forceReload) {
  278. Some(newProducts)
  279. } else {
  280. None
  281. }
  282. updated.foreach(currentProducts = _)
  283. currentAnalysis = Some(newAnalysis)
  284. forceReload = false
  285. updated
  286. }
  287. def findSource(className: String) = {
  288. val topType = className.split('$').head
  289. currentAnalysis.flatMap { analysis =>
  290. analysis.apis.internal.flatMap {
  291. case (sourceFile, source) => {
  292. source.api.definitions.find(defined => defined.name == topType).map(_ => {
  293. sourceFile: java.io.File
  294. })
  295. }
  296. }.headOption
  297. }
  298. }
  299. def remapProblemForGeneratedSources(problem: xsbti.Problem) = {
  300. problem.position.sourceFile.collect {
  301. // Templates
  302. case play.templates.MaybeGeneratedSource(generatedSource) => {
  303. new xsbti.Problem {
  304. def message = problem.message
  305. def position = new xsbti.Position {
  306. def line = {
  307. problem.position.line.map(l => generatedSource.mapLine(l.asInstanceOf[Int])).map(l => xsbti.Maybe.just(l.asInstanceOf[java.lang.Integer])).getOrElse(xsbti.Maybe.nothing[java.lang.Integer])
  308. }
  309. def lineContent = ""
  310. def offset = xsbti.Maybe.nothing[java.lang.Integer]
  311. def pointer = {
  312. problem.position.offset.map { offset =>
  313. generatedSource.mapPosition(offset.asInstanceOf[Int]) - IO.read(generatedSource.source.get).split('\n').take(problem.position.line.map(l => generatedSource.mapLine(l.asInstanceOf[Int])).get - 1).mkString("\n").size - 1
  314. }.map { p =>
  315. xsbti.Maybe.just(p.asInstanceOf[java.lang.Integer])
  316. }.getOrElse(xsbti.Maybe.nothing[java.lang.Integer])
  317. }
  318. def pointerSpace = xsbti.Maybe.nothing[String]
  319. def sourceFile = xsbti.Maybe.just(generatedSource.source.get)
  320. def sourcePath = xsbti.Maybe.just(sourceFile.get.getCanonicalPath)
  321. }
  322. def severity = problem.severity
  323. }
  324. }
  325. // Routes files
  326. case play.core.Router.RoutesCompiler.MaybeGeneratedSource(generatedSource) => {
  327. new xsbti.Problem {
  328. def message = problem.message
  329. def position = new xsbti.Position {
  330. def line = {
  331. problem.position.line.flatMap(l => generatedSource.mapLine(l.asInstanceOf[Int])).map(l => xsbti.Maybe.just(l.asInstanceOf[java.lang.Integer])).getOrElse(xsbti.Maybe.nothing[java.lang.Integer])
  332. }
  333. def lineContent = ""
  334. def offset = xsbti.Maybe.nothing[java.lang.Integer]
  335. def pointer = xsbti.Maybe.nothing[java.lang.Integer]
  336. def pointerSpace = xsbti.Maybe.nothing[String]
  337. def sourceFile = xsbti.Maybe.just(new File(generatedSource.source.get.path))
  338. def sourcePath = xsbti.Maybe.just(sourceFile.get.getCanonicalPath)
  339. }
  340. def severity = problem.severity
  341. }
  342. }
  343. }.getOrElse {
  344. problem
  345. }
  346. }
  347. def getProblems(incomplete: Incomplete): Seq[xsbti.Problem] = {
  348. (Compiler.allProblems(incomplete) ++ {
  349. Incomplete.linearize(incomplete).filter(i => i.node.isDefined && i.node.get.isInstanceOf[ScopedKey[_]]).flatMap { i =>
  350. val JavacError = """\[error\]\s*(.*[.]java):(\d+):\s*(.*)""".r
  351. val JavacErrorInfo = """\[error\]\s*([a-z ]+):(.*)""".r
  352. val JavacErrorPosition = """\[error\](\s*)\^\s*""".r
  353. Project.evaluateTask(streamsManager, state).get.toEither.right.toOption.map { streamsManager =>
  354. var first: (Option[(String, String, String)], Option[Int]) = (None, None)
  355. var parsed: (Option[(String, String, String)], Option[Int]) = (None, None)
  356. Output.lastLines(i.node.get.asInstanceOf[ScopedKey[_]], streamsManager).map(_.replace(scala.Console.RESET, "")).map(_.replace(scala.Console.RED, "")).collect {
  357. case JavacError(file, line, message) => parsed = Some((file, line, message)) -> None
  358. case JavacErrorInfo(key, message) => parsed._1.foreach { o =>
  359. parsed = Some((parsed._1.get._1, parsed._1.get._2, parsed._1.get._3 + " [" + key.trim + ": " + message.trim + "]")) -> None
  360. }
  361. case JavacErrorPosition(pos) => {
  362. parsed = parsed._1 -> Some(pos.size)
  363. if (first == (None, None)) {
  364. first = parsed
  365. }
  366. }
  367. }
  368. first
  369. }.collect {
  370. case (Some(error), maybePosition) => new xsbti.Problem {
  371. def message = error._3
  372. def position = new xsbti.Position {
  373. def line = xsbti.Maybe.just(error._2.toInt)
  374. def lineContent = ""
  375. def offset = xsbti.Maybe.nothing[java.lang.Integer]
  376. def pointer = maybePosition.map(pos => xsbti.Maybe.just((pos - 1).asInstanceOf[java.lang.Integer])).getOrElse(xsbti.Maybe.nothing[java.lang.Integer])
  377. def pointerSpace = xsbti.Maybe.nothing[String]
  378. def sourceFile = xsbti.Maybe.just(file(error._1))
  379. def sourcePath = xsbti.Maybe.just(error._1)
  380. }
  381. def severity = xsbti.Severity.Error
  382. }
  383. }
  384. }
  385. }).map(remapProblemForGeneratedSources)
  386. }
  387. private def newClassloader = {
  388. new ApplicationClassLoader(this.getClass.getClassLoader, {
  389. Project.evaluateTask(dependencyClasspath in Runtime, state).get.toEither.right.get.map(_.data.toURI.toURL).toArray
  390. })
  391. }
  392. def reload = {
  393. PlayProject.synchronized {
  394. Project.evaluateTask(playReload, state).get.toEither
  395. .left.map { incomplete =>
  396. Incomplete.allExceptions(incomplete).headOption.map {
  397. case e: PlayException => e
  398. case e: xsbti.CompileFailed => {
  399. getProblems(incomplete).headOption.map(CompilationException(_)).getOrElse {
  400. UnexpectedException(Some("Compilation failed without reporting any problem!?"), Some(e))
  401. }
  402. }
  403. case e => UnexpectedException(unexpected = Some(e))
  404. }.getOrElse(
  405. UnexpectedException(Some("Compilation task failed without any exception!?")))
  406. }
  407. .right.map { compilationResult =>
  408. updateAnalysis(compilationResult).map { _ =>
  409. newClassloader
  410. }
  411. }
  412. }
  413. }
  414. override def handleWebCommand(request: play.api.mvc.RequestHeader) = {
  415. val applyEvolutions = """/@evolutions/apply/([a-zA-Z0-9_]+)""".r
  416. request.path match {
  417. case applyEvolutions(db) => {
  418. import play.api.db._
  419. import play.api.db.evolutions._
  420. import play.api.mvc.Results._
  421. OfflineEvolutions.applyScript(extracted.currentProject.base, newClassloader, db)
  422. forceReloadNextTime()
  423. Some(Redirect(request.queryString.get("redirect").filterNot(_.isEmpty).map(_(0)).getOrElse("/")))
  424. }
  425. case _ => None
  426. }
  427. }
  428. }
  429. }
  430. // ----- Play commands
  431. val playRunCommand = Command.command("run") { state: State =>
  432. val reloader = newReloader(state)
  433. println()
  434. val server = new play.core.server.NettyServer(reloader, 9000, allowKeepAlive = false)
  435. println()
  436. println(Colors.green("(Server started, use Ctrl+D to stop and go back to the console...)"))
  437. println()
  438. waitForKey()
  439. server.stop()
  440. println()
  441. state
  442. }
  443. val playStartCommand = Command.command("start") { state: State =>
  444. val extracted = Project.extract(state)
  445. Project.evaluateTask(compile in Compile, state).get.toEither match {
  446. case Left(_) => {
  447. println()
  448. println("Cannot start with errors.")
  449. println()
  450. state.fail
  451. }
  452. case Right(_) => {
  453. Project.evaluateTask(dependencyClasspath in Runtime, state).get.toEither.right.map { dependencies =>
  454. val classpath = dependencies.map(_.data).map(_.getCanonicalPath).reduceLeft(_ + java.io.File.pathSeparator + _)
  455. import java.lang.{ ProcessBuilder => JProcessBuilder }
  456. val builder = new JProcessBuilder(Array(
  457. "java", "-cp", classpath, "play.core.server.NettyServer", extracted.currentProject.base.getCanonicalPath): _*)
  458. new Thread {
  459. override def run {
  460. System.exit(Process(builder) !)
  461. }
  462. }.start()
  463. println(Colors.green(
  464. """|
  465. |(Starting server. Type Ctrl+D to exit logs, the server will remain in background)
  466. |""".stripMargin))
  467. waitForKey()
  468. println()
  469. state.copy(remainingCommands = Seq.empty)
  470. }.right.getOrElse {
  471. println()
  472. println("Oops, cannot start the server?")
  473. println()
  474. state.fail
  475. }
  476. }
  477. }
  478. }
  479. val playHelpCommand = Command.command("help") { state: State =>
  480. println(
  481. """
  482. |Welcome to Play 2.0!
  483. |
  484. |These commands are available:
  485. |-----------------------------
  486. |classpath Display the project classpath.
  487. |clean Clean all generated files.
  488. |compile Compile the current application.
  489. |console Launch the interactive Scala console (use :quit to exit).
  490. |dependencies Display the dependencies summary.
  491. |dist Construct standalone application package.
  492. |exit Exit the console.
  493. |h2-browser Launch the H2 Web browser.
  494. |license Display licensing informations.
  495. |package Package your application as a JAR.
  496. |publish Publish your application in a remote repository.
  497. |publish-local Publish your application in the local repository.
  498. |reload Reload the current application build file.
  499. |run Run the current application in DEV mode.
  500. |test Run Junit tests and/or Specs
  501. |start Start the current application in another JVM in PROD mode.
  502. |update Update application dependencies.
  503. |
  504. |You can also use any sbt feature:
  505. |---------------------------------
  506. |about Displays basic information about sbt and the build.
  507. |reboot [full] Reboots sbt and then executes the remaining commands.
  508. |< file* Reads command lines from the provided files.
  509. |!! Execute the last command again
  510. |!: Show all previous commands
  511. |!:n Show the last n commands
  512. |!n Execute the command with index n, as shown by the !: command
  513. |!-n Execute the nth command before this one
  514. |!string Execute the most recent command starting with 'string'
  515. |!?string Execute the most recent command containing 'string'
  516. |~ <action> Executes the specified command whenever source files change.
  517. |projects Displays the names of available projects.
  518. |project [project] Displays the current project or changes to the provided `project`.
  519. |- command Registers 'command' to run if a command fails.
  520. |iflast command If there are no more commands after this one, 'command' is run.
  521. |( ; command )+ Runs the provided semicolon-separated commands.
  522. |set <setting-expression> Evaluates the given Setting and applies to the current project.
  523. |tasks Displays the tasks defined for the current project.
  524. |inspect <key> Prints the value for 'key', the defining scope, delegates, related definitions, and dependencies.
  525. |eval <expression> Evaluates the given Scala expression and prints the result and type.
  526. |alias Adds, removes, or prints command aliases.
  527. |append command Appends `command` to list of commands to run.
  528. |last <key> Prints the last output associated with 'key'.
  529. |last-grep <pattern> <key> Shows lines from the last output for 'key' that match 'pattern'.
  530. |session ... Manipulates session settings. For details, run 'help session'..
  531. |
  532. |Browse the complete documentation at http://www.playframework.org.
  533. |""".stripMargin)
  534. state
  535. }
  536. val playCommand = Command.command("play") { state: State =>
  537. val extracted = Project.extract(state)
  538. import extracted._
  539. // Display logo
  540. println(play.console.Console.logo)
  541. println("""
  542. |> Type "help" or "license" for more information.
  543. |> Type "exit" or use Ctrl+D to leave this console.
  544. |""".stripMargin)
  545. state.copy(
  546. remainingCommands = state.remainingCommands :+ "shell")
  547. }
  548. val h2Command = Command.command("h2-browser") { state: State =>
  549. try {
  550. org.h2.tools.Server.main()
  551. } catch {
  552. case _ =>
  553. }
  554. state
  555. }
  556. val licenseCommand = Command.command("license") { state: State =>
  557. println(
  558. """
  559. |This software is licensed under the Apache 2 license, quoted below.
  560. |
  561. |Copyright 2011 Zenexity <http://www.zenexity.com>
  562. |
  563. |Licensed under the Apache License, Version 2.0 (the "License"); you may not
  564. |use this file except in compliance with the License. You may obtain a copy of
  565. |the License at
  566. |
  567. | http://www.apache.org/licenses/LICENSE-2.0
  568. |
  569. |Unless required by applicable law or agreed to in writing, software
  570. |distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  571. |WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  572. |License for the specific language governing permissions and limitations under
  573. |the License.
  574. """.stripMargin)
  575. state
  576. }
  577. val classpathCommand = Command.command("classpath") { state: State =>
  578. val extracted = Project.extract(state)
  579. Project.evaluateTask(dependencyClasspath in Runtime, state).get.toEither match {
  580. case Left(_) => {
  581. println()
  582. println("Cannot compute the classpath")
  583. println()
  584. state.fail
  585. }
  586. case Right(classpath) => {
  587. println()
  588. println("Here is the computed classpath of your application:")
  589. println()
  590. classpath.foreach { item =>
  591. println("\t- " + item.data.getAbsolutePath)
  592. }
  593. println()
  594. state
  595. }
  596. }
  597. }
  598. // -- Dependencies
  599. val computeDependencies = TaskKey[Seq[Map[Symbol, Any]]]("ivy-dependencies")
  600. val computeDependenciesTask = (deliverLocal, ivySbt, streams, organizationName, moduleName, version, scalaVersion) map { (_, ivySbt, s, org, id, version, scalaVersion) =>
  601. import scala.xml._
  602. ivySbt.withIvy(s.log) { ivy =>
  603. val report = XML.loadFile(
  604. ivy.getResolutionCacheManager.getConfigurationResolveReportInCache(org + "-" + id + "_" + scalaVersion, "runtime"))
  605. val deps: Seq[Map[Symbol, Any]] = (report \ "dependencies" \ "module").flatMap { module =>
  606. (module \ "revision").map { rev =>
  607. Map(
  608. 'module -> (module \ "@organisation" text, module \ "@name" text, rev \ "@name"),
  609. 'evictedBy -> (rev \ "evicted-by").headOption.map(_ \ "@rev" text),
  610. 'requiredBy -> (rev \ "caller").map { caller =>
  611. (caller \ "@organisation" text, caller \ "@name" text, caller \ "@callerrev" text)
  612. },
  613. 'artifacts -> (rev \ "artifacts" \ "artifact").flatMap { artifact =>
  614. (artifact \ "@location").headOption.map(node => new java.io.File(node.text).getName)
  615. })
  616. }
  617. }
  618. deps
  619. }
  620. }
  621. val computeDependenciesCommand = Command.command("dependencies") { state: State =>
  622. val extracted = Project.extract(state)
  623. Project.evaluateTask(computeDependencies, state).get.toEither match {
  624. case Left(_) => {
  625. println()
  626. println("Cannot compute dependencies")
  627. println()
  628. state.fail
  629. }
  630. case Right(dependencies) => {
  631. println()
  632. println("Here are the resolved dependencies of your application:")
  633. println()
  634. import scala.Console._
  635. def asTableRow(module: Map[Symbol, Any]): Seq[(String, String, String, Boolean)] = {
  636. val formatted = (Seq(module.get('module).map {
  637. case (org, name, rev) => org + ":" + name + ":" + rev
  638. }).flatten,
  639. module.get('requiredBy).map {
  640. case callers @ Seq(_*) => callers.map {
  641. case (org, name, rev) => org + ":" + name + ":" + rev
  642. }
  643. }.flatten.toSeq,
  644. module.get('evictedBy).map {
  645. case Some(rev) => Seq("Evicted by " + rev)
  646. case None => module.get('artifacts).map {
  647. case artifacts: Seq[String] => artifacts.map("As " + _)
  648. }.flatten
  649. }.flatten.toSeq)
  650. val maxLines = Seq(formatted._1.size, formatted._2.size, formatted._3.size).max
  651. formatted._1.padTo(maxLines, "").zip(
  652. formatted._2.padTo(maxLines, "")).zip(
  653. formatted._3.padTo(maxLines, "")).map {
  654. case ((name, callers), notes) => (name, callers, notes, module.get('evictedBy).map { case Some(_) => true; case _ => false }.get)
  655. }
  656. }
  657. def display(modules: Seq[Seq[(String, String, String, Boolean)]]) {
  658. val c1Size = modules.flatten.map(_._1.size).max
  659. val c2Size = modules.flatten.map(_._2.size).max
  660. val c3Size = modules.flatten.map(_._3.size).max
  661. def bar(length: Int) = (1 to length).map(_ => "-").mkString
  662. val lineFormat = "| %-" + (c1Size + 9) + "s | %-" + (c2Size + 9) + "s | %-" + (c3Size + 9) + "s |"
  663. val separator = "+-%s-+-%s-+-%s-+".format(
  664. bar(c1Size), bar(c2Size), bar(c3Size))
  665. println(separator)
  666. println(lineFormat.format(CYAN + "Module" + RESET, CYAN + "Required by" + RESET, CYAN + "Note" + RESET))
  667. println(separator)
  668. modules.foreach { lines =>
  669. lines.foreach {
  670. case (module, caller, note, evicted) => {
  671. println(lineFormat.format(
  672. if (evicted) (RED + module + RESET) else (GREEN + module + RESET),
  673. (WHITE + caller + RESET),
  674. if (evicted) (RED + note + RESET) else (WHITE + note + RESET)))
  675. }
  676. }
  677. println(separator)
  678. }
  679. }
  680. display(dependencies.map(asTableRow))
  681. println()
  682. state
  683. }
  684. }
  685. }
  686. // ----- Default settings
  687. lazy val defaultJavaSettings = Seq[Setting[_]](
  688. templatesImport ++= Seq(
  689. "models._",
  690. "controllers._",
  691. "java.lang._",
  692. "java.util._",
  693. "scala.collection.JavaConversions._",
  694. "scala.collection.JavaConverters._",
  695. "play.api.i18n.Messages",
  696. "play.mvc._",
  697. "play.data._",
  698. "com.avaje.ebean._",
  699. "play.mvc.Http.Context.Implicit._",
  700. "views.%format%._"))
  701. lazy val defaultScalaSettings = Seq[Setting[_]](
  702. templatesImport ++= Seq(
  703. "models._",
  704. "controllers._",
  705. "play.api.i18n.Messages",
  706. "play.api.mvc._",
  707. "play.api.data._",
  708. "views.%format%._"))
  709. lazy val defaultSettings = Seq[Setting[_]](
  710. resolvers ++= Seq(
  711. Resolver.url("Play Repository", url("http://download.playframework.org/ivy-releases/"))(Resolver.ivyStylePatterns),
  712. "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"),
  713. target <<= baseDirectory / "target",
  714. sourceDirectory in Compile <<= baseDirectory / "app",
  715. sourceDirectory in Test <<= baseDirectory / "test",
  716. confDirectory <<= baseDirectory / "conf",
  717. scalaSource in Compile <<= baseDirectory / "app",
  718. scalaSource in Test <<= baseDirectory / "test",
  719. javaSource in Compile <<= baseDirectory / "app",
  720. javaSource in Test <<= baseDirectory / "test",
  721. distDirectory <<= baseDirectory / "dist",
  722. libraryDependencies += "play" %% "play" % play.core.PlayVersion.current,
  723. libraryDependencies ++= Seq("org.specs2" %% "specs2" % "1.6.1" % "test",
  724. "com.novocode" % "junit-interface" % "0.7" % "test",
  725. "org.seleniumhq.selenium" % "selenium-chrome-driver" % "2.11.0" % "test",
  726. "org.seleniumhq.selenium" % "selenium-htmlunit-driver" % "2.11.0" % "test"),
  727. sourceGenerators in Compile <+= (confDirectory, sourceManaged in Compile) map RouteFiles,
  728. sourceGenerators in Compile <+= (sourceDirectory in Compile, sourceManaged in Compile, templatesTypes, templatesImport) map ScalaTemplates,
  729. commands ++= Seq(playCommand, playRunCommand, playStartCommand, playHelpCommand, h2Command, classpathCommand, licenseCommand, computeDependenciesCommand),
  730. shellPrompt := playPrompt,
  731. copyResources in Compile <<= (copyResources in Compile, playCopyResources) map { (r, pr) => r ++ pr },
  732. mainClass in (Compile, run) := Some(classOf[play.core.server.NettyServer].getName),
  733. compile in (Compile) <<= PostCompile,
  734. dist <<= distTask,
  735. computeDependencies <<= computeDependenciesTask,
  736. playCopyResources <<= playCopyResourcesTask,
  737. playCompileEverything <<= playCompileEverythingTask,
  738. playPackageEverything <<= playPackageEverythingTask,
  739. playReload <<= playReloadTask,
  740. playStage <<= playStageTask,
  741. cleanFiles <+= distDirectory.identity,
  742. resourceGenerators in Compile <+= LessCompiler,
  743. resourceGenerators in Compile <+= CoffeescriptCompiler,
  744. resourceGenerators in Compile <+= JavascriptCompiler,
  745. minify := false,
  746. playResourceDirectories := Seq.empty[File],
  747. playResourceDirectories <+= baseDirectory / "conf",
  748. playResourceDirectories <+= baseDirectory / "public",
  749. templatesImport := Seq("play.api.templates._", "play.api.templates.PlayMagic._"),
  750. templatesTypes := {
  751. case "html" => ("play.api.templates.Html", "play.api.templates.HtmlFormat")
  752. case "txt" => ("play.api.templates.Txt", "play.api.templates.TxtFormat")
  753. case "xml" => ("play.api.templates.Xml", "play.api.templates.XmlFormat")
  754. })
  755. // ----- Create a Play project with default settings
  756. def apply(name: String, applicationVersion: String = "1.0", dependencies: Seq[ModuleID] = Nil, path: File = file(".")) = {
  757. Project(name, path)
  758. .settings(parallelExecution in Test := false)
  759. .settings(PlayProject.defaultSettings: _*)
  760. .settings(
  761. version := applicationVersion,
  762. libraryDependencies ++= dependencies)
  763. }
  764. }