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

Conversation

ggrossetie
Copy link
Contributor

@ggrossetie ggrossetie commented Apr 1, 2018

Based on @pleira implementation

This is a work in progress:

For now the main issue is that an exception is thrown when TwirlCompiler.compile is invoked.
Oddly tests are running fine in Intellij IDEA. In this case the reflection is calling JavaMirrors.JavaVanillaMethodMirror.apply(args: Any*). But when I run the tests using mill twirllib.test, the reflection is calling JavaMirrors.JavaTransformingMethodMirror.apply(args: Any*).

I've also tried to use Java reflection but without success:

val clt = cl.loadClass("play.twirl.compiler.TwirlCompiler")
val module = cl.loadClass("play.twirl.compiler.TwirlCompiler$").getField("MODULE$")
val compileMethod = clt.getMethods.find(_.getName == "compile").get
compileMethod.invoke(module, 
    source,
    sourceDirectory,
    generatedDirectory,
    formatterType,
    additionalImports,
    constructorAnnotations,
    codec,
    inclusiveDot) // error: the result type of an implicit conversion must be more specific than AnyRef

Help, feedback or review will be greatly appreciated 😄

@@ -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.

@rockjam
Copy link
Contributor

rockjam commented Apr 1, 2018

@Mogztter I'll take a look at those ClassNotFoundException issues when time permits. It's not clear what's wrong from first look

@ggrossetie
Copy link
Contributor Author

ggrossetie commented Apr 1, 2018

I'll take a look at those ClassNotFoundException issues when time permits. It's not clear what's wrong from first look

@rockjam Ok. but I'm no longer sure that this issue can be reproduced with mill twirllib.test maybe it's an issue with the Intellij IDEA tests' classpath. I will do some more testing tomorrow 😉

EDIT: I can't reproduce this issue when running the tests with mill twirllib.test so definitely an issue with IDEA classpath.

@rockjam is it possible to do remote debugging ? I've tried JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=1044" mill twirllib.test but it's not working (my breakpoints are ignored)

@ggrossetie
Copy link
Contributor Author

ggrossetie commented Apr 2, 2018

I think the reflection is struggling with the parameter type Seq[String] = Nil. To workaround this issue, I'm now using the Java API (available in Twirl 1.3+):

https://github.com/playframework/twirl/blob/f4ed321dee30c977b7f303ef1229169180beab45/compiler/src/main/java/play/japi/twirl/compiler/TwirlCompiler.java#L31

Though, I'm unable to call the compile method with a primitive boolean using reflection, so codec and inclusiveDot parameters will not be available.

@rockjam
Copy link
Contributor

rockjam commented Apr 2, 2018

is it possible to do remote debugging ?

Honestly, I didn't try. You can try
JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=1044" mill -i twirllib.testLocal

@ggrossetie
Copy link
Contributor Author

@rockjam I've created a simple project where I'm using Java reflection to call the Twirl compiler and it's working fine: https://github.com/Mogztter/mill-twirl-compiler/blob/master/app/src/Main.scala

If I'm using the same code inside the TwirlWorker, the following exception is thrown: java.lang.IllegalArgumentException: argument type mismatch.

I've checked both classpath and I didn't see any difference.
Please not that I had to add the following dependency scala-parser-combinators_2.12-1.1.0.jar in https://github.com/Mogztter/mill-twirl-compiler/blob/master/build.sc#L10

Do you reproduce this issue ? I must be doing something wrong but I can't see what... thanks again for your help 👍

@rockjam
Copy link
Contributor

rockjam commented Apr 4, 2018

@Mogztter I got one question, doesn't twirl comes with some kind of CLI, or main class? In this case, we could just run it as a subprocess

@ggrossetie
Copy link
Contributor Author

Indeed it will make the integration easier but I don't think there is a CLI.
The Twirl source code is here: https://github.com/playframework/twirl

I'm really clueless about this exception since I can't reproduce it outside Mill.

@rockjam
Copy link
Contributor

rockjam commented Apr 5, 2018

I'll try code from your PR to figure out what's going on with it in next couple days

@lihaoyi lihaoyi force-pushed the master branch 2 times, most recently from 9ebd19b to 779e453 Compare April 6, 2018 22:55
@ggrossetie
Copy link
Contributor Author

@rockjam Did you have a chance to reproduce the issue ?

@ggrossetie ggrossetie force-pushed the twirllib branch 2 times, most recently from f277471 to 896ed8a Compare April 26, 2018 18:07
@ggrossetie
Copy link
Contributor Author

Rebased on master

@rockjam
Copy link
Contributor

