-
Notifications
You must be signed in to change notification settings - Fork 270
Added GraalVM native support to Apache FreeMarker #121
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
base: 2.3-gae
Are you sure you want to change the base?
Added GraalVM native support to Apache FreeMarker #121
Conversation
54a345f to
6fc56e1
Compare
|
I misunderstood at the beginning. I’ve pushed some modifications, and hopefully, it should be fine now. I’ve also done some additional testing and made the following changes:
Additionally I tested the pull request on my fork : https://github.com/fugerit-org/freemarker/actions/workflows/ci.yml I really hope now everything is ok. See you |
6fc56e1 to
0afb662
Compare
| if (!isAutoDetected(libraryEnumToTry)) continue; | ||
| if (libraryEnumToTry == LIBRARY_LOG4J && hasLog4LibraryThatDelegatesToWorkingSLF4J()) { | ||
| // skip hasLog4LibraryThatDelegatesToWorkingSLF4J when running in GraalVM native image | ||
| if (!IS_GRAALVM_NATIVE && libraryEnumToTry == LIBRARY_LOG4J && hasLog4LibraryThatDelegatesToWorkingSLF4J()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that still prefers Log4J API instead of SLF4J API when we have log4j-over-slf4j, under GrallVM Native only, and it should prefer SLF4J in that case. I know, this Logger class is a mess. Anyway, I looked into more, and the easiest way to achieve what I said is just changing isAutoDetected so that if IS_GRAALVM_NATIVE is true, then we return true for LIBRARY_SLF4J. Actually, even better long term, let's return true for LIBRARY_COMMONS too, and that way for GraalVM Native we just brought ahead what's planned for 2.4 anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having a deeper look at it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ddekany let's see if this is ok for you.
I preferred to create separate methods so maybe it's easier to understand than complex conditional statements, do you agree?
// legacy auto-detection (until FreeMarker 2.3.X)
private static boolean isAutoDetectedLegacy( int libraryEnum ) {
return !(libraryEnum == LIBRARY_AUTO || libraryEnum == LIBRARY_NONE
|| libraryEnum == LIBRARY_SLF4J || libraryEnum == LIBRARY_COMMONS);
}
// next generation auto-detection (FreeMarker 2.4.X and on)
private static boolean isAutoDetectedNG( int libraryEnum ) {
return !(libraryEnum == LIBRARY_AUTO || libraryEnum == LIBRARY_NONE);
}
private static boolean isAutoDetected(int libraryEnum) {
// 2.4: Remove libraryEnum == LIBRARY_SLF4J || libraryEnum == LIBRARY_COMMONS (use only isAutoDetectedNG()=
return IS_GRAALVM_NATIVE ? isAutoDetectedNG(libraryEnum) : isAutoDetectedLegacy(libraryEnum);
}At beginning didn't fully grasped the meaning of all logger detection logic. I hope that now it is ok.
| { | ||
| "pattern": "\\Qfreemarker/ext/beans/DefaultMemberAccessPolicy-rules\\E", | ||
| "condition": { | ||
| "typeReachable": "freemarker.ext.beans.DefaultMemberAccessPolicy" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't we also need to add freemarker/ext/beans/unsafeMethods.properties accessed from freemarker.ext.beans.LegacyDefaultMemberAccessPolicy here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe 'freemarker/ext/beans/unsafeMethods.properties' when freemarker.ext.beans.LegacyDefaultMemberAccessPolicy is reachable? (just commited it this way)
do you think is it better to squash commits?
| */ | ||
| public void sayHello() throws Exception { | ||
| try (StringWriter buffer = new StringWriter()) { | ||
| Version version = Configuration.getVersion(); // using latest version |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new Version(Configuration.getVersion().toString()), because, recycling the current version like that is in principle not allowed (current only logs a WARN, eventually will throw exception). (Because, people almost always do this by mistake, not understanding what that setting does. So in the rare cases where you really mean to do it, there's some gymnastic involved.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, updated this one too. (in production projects I never resuse it).
rat-excludes
Outdated
| # ignore for freemarker-test-graalvm-native module (only used for GraalVM native support compliance) | ||
| freemarker-test-graalvm-native/build/** | ||
| freemarker-test-graalvm-native/README.md | ||
| freemarker-test-graalvm-native/src/main/resources/freemarker-templates/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add the licence header to the template instead (inside <#-- ... -->)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is done too. Sorry If i missed it
35e569b to
9d13f81
Compare
|
@ddekanyI have squashed everything into two commits :
I hope all your requests have been fulfilled. Regards |
9d13f81 to
c545b0a
Compare
|
Hey, thanks a lot for all the effort already invested here. 👍 I think it would be very useful to include a hint in the |
68504c0 to
9c8e84c
Compare
|
You are right @klopfdreh, I’ve tried to do my best with this addition—let me know if it looks good to you: Thanks in advance! P.S.: Initially, I was considering it, but since the README file was quite slim, I thought it would be better to include this kind of information in the broader documentation later on. |
|
Following this thread with interest @fugerit79. Thanks for the work you put in! I'm developing a PicoCLI app and would like to include Freemarker. Sadly I'm unable to build it (because of Apple Silicon?) Regarding documentation: Next to the GraalVM reference, an example reflection entry might help clarifying things. |
|
What's the build error? You are unable to build this branch, or FreeMarker in general? |
|
Hello @OnnoH Which is the build issue you are experiencing? (can you provide a reproducer and explain what is not working exactly?) @ddekany @OnnoH The CI build is working smoothly on my fork : I've tried a local build with my macboc pro M1 and that worked too. About the reflecting sample I linked the reachability metadata repository and the documentation, where there are plenty of examples. |
|
@OnnoH Here's a build of this branch: https://freemarker.apache.org/builds/pr121/ |
|
Thanks @fugerit79 and @ddekany. It was a toolchain issue and after installing the required legacy JDKs, the build worked! (After migrating to a new machine, I never thought about installing them ;-) Adding the new .jar to my project it produced the expected results when passing in an object or an object in a map. The picocli-codegen annotation processor added the needed reflection for this class automatically. When starting my application, it spits out this error: Do I need to include the Slf4J dependencies in my project? |
|
Hello @OnnoH , no you do not need add SLF4J if you do not use it already. I suspect for some reason this property is not set : "org.graalvm.nativeimage.imagecode" (I tested it in many scenarios and it is supposed to be always set at runtime on a GraalVM generated executable). Can you provide :
Thanks in advance. |
|
Sure @fugerit79. Image code: |
|
Hello @OnnoH it seems to me the configuration is ok. Are you using maven as build system? Are you sure your application has been actually built with your FreeMarker build? I tested the build on a few maven applications, but to do so I did :
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker-gae</artifactId>
<version>2.3.35-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.fugerit.java</groupId>
<artifactId>fj-doc-base</artifactId>
<exclusions>
<exclusion>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</exclusion>
</exclusions>
</dependency>Here you can find an example. You can check if the "standard" FreeMarker jar is still included in your build by running : mvn dependency:treeIf you want you can provide the output. The result should only include freemarker-gae artifact. Note : That is needed just because this pull request has not yet merged and published in an official release of FreeMarker. |
|
BTW, you can also have an |
|
Yes I use Maven for my project @fugerit79 and the new
Including |
|
@OnnoH, bug if you can handle and print version in template, it means after the error : _"ERROR freemarker.log.LoggerFactory: Unexpected error when initializing logging for "SLF4J". Exception: java.lang.RuntimeException: Unexpected error when creating logger factory for "SLF4J". Caused by: java.lang.ClassNotFoundException: freemarker.log.SLF4JLoggerFactory" The application is working? or you printed the version with a non native build? Thanks in advance. |
|
@OnnoH Yeah, it actually prints "nightly", not SNAPSHOT... same thing. Certainly I'm stating the obvious, but you also have to be sure that you have built that from the branch of the PR. |
|
Thanks @ddekany , @OnnoH just to be completely sure, are you building the project from the branch on my fork? (as the PR has not been merged yet) : https://github.com/fugerit-org/freemarker/tree/1-add-graalvm-support-to-apache-freemarker |
|
Yes, I'm sure @fugerit79 . Indeed after the error, the application works as expected. |
|
@OnnoH ok I see. Thanks for the feedback. I'd like to check it anyway. any chance you have a reproducer code? (Maybe a public repository?) |
Good news. I did start a project from scratch and the error no longer shows up! I'll peel back the layers of my other project to see what might cause the error. If I can reproduce it, I'll let you know. But the project is a bit of a mess because I dug too many rabbit holes, so it might take some time 😁 |
|
Found the culprits! and They both have a transitive dependency to Slf4J. Although never called, the mere presence of was apparently enough to throw the error. I'm will add it to the sample project and share it here when ready. |
|
If the |
|
@ddekany, yes, actually it should be added to |
|
I added reflection configuration for LoggerFactory implementations : I was able to reproduce and test the behaviour on a POC repository : https://github.com/fugerit-org/poc-freemarker-graalvm I tested :
It seems to work now. Note 1 : I previously tested mainly on modern framework like Quarkus or SpringBoot, which have a good built in support for GraalVM. Now the PR should be more stable. |
|
Thanks for all the work @fugerit79 and @ddekany! I pulled the changes, ran a build and tested it with my pet project and this sample: https://github.com/OnnoH/picocli-freemarker And it's squeaky clean 😜 |
|
@OnnoH thanks to you for the debugging :) |
|
Just one side note - if you are using Spring Boot you also can use RuntimeHintsRegistrar During the AOT processing at build time you can create the hints dynamically. |
|
@fugerit79 Thanks for the fixes! Regarding the README (discussed much earlier), feel free to add any pointer to it, based on the discussion here. After the merge (in a few days I think) I will move that information over into the documentation, and leave a pointer to that in the README, so no worries about the length. |
|
@ddekany Of course, is it ok this guide? I created a sample project to create this guide, if you want you can quote it : https://github.com/fugerit-org/freemarker-graalvm-sample/tree/main/native-image If you think it is useful I can add a short guide for Gradle and Maven. UPDATE : I added Maven : https://github.com/fugerit-org/freemarker-graalvm-sample/tree/main/native-image-maven and Gradle KTS sample : https://github.com/fugerit-org/freemarker-graalvm-sample/tree/main/native-image-gradle And a few more for quarkus, micronaut, springboot and helidon : https://github.com/fugerit-org/freemarker-graalvm-sample/blob/main/README.md |
native-image.properties (to handle initialized at runtime) resource-config.json (to load configuration files in resources/) isAutoDetected() in logger does not skip LIBRARY_SLF4J and LIBRARY_COMMONS when IS_GRAALVM_NATIVE = true (anticipate FreeMarker 2.4) Added GraalVM ready badge
See freemarker-test-native/README.md
To handle LoggerFactory instances handled by reflection
86dc986 to
4ad7311
Compare
|
@ddekany I did a rebase on 2.3-gae just in case. |
Native support
In this pull request :
GraalVM native support
Test module
Added a test module to build as a GraalVM native image.
CI
Updated CI to test the GraalVM native support by building and running the test module as a native executable.