Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a TwirlModule to compile Twirl templates #271

Merged
merged 9 commits into from
May 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ object scalajslib extends MillModule {
}
}

object twirllib extends MillModule {

def moduleDeps = Seq(scalalib)

}

def testRepos = T{
Seq(
"MILL_ACYCLIC_REPO" ->
Expand Down
2 changes: 1 addition & 1 deletion ci/test-mill-0.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ set -eux
git clean -xdf

# Run tests
mill -i all {main,scalalib,scalajslib,main.client}.test
mill -i all {main,scalalib,scalajslib,twirllib,main.client}.test
2 changes: 1 addition & 1 deletion ci/test-mill-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ mill -i dev.assembly
rm -rf ~/.mill

# Second build & run tests
out/dev/assembly/dest/mill -i all {main,scalalib,scalajslib}.test
out/dev/assembly/dest/mill -i all {main,scalalib,scalajslib,twirllib}.test

2 changes: 1 addition & 1 deletion main/test/src/mill/define/CacherTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ object CacherTests extends TestSuite{
}
object Middle extends Middle
trait Middle extends Base{
def value = T{ super.value() + 2}
override def value = T{ super.value() + 2}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intellij IDEA is complaining about this line (preventing me from running the tests) but Mill can compile the code just fine 😐

Copy link
Contributor

@rockjam rockjam Apr 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, that's fine in mill, because there is a compiler plugin that makes override word optional, but only in build files

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation.
Do you know if there is a way to configure intellij IDEA in order to use this compiler plugin for this file ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically it should be possible, If I understand correctly, you need to include http://search.maven.org/#artifactdetails%7Ccom.lihaoyi%7Cmill-moduledefs_2.12%7C0.1.7%7Cjar in classpath and add "-P:auto-override-plugin". to scalac options.

def overriden = T{ super.value()}
}
object Terminal extends Terminal
Expand Down
56 changes: 56 additions & 0 deletions twirllib/src/mill/twirllib/TwirlModule.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package mill
package twirllib

import coursier.{Cache, MavenRepository}
import mill.define.Sources
import mill.eval.PathRef
import mill.scalalib.Lib.resolveDependencies
import mill.scalalib._
import mill.util.Loose

import scala.io.Codec
import scala.util.Properties

trait TwirlModule extends mill.Module {

def twirlVersion: T[String]

def twirlSources: Sources = T.sources {
millSourcePath / 'views
}

def twirlClasspath: 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::twirl-compiler:${twirlVersion()}",
ivy"org.scala-lang.modules::scala-parser-combinators:1.1.0"
)
)
}

// REMIND currently it's not possible to override these default settings
private def twirlAdditionalImports: Seq[String] = Nil

private def twirlConstructorAnnotations: Seq[String] = Nil

private def twirlCodec: Codec = Codec(Properties.sourceEncoding)

private def twirlInclusiveDot: Boolean = false

def compileTwirl: T[CompilationResult] = T.persistent {
TwirlWorkerApi.twirlWorker
.compile(
twirlClasspath().map(_.path),
twirlSources().map(_.path),
T.ctx().dest,
twirlAdditionalImports,
twirlConstructorAnnotations,
twirlCodec,
twirlInclusiveDot)
}
}
119 changes: 119 additions & 0 deletions twirllib/src/mill/twirllib/TwirlWorker.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package mill
package twirllib

import java.io.File
import java.lang.reflect.Method
import java.net.URLClassLoader

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

import scala.io.Codec

class TwirlWorker {

private var twirlInstanceCache = Option.empty[(Long, TwirlWorkerApi)]

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 _ =>
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],
constructorAnnotations: Seq[String],
codec: Codec,
inclusiveDot: Boolean) {
val o = compileMethod.invoke(null, source,
sourceDirectory,
generatedDirectory,
formatterType,
defaultAdditionalImportsMethod.invoke(null),
defaultConstructorAnnotationsMethod.invoke(null),
defaultCodecMethod.invoke(null),
defaultFlagMethod.invoke(null))
}
}
twirlInstanceCache = Some((classloaderSig, instance))
instance
}
}

