Skip to content

Commit

Permalink
twirl that works with scala API
Browse files Browse the repository at this point in the history
  • Loading branch information
rockjam authored and ggrossetie committed May 11, 2018
1 parent 079e4e4 commit deff656
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 81 deletions.
5 changes: 4 additions & 1 deletion twirllib/src/mill/twirllib/TwirlModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ trait TwirlModule extends mill.Module {
MavenRepository("https://repo1.maven.org/maven2")
),
Lib.depToDependency(_, "2.12.4"),
Seq(ivy"com.typesafe.play::twirl-compiler:${twirlVersion()}")
Seq(
ivy"com.typesafe.play::twirl-compiler:${twirlVersion()}",
ivy"org.scala-lang.modules::scala-parser-combinators:1.0.5"
)
)
}

Expand Down
118 changes: 38 additions & 80 deletions twirllib/src/mill/twirllib/TwirlWorker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,104 +3,62 @@ package twirllib

import java.io.File
import java.net.URLClassLoader
import java.util

import ammonite.ops.{Path, ls}
import mill.eval.PathRef
import mill.scalalib.CompilationResult

import scala.io.Codec
import scala.reflect.runtime.universe._
import scala.util.Properties

class TwirlWorker {

private var twirlInstanceCache = Option.empty[(Long, TwirlWorkerApi)]
import scala.collection.JavaConverters._

private def twirl(twirlClasspath: Agg[Path]) = {
val classloaderSig = twirlClasspath.map(p => p.toString().hashCode + p.mtime.toMillis).sum
twirlInstanceCache match {
case Some((sig, instance)) if sig == classloaderSig => instance
case _ =>
//callScalaAPI(twirlClasspath, classloaderSig)
callJavaAPI(twirlClasspath, classloaderSig)
}
}

private def callJavaAPI(twirlClasspath: Agg[Path], classloaderSig: Long) = {
val cl = new URLClassLoader(twirlClasspath.map(_.toIO.toURI.toURL).toArray)
val twirlCompilerClass = cl.loadClass("play.japi.twirl.compiler.TwirlCompiler")
// Use the Java API (available in Twirl 1.3+)
// Using reflection on a method with "Seq[String] = Nil" parameter type does not seem to work.

// REMIND: Unable to call the compile method with a primitive boolean
// codec and inclusiveDot will not be available
val compileMethod = twirlCompilerClass.getMethod("compile",
classOf[File],
classOf[File],
classOf[File],
classOf[String],
classOf[util.Collection[String]],
classOf[util.List[String]])

val instance = new TwirlWorkerApi {
override def compileTwirl(source: File,
sourceDirectory: File,
generatedDirectory: File,
formatterType: String,
additionalImports: Seq[String] = Nil,
constructorAnnotations: Seq[String] = Nil,
codec: Codec = Codec(Properties.sourceEncoding),
inclusiveDot: Boolean = false) {
val o = compileMethod.invoke(null, source,
sourceDirectory,
generatedDirectory,
formatterType,
additionalImports.asJava,
constructorAnnotations.asJava)
}
}
twirlInstanceCache = Some((classloaderSig, instance))
instance
}

private def callScalaAPI(twirlClasspath: Agg[Path], classloaderSig: Long) = {
val cl = new URLClassLoader(twirlClasspath.map(_.toIO.toURI.toURL).toArray)
val runtime = runtimeMirror(cl)
val moduleMirror = runtime.reflectModule(runtime.staticModule("play.twirl.compiler.TwirlCompiler"))
val moduleInstance = moduleMirror.instance
val instanceMirror = runtime.reflect(moduleInstance)
val compileSymbol = instanceMirror.symbol.typeSignature.member(TermName("compile")).asMethod
// FIXME: type erasure, unable to call "play.twirl.compiler.TwirlCompiler.compile" function
// https://github.com/playframework/twirl/blob/dd56444cf9ea2f95ef3961d3af911561f846df50/compiler/src/main/scala/play/twirl/compiler/TwirlCompiler.scala#L167
val instance = new TwirlWorkerApi {
override def compileTwirl(source: File,
sourceDirectory: File,
generatedDirectory: File,
formatterType: String,
additionalImports: Seq[String] = Nil,
constructorAnnotations: Seq[String] = Nil,
codec: Codec = Codec(Properties.sourceEncoding),
inclusiveDot: Boolean = false) {

// scala.MatchError: List() (of class scala.collection.immutable.Nil$)
// at scala.reflect.runtime.JavaMirrors$JavaMirror$DerivedValueClassMetadata.unboxer$lzycompute(JavaMirrors.scala:270)
// at scala.reflect.runtime.JavaMirrors$JavaMirror$DerivedValueClassMetadata.unboxer(JavaMirrors.scala:269)
// at scala.reflect.runtime.JavaMirrors$JavaMirror$MethodMetadata.paramUnboxers(JavaMirrors.scala:416)
// at scala.reflect.runtime.JavaMirrors$JavaMirror$JavaTransformingMethodMirror.apply(JavaMirrors.scala:435)
instanceMirror.reflectMethod(compileSymbol)(source,
sourceDirectory,
generatedDirectory,
formatterType,
additionalImports,
constructorAnnotations,
codec,
inclusiveDot)
}
val cl = new URLClassLoader(twirlClasspath.map(_.toIO.toURI.toURL).toArray)
val twirlCompilerClass = cl.loadClass("play.twirl.compiler.TwirlCompiler")
val compileMethod = twirlCompilerClass.getMethod("compile",
classOf[java.io.File],
classOf[java.io.File],
classOf[java.io.File],
classOf[java.lang.String],
cl.loadClass("scala.collection.Seq"),
cl.loadClass("scala.collection.Seq"),
cl.loadClass("scala.io.Codec"),
classOf[Boolean])

val defaultAdditionalImportsMethod = twirlCompilerClass.getMethod("compile$default$5")
val defaultConstructorAnnotationsMethod = twirlCompilerClass.getMethod("compile$default$6")
val defaultCodecMethod = twirlCompilerClass.getMethod("compile$default$7")
val defaultFlagMethod = twirlCompilerClass.getMethod("compile$default$8")

val instance = new TwirlWorkerApi {
override def compileTwirl(source: File,
sourceDirectory: File,
generatedDirectory: File,
formatterType: String,
additionalImports: Seq[String] = Nil,
constructorAnnotations: Seq[String] = Nil,
codec: Codec = Codec(Properties.sourceEncoding),
inclusiveDot: Boolean = false) {
val o = compileMethod.invoke(null, source,
sourceDirectory,
generatedDirectory,
formatterType,
defaultAdditionalImportsMethod.invoke(additionalImports),
defaultConstructorAnnotationsMethod.invoke(constructorAnnotations),
defaultCodecMethod.invoke(codec),
defaultFlagMethod.invoke(inclusiveDot))
}
}
twirlInstanceCache = Some((classloaderSig, instance))
instance
}
twirlInstanceCache = Some((classloaderSig, instance))
instance
}

def compile(twirlClasspath: Agg[Path], sourceDirectories: Seq[Path], dest: Path)
Expand Down

0 comments on commit deff656

Please sign in to comment.