summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
authorJean Helou <jhe@codamens.fr>2019-02-11 22:21:18 +0100
committerTobias Roeser <le.petit.fou@web.de>2019-02-14 17:45:16 +0100
commit3fe65a95534aaafb6ce60ac387f45171929f26b5 (patch)
treed4dd0c0c0b660e59c3e1e8e7f84b70389ac0cb6f /contrib
parentb095094c40cdcd99fa65aeb3fe78390334c0636e (diff)
downloadmill-3fe65a95534aaafb6ce60ac387f45171929f26b5.tar.gz
mill-3fe65a95534aaafb6ce60ac387f45171929f26b5.tar.bz2
mill-3fe65a95534aaafb6ce60ac387f45171929f26b5.zip
Reworks the toplevel playlib module
This is the first commit of a redesign of the play lib module. The new design was massively inspired from the `scalajslib` module. It adds a specialized worker for each version of play, both workers implement a common api from an `api`. The main module delegates to a `loader` which dynamically looks up the bridge instance through reflection then triggers the generation. - adds a `RouteCompilerWorkerApi` trait which establishes the bridge to the actual `RouteCompilerWorker`. - drops the existing `RouterGeneratorWorker` (it is specialized by versions of play and extracted to its own submodule). - updates the `RouterModule` with improved settings and documentation.
Diffstat (limited to 'contrib')
-rw-r--r--contrib/playlib/src/mill/playlib/RouteCompilerWorkerApi.scala68
-rw-r--r--contrib/playlib/src/mill/playlib/RouterGeneratorWorker.scala117
-rw-r--r--contrib/playlib/src/mill/playlib/RouterModule.scala100
3 files changed, 146 insertions, 139 deletions
diff --git a/contrib/playlib/src/mill/playlib/RouteCompilerWorkerApi.scala b/contrib/playlib/src/mill/playlib/RouteCompilerWorkerApi.scala
new file mode 100644
index 00000000..6e9f68ed
--- /dev/null
+++ b/contrib/playlib/src/mill/playlib/RouteCompilerWorkerApi.scala
@@ -0,0 +1,68 @@
+package mill.playlib
+
+import ammonite.ops.Path
+import mill._
+import mill.api.{Ctx, Result}
+import mill.define.{Discover, ExternalModule, Worker}
+import mill.playlib.api.RouteCompilerType
+import mill.scalalib.api.CompilationResult
+
+class RouteCompilerWorker {
+ private var routeCompilerInstanceCache = Option.empty[(Long, mill.playlib.api.RouteCompilerWorkerApi)]
+
+ private def bridge(toolsClasspath: Agg[os.Path])
+ (implicit ctx: Ctx) = {
+ val classloaderSig =
+ toolsClasspath.map(p => p.toString().hashCode + os.mtime(p)).sum
+ routeCompilerInstanceCache match {
+ case Some((sig, bridge)) if sig == classloaderSig => bridge
+ case _ =>
+ val toolsClassPath = toolsClasspath.map(_.toIO.toURI.toURL).toVector
+ ctx.log.debug("Loading classes from\n"+toolsClassPath.mkString("\n"))
+ val cl = mill.api.ClassLoader.create(
+ toolsClassPath,
+ getClass.getClassLoader
+ )
+ val bridge = cl
+ .loadClass("mill.playlib.worker.RouteCompilerWorker")
+ .getDeclaredConstructor()
+ .newInstance()
+ .asInstanceOf[mill.playlib.api.RouteCompilerWorkerApi]
+ routeCompilerInstanceCache = Some((classloaderSig, bridge))
+ bridge
+ }
+ }
+
+
+ def compile(routerClasspath: Agg[Path],
+ file: Path,
+ additionalImports: Seq[String],
+ forwardsRouter: Boolean,
+ reverseRouter: Boolean,
+ namespaceReverseRouter: Boolean,
+ generatorType: RouteCompilerType,
+ dest: Path)(implicit ctx: Ctx)
+ : Result[CompilationResult] = {
+ bridge(routerClasspath)
+ .compile(
+ file,
+ additionalImports,
+ forwardsRouter,
+ reverseRouter,
+ namespaceReverseRouter,
+ generatorType,
+ dest
+ )(ctx)
+ }
+
+
+}
+
+object RouteCompilerWorkerModule extends ExternalModule {
+ def routeCompilerWorker: Worker[RouteCompilerWorker] = T.worker {
+ new RouteCompilerWorker()
+ }
+
+ lazy val millDiscover = Discover[this.type]
+}
+
diff --git a/contrib/playlib/src/mill/playlib/RouterGeneratorWorker.scala b/contrib/playlib/src/mill/playlib/RouterGeneratorWorker.scala
deleted file mode 100644
index 35eaf047..00000000
--- a/contrib/playlib/src/mill/playlib/RouterGeneratorWorker.scala
+++ /dev/null
@@ -1,117 +0,0 @@
-package mill
-package playlib
-
-import java.io.File
-import java.net.URLClassLoader
-
-import ammonite.ops.Path
-import mill.eval.PathRef
-import mill.scalalib.api.CompilationResult
-
-import scala.collection.JavaConverters._
-
-class RouterGeneratorWorker {
-
- private var routerGeneratorInstances = Option.empty[(Long, RouterGeneratorWorkerApi)]
-
- private def router(routerClasspath: Agg[Path]) = {
- val classloaderSig = routerClasspath.map(p => p.toString().hashCode + os.mtime(p)).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]] = {
- // Since the classloader is isolated, we do not share any classes with the Mill classloader.
- // Thus both classloaders have different copies of "scala.collection.Seq" which are not compatible.
- val additionalImports = cl.loadClass("scala.collection.mutable.WrappedArray$ofRef")
- .getConstructors()(0)
- .newInstance(task.additionalImports.toArray)
- .asInstanceOf[AnyRef]
- val args = Array[AnyRef](task.file,
- additionalImports,
- Boolean.box(task.forwardsRouter),
- Boolean.box(task.reverseRouter),
- Boolean.box(task.namespaceReverseRouter))
- 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)
- // compile method returns an object of type Either[Seq[RoutesCompilationError], Seq[File]]
- result.getClass.getName match {
- case "scala.util.Right" =>
- val files = cl.loadClass("scala.util.Right")
- .getMethod("value")
- .invoke(result)
- val asJavaMethod = cl.loadClass("scala.collection.convert.DecorateAsJava")
- .getMethod("seqAsJavaListConverter", cl.loadClass("scala.collection.Seq"))
- val javaConverters = cl.loadClass("scala.collection.JavaConverters$")
- val javaConvertersInstance = javaConverters.getField("MODULE$").get(javaConverters)
- val filesJava = cl.loadClass("scala.collection.convert.Decorators$AsJava")
- .getMethod("asJava")
- .invoke(asJavaMethod.invoke(javaConvertersInstance, files))
- .asInstanceOf[java.util.List[File]]
- Right(filesJava.asScala)
- case "scala.util.Left" =>
- // TODO: convert the error of type RoutesCompilationError to a CompilationResult
- Left(Seq(CompilationResult(Path(""), PathRef(Path("")))))
- }
- }
- }
- 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
index a7073492..ac1f71ff 100644
--- a/contrib/playlib/src/mill/playlib/RouterModule.scala
+++ b/contrib/playlib/src/mill/playlib/RouterModule.scala
@@ -2,53 +2,109 @@ package mill
package playlib
import coursier.{Cache, MavenRepository}
-import scalalib.Lib
-import scalalib.Lib.resolveDependencies
-import scalalib.api._
-import scalalib._
import mill.api.Loose
+import mill.playlib.api.RouteCompilerType
+import mill.scalalib.Lib.resolveDependencies
+import mill.scalalib._
+import mill.scalalib.api._
-trait RouterModule extends mill.Module {
+trait RouterModule extends mill.Module with ScalaModule {
+ /**
+ * Defines the version of playframework to be used to compile this projects
+ * routes.
+ */
def playVersion: T[String]
+ override def generatedSources = T{ super.generatedSources() ++ Seq(compileRouter().classes) }
+
+ /**
+ * The [[PathRef]] to the main routes file.
+ *
+ * This is the default path for play projects and it should be fine but you
+ * can override it if needed.
+ */
def routesFile: T[PathRef] = T {
val routesPath = millSourcePath / "conf" / "routes"
PathRef(routesPath)
}
+ /**
+ * A [[Seq]] of additional imports to be added to the routes file.
+ * Defaults to :
+ * <ul>
+ * <li> controllers.Assets.Asset
+ * <li> play.libs.F
+ * </ul>
+ */
+ def routesAdditionalImport: Seq[String] = Seq(
+ "controllers.Assets.Asset",
+ "play.libs.F"
+ )
+
+ def generateForwardsRouter: Boolean = true
+
+ def generateReverseRouter: Boolean = true
+
+ def namespaceReverseRouter: Boolean = false
+
+ /**
+ * The routes compiler type to be used. Can only be one of:
+ * <ul>
+ * <li>[[RouteCompilerType.InjectedGenerator]]
+ * <li>[[RouteCompilerType.StaticGenerator]]
+ * </ul>
+ * @return
+ */
+ def generatorType: RouteCompilerType = RouteCompilerType.InjectedGenerator
+
def routerClasspath: T[Loose.Agg[PathRef]] = T {
resolveDependencies(
Seq(
Cache.ivy2Local,
MavenRepository("https://repo1.maven.org/maven2")
),
- Lib.depToDependency(_, "2.12.4"),
+ Lib.depToDependency(_, scalaVersion()),
Seq(
ivy"com.typesafe.play::routes-compiler:${playVersion()}"
)
)
}
- def routesAdditionalImport: Seq[String] = Seq(
- "controllers.Assets.Asset",
- "play.libs.F"
- )
+ final def compileRouter: T[CompilationResult] = T {
+ T.ctx().log.debug(s"compiling play routes with ${playVersion()} worker")
+ RouteCompilerWorkerModule.routeCompilerWorker().compile(
+ toolsClasspath().map(_.path),
+ routesFile().path,
+ routesAdditionalImport,
+ generateForwardsRouter,
+ generateReverseRouter,
+ namespaceReverseRouter,
+ generatorType,
+ T.ctx().dest)
+ }
- def generateForwardsRouter: Boolean = true
+ private def playMinorVersion: T[String] = T {
+ playVersion().split("\\.").take(2).mkString("", ".", ".0")
+ }
- def generateReverseRouter: Boolean = true
+ private def playRouteCompilerWorkerClasspath = T {
+ val workerKey = "MILL_CONTRIB_PLAYLIB_ROUTECOMPILER_WORKER_" + playMinorVersion().replace(".",
+ "_")
+ T.ctx.log.debug(s"classpath worker key: $workerKey")
- def namespaceReverseRouter: Boolean = false
+ //While the following seems to work (tests pass), I am not completely
+ //confident that the strings I used for artifact and resolveFilter are
+ //actually correct
+ mill.modules.Util.millProjectModule(
+ workerKey,
+ s"mill-contrib-playlib-worker-${playVersion()}",
+ repositories,
+ resolveFilter = _.toString.contains("mill-contrib-playlib-worker")
+ )
+ }
- def compileRouter: T[CompilationResult] = T.persistent {
- RouterGeneratorWorkerApi.routerGeneratorWorker
- .compile(routerClasspath().map(_.path),
- routesFile().path,
- routesAdditionalImport,
- generateForwardsRouter,
- generateReverseRouter,
- namespaceReverseRouter,
- T.ctx().dest)
+ private def toolsClasspath = T {
+ playRouteCompilerWorkerClasspath() ++ routerClasspath()
}
} \ No newline at end of file