def compile(twirlClasspath: Agg[Path],
sourceDirectories: Seq[Path],
dest: Path,
additionalImports: Seq[String],
constructorAnnotations: Seq[String],
codec: Codec,
inclusiveDot: Boolean)
(implicit ctx: mill.util.Ctx): mill.eval.Result[CompilationResult] = {
val compiler = twirl(twirlClasspath)

def compileTwirlDir(inputDir: Path) {
ls.rec(inputDir).filter(_.name.matches(".*.scala.(html|xml|js|txt)"))
.foreach { template =>
val extFormat = twirlExtensionFormat(template.name)
compiler.compileTwirl(template.toIO,
inputDir.toIO,
dest.toIO,
s"play.twirl.api.$extFormat",
additionalImports,
constructorAnnotations,
codec,
inclusiveDot
)
}
}

sourceDirectories.foreach(compileTwirlDir)

val zincFile = ctx.dest / 'zinc
val classesDir = ctx.dest / 'html

mill.eval.Result.Success(CompilationResult(zincFile, PathRef(classesDir)))
}

private def twirlExtensionFormat(name: String) =
if (name.endsWith("html")) "HtmlFormat"
else if (name.endsWith("xml")) "XmlFormat"
else if (name.endsWith("js")) "JavaScriptFormat"
else "TxtFormat"
}

trait TwirlWorkerApi {
def compileTwirl(source: File,
sourceDirectory: File,
generatedDirectory: File,
formatterType: String,
additionalImports: Seq[String],
constructorAnnotations: Seq[String],
codec: Codec,
inclusiveDot: Boolean)
}

object TwirlWorkerApi {

def twirlWorker = new TwirlWorker()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@(title: String)
<html>
<body>
<h1>@title</h1>
</body>
</html>
75 changes: 75 additions & 0 deletions twirllib/test/src/mill/twirllib/HelloWorldTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package mill.twirllib

import ammonite.ops.{Path, cp, ls, mkdir, pwd, rm, _}
import mill.util.{TestEvaluator, TestUtil}
import utest.framework.TestPath
import utest.{TestSuite, Tests, assert, _}

object HelloWorldTests extends TestSuite {

trait HelloBase extends TestUtil.BaseModule {
override def millSourcePath: Path = TestUtil.getSrcPathBase() / millOuterCtx.enclosing.split('.')
}

trait HelloWorldModule extends mill.twirllib.TwirlModule {
def twirlVersion = "1.0.0"
}

object HelloWorld extends HelloBase {

object core extends HelloWorldModule {
override def twirlVersion = "1.3.15"
}
}

val resourcePath: Path = pwd / 'twirllib / '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 compileClassfiles: Seq[RelPath] = Seq[RelPath](
"hello.template.scala"
)

def tests: Tests = Tests {
'twirlVersion - {

'fromBuild - workspaceTest(HelloWorld) { eval =>
val Right((result, evalCount)) = eval.apply(HelloWorld.core.twirlVersion)

assert(
result == "1.3.15",
evalCount > 0
)
}
}
'compileTwirl - workspaceTest(HelloWorld) { eval =>
val Right((result, evalCount)) = eval.apply(HelloWorld.core.compileTwirl)

val outputFiles = ls.rec(result.classes.path)
val expectedClassfiles = compileClassfiles.map(
eval.outPath / 'core / 'compileTwirl / 'dest / 'html / _
)
assert(
result.classes.path == eval.outPath / 'core / 'compileTwirl / 'dest / 'html,
outputFiles.nonEmpty,
outputFiles.forall(expectedClassfiles.contains),
outputFiles.size == 1,
evalCount > 0
)

// don't recompile if nothing changed
val Right((_, unchangedEvalCount)) = eval.apply(HelloWorld.core.compileTwirl)

assert(unchangedEvalCount == 0)
}
}
}