rockjam commented Apr 26, 2018

@Mogztter sorry, didn't have a chance to look at it yet. I'll try to take a look this weekend. Is it ok for you?

@ggrossetie
Copy link
Contributor Author

Sure thanks for taking the time.
In the mean time I will fix the build failure 😀

}
}

trait TestTwirlModule extends TwirlModule with TestModule {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure that this trait would be useful

}
}
'compileTwirl - {
'fromScratch - workspaceTest(HelloWorld) { eval =>
Copy link
Contributor

Choose a reason for hiding this comment

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

fromScratch adds one more level of nesting for single test. How about unwrapping it, and leaving 'compileTwirl -workspaceTest(HelloWorld) {...} ?

assert(
result.classes.path == eval.outPath / 'core / 'compileTwirl / 'dest / 'html,
outputFiles.nonEmpty,
outputFiles.forall(expectedClassfiles.contains),
Copy link
Contributor

Choose a reason for hiding this comment

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

compileClassfiles has only a single element. I guess we could check for a single file in tests too.

@rockjam
Copy link
Contributor

rockjam commented Apr 30, 2018

@Mogztter I couldn't reproduce theClassNotFoundException issue. I left some comments regarding PR itself. Apart from that it looks fine

@ggrossetie
Copy link
Contributor Author

@rockjam Thanks for your review!

I couldn't reproduce theClassNotFoundException issue.

You were able to call the Scala API https://github.com/playframework/twirl/blob/881b41e58350320570e6d3786cca87db5d04ecb4/compiler/src/main/scala/play/twirl/compiler/TwirlCompiler.scala#L167 ?

I left some comments regarding PR itself. Apart from that it looks fine

Ok I will fix them 👍

@rockjam
Copy link
Contributor

rockjam commented Apr 30, 2018

@Mogztter I actually did succeed with scala API of twirl. Here is a commit that uses scala API, but passes default values(from reflected TwirlCompiler class) of parameters to compile method. You can take a look here: rockjam@0daf9ca

I also included scala parser-combinator library in classpath, since otherwise it failed. with ClassNotFoundException

@rockjam
Copy link
Contributor

rockjam commented Apr 30, 2018

@Mogztter and here is the second take that removes reflective calls to default methods. But I'm not sure that @lihaoyi will aprove such tricks with classloading, since we mix mill's classloader with classloader of twirl, and we may go into trouble when mill and twirl will have different scala versions. rockjam@dc546f6

@ggrossetie
Copy link
Contributor Author

I actually did succeed with scala API of twirl. Here is a commit that uses scala API, but passes default values(from reflected TwirlCompiler class) of parameters to compile method. You can take a look here: rockjam/mill@0daf9ca

Oh I see... 🤔
Is it possible to invoke the "default value function" with the method parameter ? I mean if additionalImports or constructorAnnotations is defined in the TwirlWorker, I want these parameters to be passed to the TwirlCompiler API.

I also included scala parser-combinator library in classpath, since otherwise it failed. with ClassNotFoundException

So it's not just me 😌

nd here is the second take that removes reflective calls to default methods. But I'm not sure that @lihaoyi will aprove such tricks with classloading, since we mix mill's classloader with classloader of twirl, and we may go into trouble when mill and twirl will have different scala versions. rockjam/mill@dc546f6

Indeed, we should not mess with the classloader 😉
I think the first solution is good as long as we can pass method parameters to the TwirlCompiler API, namely:

  • additionalImports
  • constructorAnnotations
  • codec
  • inclusiveDot

@rockjam
Copy link
Contributor

rockjam commented May 1, 2018

@Mogztter Well, to pass additionalImports, constructorAnnotations and codec you have to invoke constructor of types these values have via runtime reflection in twirl's classloader, I guess

@ggrossetie
Copy link
Contributor Author

Sorry I've been busy lately, I will try to go back to this pull request next week.
The next step will be to try to compile a simple Play! application with a Twirl template (to make sure that everything is working fine together) 😄

@rockjam
Copy link
Contributor

rockjam commented May 6, 2018

Sounds great. We could add a sample play app in our integration test suite after this PR gets merged

@ggrossetie
Copy link
Contributor Author

Sounds great. We could add a sample play app in our integration test suite after this PR gets merged

That would be awesome 👍
But I think we should do it first because we will for sure find some limitations with the current implementation.

We can use this template https://github.com/playframework/play-scala-seed.g8 ?

@rockjam
Copy link
Contributor

rockjam commented May 11, 2018

I guess we could. Or we could port some real play application, built with mill. But let's do it in separate PR

@ggrossetie
Copy link
Contributor Author

Ok 👌

I've integrated your commit and rebased on master so I think we are ready.
I can squash the commits if you want ?

One last question about the API, do you think we should keep the twirl prefix ?

Or we could port some real play application, built with mill

Yes I guess we could. A popular Play application on GitHub is Lichess: https://github.com/ornicar/lila

But it will be quite challenging to build it with Mill 😋

@lihaoyi
Copy link
Member

lihaoyi commented May 11, 2018

No need to squash, Github can squash during the merge through the UI whenever @rockjam is ready.

Lichess seems like a bit ambitious, probably should start with something smaller like Hello World =P

@ggrossetie ggrossetie changed the title WIP: Add a TwirlModule to compile Twirl templates Add a TwirlModule to compile Twirl templates May 12, 2018
@rockjam
Copy link
Contributor

rockjam commented May 12, 2018

One last question about the API, do you think we should keep the twirl prefix ?

Do you mean twirl prefix in package names? If yes, lets keep it.

I will take a look at this PR this weekend

@ggrossetie
Copy link
Contributor Author

ggrossetie commented May 12, 2018

@rockjam No I meant the prefix in function names:

  • twirlVersion -> version
  • twirlSources -> sources
  • twirlClasspath -> classpath
  • compileTwirl -> compile

Also I noticed that mill dev.assembly does not contain TwirlModule, I guess we need to add twirllib here https://github.com/lihaoyi/mill/blob/032ae8b5138863c1523cf34a612ba1b19870713b/build.sc#L294

I will take a look at this PR this weekend

Thanks 👍

@Ammonite-Bot
Copy link
Collaborator

Ammonite-Bot commented May 12, 2018 via email

@ggrossetie
Copy link
Contributor Author

ggrossetie commented May 13, 2018

Ok should we rename compileTwirl to twirlCompile ?

Lichess seems like a bit ambitious, probably should start with something smaller like Hello World =P

I agree.
We should probably also need to implement a routes compiler : https://github.com/playframework/playframework/blob/master/framework/src/sbt-plugin/src/main/scala/play/sbt/routes/RoutesCompiler.scala

@rockjam
Copy link
Contributor

rockjam commented May 14, 2018

@Mogztter created separate issue for play routes compiler: #324

Ok should we rename compileTwirl tout twirlCompile ?

I don't have strong preference. compileTwirl sounds fine

sourceDirectory,
generatedDirectory,
formatterType,
defaultAdditionalImportsMethod.invoke(additionalImports),
Copy link
Contributor

Choose a reason for hiding this comment

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

How does it work, can you explain a little bit? Why do we invoke method with additionalImports parameter?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will add a test case.

The basic idea is to be able to override/define the default argument by invoking the method.
So instead of using Nil the Twirl compiler can be configured with additional imports. But maybe that's not how to do it ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rockjam Good catch! It's not working 😞
defaultAdditionalImportsMethod is a method that does not take any argument and the call is wrong (ie. the first argument must be the object):

defaultAdditionalImportsMethod.invoke(null, additionalImports)

If I replace defaultAdditionalImportsMethod.invoke(null) with additionalImports then the reflection does not work anymore and the following exception is thrown:

java.lang.IllegalArgumentException: argument type mismatch

Back to square one I guess 😢
So it's working but we cannot define/override additionalImports, constructorAnnotations, codec, or inclusiveDot.

Copy link
Contributor Author

@ggrossetie ggrossetie May 21, 2018

Choose a reason for hiding this comment

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

Interestingly, additionalImports is an instance of scala.collection.immutable.$colon$colon (maybe that's why the reflection is confused ?)

Copy link
Contributor

Choose a reason for hiding this comment

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

We can keep the defaults for these arguments for now, and merge this PR like that. And we can address modifying of these arguments in subsequent PR. How about that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I will revert my changes to invoke the method without argument

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rockjam Done! I've created (private) functions in TwirlModule and add a comment so when we know how to override these arguments, everything will be ready.

@ggrossetie
Copy link
Contributor Author

@rockjam Thanks for creating the issue.

@rockjam
Copy link
Contributor

rockjam commented May 23, 2018

@Mogztter travis fails for some reason. Can you rebase it on master to trigger CI?

@ggrossetie
Copy link
Contributor Author

@rockjam Done!

@rockjam rockjam merged commit 02436b4 into com-lihaoyi:master May 24, 2018
@rockjam
Copy link
Contributor

rockjam commented May 24, 2018

@Mogztter thank you

@lefou lefou added this to the 0.2.3 milestone May 2, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants