From fc18309865baf869d9c6f7ff7879f33f4cc122e6 Mon Sep 17 00:00:00 2001 From: Guillaume Grossetie Date: Thu, 28 Jun 2018 16:36:59 +0200 Subject: Create a Play! module to compile the router --- build.sc | 2 +- ci/test-mill-0.sh | 2 +- .../src/mill/playlib/RouterGeneratorWorker.scala | 94 ++++++++++++++++++++++ .../playlib/src/mill/playlib/RouterModule.scala | 52 ++++++++++++ .../test/resources/hello-world/core/conf/routes | 2 + .../hello-world/core/views/hello.scala.html | 6 ++ .../src/mill/playlib/HelloWorldRouterTests.scala | 77 ++++++++++++++++++ .../hello-world/core/views/hello.scala.html | 6 -- 8 files changed, 233 insertions(+), 8 deletions(-) create mode 100644 contrib/playlib/src/mill/playlib/RouterGeneratorWorker.scala create mode 100644 contrib/playlib/src/mill/playlib/RouterModule.scala create mode 100644 contrib/playlib/test/resources/hello-world/core/conf/routes create mode 100644 contrib/playlib/test/resources/hello-world/core/views/hello.scala.html create mode 100644 contrib/playlib/test/src/mill/playlib/HelloWorldRouterTests.scala delete mode 100644 contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html diff --git a/build.sc b/build.sc index 310f7060..fc58fff5 100755 --- a/build.sc +++ b/build.sc @@ -243,7 +243,7 @@ object contrib extends MillModule { ) } - object twirllib extends MillModule { + object playlib extends MillModule { def moduleDeps = Seq(scalalib) } diff --git a/ci/test-mill-0.sh b/ci/test-mill-0.sh index 92dc34c5..e8d8f67c 100755 --- a/ci/test-mill-0.sh +++ b/ci/test-mill-0.sh @@ -6,4 +6,4 @@ set -eux git clean -xdf # Run tests -mill -i all {main,scalalib,scalajslib,contrib.twirllib,main.client,contrib.scalapblib}.test +mill -i all {main,scalalib,scalajslib,contri.playlib,main.client,contrib.scalapblib}.test diff --git a/contrib/playlib/src/mill/playlib/RouterGeneratorWorker.scala b/contrib/playlib/src/mill/playlib/RouterGeneratorWorker.scala new file mode 100644 index 00000000..dd3a818c --- /dev/null +++ b/contrib/playlib/src/mill/playlib/RouterGeneratorWorker.scala @@ -0,0 +1,94 @@ +package mill +package playlib + +import java.io.File +import java.net.URLClassLoader + +import ammonite.ops.Path +import mill.eval.PathRef +import mill.scalalib.CompilationResult + +class RouterGeneratorWorker { + + private var routerGeneratorInstances = Option.empty[(Long, RouterGeneratorWorkerApi)] + + private def router(routerClasspath: Agg[Path]) = { + val classloaderSig = routerClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum + routerGeneratorInstances match { + case Some((sig, instance)) if sig == classloaderSig => instance + case _ => + val cl = new URLClassLoader(routerClasspath.map(_.toIO.toURI.toURL).toArray, null) + val routerCompilerClass = cl.loadClass("play.routes.compiler.RoutesCompiler") + val routesCompilerTaskClass = cl.loadClass("play.routes.compiler.RoutesCompiler$RoutesCompilerTask") + val routerCompilerTaskConstructor = routesCompilerTaskClass.getConstructor( + classOf[File], + cl.loadClass("scala.collection.Seq"), + classOf[Boolean], + classOf[Boolean], + classOf[Boolean]) + val staticRoutesGeneratorModule = cl.loadClass("play.routes.compiler.StaticRoutesGenerator$").getField("MODULE$") + val injectedRoutesGeneratorModule = cl.loadClass("play.routes.compiler.InjectedRoutesGenerator$").getField("MODULE$") + val compileMethod = routerCompilerClass.getMethod("compile", + routesCompilerTaskClass, + cl.loadClass("play.routes.compiler.RoutesGenerator"), + classOf[java.io.File]) + val instance = new RouterGeneratorWorkerApi { + override def compile(task: RoutesCompilerTask, generatorType: String = "injected", generatedDir: File): Either[Seq[CompilationResult], Seq[File]] = { + val args = Array[AnyRef](task.file: java.io.File, + task.additionalImports: scala.collection.Seq[String], + Boolean.box(true), + Boolean.box(true), + Boolean.box(false)) + val routesCompilerTaskInstance = routerCompilerTaskConstructor.newInstance(args: _*).asInstanceOf[Object] + val routesGeneratorInstance = generatorType match { + case "injected" => injectedRoutesGeneratorModule.get(null) + case "static" => staticRoutesGeneratorModule.get(null) + case _ => throw new Exception(s"Unrecognized generator type: $generatorType. Use injected or static") + } + val result = compileMethod.invoke(null, + routesCompilerTaskInstance, + routesGeneratorInstance, + generatedDir) + result.asInstanceOf[Either[AnyVal, Seq[File]]] match { + case Right(value) => Right(value) + case Left(_) => Left(Seq(CompilationResult(Path(""), PathRef(Path(""))))) // FIXME: convert the error to a CompilationResult + } + } + } + routerGeneratorInstances = Some((classloaderSig, instance)) + instance + } + } + + def compile(routerClasspath: Agg[Path], + file: Path, + additionalImports: Seq[String], + forwardsRouter: Boolean, + reverseRouter: Boolean, + namespaceReverseRouter: Boolean, + dest: Path) + (implicit ctx: mill.util.Ctx): mill.eval.Result[CompilationResult] = { + val compiler = router(routerClasspath) + + val result = compiler.compile(RoutesCompilerTask(file.toIO, additionalImports, forwardsRouter, reverseRouter, namespaceReverseRouter), generatedDir = dest.toIO) + + result match { + case Right(_) => + val zincFile = ctx.dest / 'zinc + mill.eval.Result.Success(CompilationResult(zincFile, PathRef(ctx.dest))) + case Left(_) => mill.eval.Result.Failure("Unable to compile the routes") // FIXME: convert the error to a Failure + } + } +} + +trait RouterGeneratorWorkerApi { + + def compile(task: RoutesCompilerTask, generatorType: String = "injected", generatedDir: File): Either[Seq[CompilationResult], Seq[File]] +} + +case class RoutesCompilerTask(file: File, additionalImports: Seq[String], forwardsRouter: Boolean, reverseRouter: Boolean, namespaceReverseRouter: Boolean) + +object RouterGeneratorWorkerApi { + + def routerGeneratorWorker = new RouterGeneratorWorker() +} diff --git a/contrib/playlib/src/mill/playlib/RouterModule.scala b/contrib/playlib/src/mill/playlib/RouterModule.scala new file mode 100644 index 00000000..947e895b --- /dev/null +++ b/contrib/playlib/src/mill/playlib/RouterModule.scala @@ -0,0 +1,52 @@ +package mill +package playlib + +import coursier.{Cache, MavenRepository} +import mill.scalalib.Lib.resolveDependencies +import mill.scalalib._ +import mill.util.Loose + +trait RouterModule extends mill.Module { + + def playVersion: T[String] + + def routesFile: T[PathRef] = T { + val routesPath = millSourcePath / "conf" / "routes" + PathRef(routesPath) + } + + def routerClasspath: T[Loose.Agg[PathRef]] = T { + resolveDependencies( + Seq( + Cache.ivy2Local, + MavenRepository("https://repo1.maven.org/maven2") + ), + Lib.depToDependency(_, "2.12.4"), + Seq( + ivy"com.typesafe.play::routes-compiler:${playVersion()}" + ) + ) + } + + def routesAdditionalImport: Seq[String] = Seq( + "controllers.Assets.Asset", + "play.libs.F" + ) + + def generateForwardsRouter: Boolean = true + + def generateReverseRouter: Boolean = true + + def namespaceReverseRouter: Boolean = false + + def compileRouter: T[CompilationResult] = T.persistent { + RouterGeneratorWorkerApi.routerGeneratorWorker + .compile(routerClasspath().map(_.path), + routesFile().path, + routesAdditionalImport, + generateForwardsRouter, + generateReverseRouter, + namespaceReverseRouter, + T.ctx().dest) + } +} \ No newline at end of file diff --git a/contrib/playlib/test/resources/hello-world/core/conf/routes b/contrib/playlib/test/resources/hello-world/core/conf/routes new file mode 100644 index 00000000..7d5e5498 --- /dev/null +++ b/contrib/playlib/test/resources/hello-world/core/conf/routes @@ -0,0 +1,2 @@ +GET / controllers.HomeController.index +GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) diff --git a/contrib/playlib/test/resources/hello-world/core/views/hello.scala.html b/contrib/playlib/test/resources/hello-world/core/views/hello.scala.html new file mode 100644 index 00000000..3603fe07 --- /dev/null +++ b/contrib/playlib/test/resources/hello-world/core/views/hello.scala.html @@ -0,0 +1,6 @@ +@this(title: String) + + +

@title

+ + \ No newline at end of file diff --git a/contrib/playlib/test/src/mill/playlib/HelloWorldRouterTests.scala b/contrib/playlib/test/src/mill/playlib/HelloWorldRouterTests.scala new file mode 100644 index 00000000..03742714 --- /dev/null +++ b/contrib/playlib/test/src/mill/playlib/HelloWorldRouterTests.scala @@ -0,0 +1,77 @@ +package mill.playlib + +import ammonite.ops.{Path, cp, ls, mkdir, pwd, rm, _} +import mill.util.{TestEvaluator, TestUtil} +import utest.framework.TestPath +import utest.{TestSuite, Tests, assert, _} + +object HelloWorldRouterTests extends TestSuite { + + trait HelloBase extends TestUtil.BaseModule { + override def millSourcePath: Path = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.') + } + + trait HelloWorldModule extends mill.playlib.RouterModule { + def playVersion = "2.6.15" + } + + object HelloWorld extends HelloBase { + + object core extends HelloWorldModule { + override def playVersion = "2.6.14" + } + } + + val resourcePath: Path = pwd / 'contrib / 'playlib / 'test / 'resources / "hello-world" + + def workspaceTest[T, M <: TestUtil.BaseModule](m: M, resourcePath: Path = resourcePath) + (t: TestEvaluator[M] => T) + (implicit tp: TestPath): T = { + val eval = new TestEvaluator(m) + rm(m.millSourcePath) + rm(eval.outPath) + mkdir(m.millSourcePath / up) + cp(resourcePath, m.millSourcePath) + t(eval) + } + + def tests: Tests = Tests { + 'twirlVersion - { + + 'fromBuild - workspaceTest(HelloWorld) { eval => + val Right((result, evalCount)) = eval.apply(HelloWorld.core.playVersion) + + assert( + result == "2.6.14", + evalCount > 0 + ) + } + } + 'compileRouter - workspaceTest(HelloWorld) { eval => + val Right((result, evalCount)) = eval.apply(HelloWorld.core.compileRouter) + + val outputFiles = ls.rec(result.classes.path).filter(_.isFile) + val expectedClassfiles = Seq[RelPath]( + RelPath("controllers/ReverseRoutes.scala"), + RelPath("controllers/routes.java"), + RelPath("router/Routes.scala"), + RelPath("router/RoutesPrefix.scala"), + RelPath("controllers/javascript/JavaScriptReverseRoutes.scala") + ).map( + eval.outPath / 'core / 'compileRouter / 'dest / _ + ) + assert( + result.classes.path == eval.outPath / 'core / 'compileRouter / 'dest, + outputFiles.nonEmpty, + outputFiles.forall(expectedClassfiles.contains), + outputFiles.size == 5, + evalCount > 0 + ) + + // don't recompile if nothing changed + val Right((_, unchangedEvalCount)) = eval.apply(HelloWorld.core.compileRouter) + + assert(unchangedEvalCount == 0) + } + } +} diff --git a/contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html b/contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html deleted file mode 100644 index 3603fe07..00000000 --- a/contrib/twirllib/test/resources/hello-world/core/views/hello.scala.html +++ /dev/null @@ -1,6 +0,0 @@ -@this(title: String) - - -

@title

- - \ No newline at end of file -- cgit v1.2.3