",
+ "parameterTypes" : [ ]
+ },{
+ "name" : "getDescription",
+ "parameterTypes" : [ ]
+ } ]
+}]
+```
+
+#### Build the native image
+
+```shell
+#!/bin/bash
+
+# setting up environment
+export BASEDIR=.
+export CP=./lib/freemarker-gae-2.3.35-SNAPSHOT.jar:.
+
+# just in time application build
+javac -cp ${CP} -d build ./src/FreeMarkerGraalVMSample.java
+
+# ahead of time application build
+#
+# -H:IncludeResources=^templates/.*
+# will make the templates available to the native-image
+#
+# -H:ReflectionConfigurationFiles=./config/custom-reflect-config.json
+# will setup reflection custom configuration
+native-image \
+ -cp "${CP}:build" \
+ -H:Path=build \
+ -H:Class=FreeMarkerGraalVMSample \
+ -H:IncludeResources=^templates/.* \
+ -H:+UnlockExperimentalVMOptions \
+ -H:ReflectionConfigurationFiles=./config/custom-reflect-config.json \
+ --no-fallback \
+ --report-unsupported-elements-at-runtime
+
+# running the application
+./build/freemarkergraalvmsample
+```
\ No newline at end of file
diff --git a/freemarker-core/src/main/java/freemarker/log/Logger.java b/freemarker-core/src/main/java/freemarker/log/Logger.java
index 0a197e62c..6eafc2c88 100644
--- a/freemarker-core/src/main/java/freemarker/log/Logger.java
+++ b/freemarker-core/src/main/java/freemarker/log/Logger.java
@@ -32,7 +32,11 @@
* {@code log4j-over-slf4j} properly installed (means, you have no real Log4j in your class path, and SLF4J has a
* backing implementation like {@code logback-classic}), then FreeMarker will use SLF4J directly instead of Log4j (since
* FreeMarker 2.3.22).
- *
+ *
+ * NOTE: When running on GraalVM native image (system property 'org.graalvm.nativeimage.imagecode' set),
+ * FreeMarker 2.4 behaviour will be anticipated (SLF4J / Apache Commons are auto-detected).
+ * Additionally, log4j-over-slf4j lookup is skipped.
+ *
*
* If the auto detection sequence describet above doesn't give you the result that you want, see
* {@link #SYSTEM_PROPERTY_NAME_LOGGER_LIBRARY}.
@@ -157,6 +161,9 @@ public abstract class Logger {
private static final String REAL_LOG4J_PRESENCE_CLASS = "org.apache.log4j.FileAppender";
private static final String LOG4J_OVER_SLF4J_TESTER_CLASS = "freemarker.log._Log4jOverSLF4JTester";
+ // it is true if running in a GraalVM native build (issue #229) - see https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/ImageInfo.html#PROPERTY_IMAGE_CODE_KEY
+ private static final boolean IS_GRAALVM_NATIVE = System.getProperty( "org.graalvm.nativeimage.imagecode" ) != null;
+
/**
* Order matters! Starts with the lowest priority.
*/
@@ -193,12 +200,22 @@ private static String getLibraryName(int libraryEnum) {
return LIBRARIES_BY_PRIORITY[(libraryEnum - 1) * 2 + 1];
}
- private static boolean isAutoDetected(int libraryEnum) {
- // 2.4: Remove libraryEnum == LIBRARY_SLF4J || libraryEnum == LIBRARY_COMMONS
+ // 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 isAutoDetectedNG())
+ return IS_GRAALVM_NATIVE ? isAutoDetectedNG(libraryEnum) : isAutoDetectedLegacy(libraryEnum);
+ }
+
private static int libraryEnum;
private static LoggerFactory loggerFactory;
private static boolean initializedFromSystemProperty;
@@ -428,7 +445,8 @@ private static LoggerFactory createLoggerFactory(int libraryEnum) throws ClassNo
if (libraryEnum == LIBRARY_AUTO) {
for (int libraryEnumToTry = MAX_LIBRARY_ENUM; libraryEnumToTry >= MIN_LIBRARY_ENUM; libraryEnumToTry--) {
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()) {
libraryEnumToTry = LIBRARY_SLF4J;
}
@@ -443,7 +461,7 @@ private static LoggerFactory createLoggerFactory(int libraryEnum) throws ClassNo
e);
}
}
- logWarnInLogger("Auto detecton couldn't set up any logger libraries; FreeMarker logging suppressed.");
+ logWarnInLogger("Auto detection couldn't set up any logger libraries; FreeMarker logging suppressed.");
return new _NullLoggerFactory();
} else {
return createLoggerFactoryForNonAuto(libraryEnum);
diff --git a/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/native-image.properties b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/native-image.properties
new file mode 100644
index 000000000..75b3bdea1
--- /dev/null
+++ b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/native-image.properties
@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+Args = --initialize-at-run-time=freemarker.ext.jython.JythonWrapper,\
+ --initialize-at-run-time=freemarker.ext.jython.JythonModel
\ No newline at end of file
diff --git a/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/reflect-config.json b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/reflect-config.json
new file mode 100644
index 000000000..9cf36441f
--- /dev/null
+++ b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/reflect-config.json
@@ -0,0 +1,82 @@
+[ {
+ "condition" : {
+ "typeReachable" : "freemarker.template.Configuration"
+ },
+ "name" : "freemarker.log._SLF4JLoggerFactory",
+ "methods" : [ {
+ "name" : "",
+ "parameterTypes" : [ ]
+ } ]
+}, {
+ "condition" : {
+ "typeReachable" : "freemarker.template.Configuration"
+ },
+ "name" : "freemarker.log.SLF4JLoggerFactory",
+ "methods" : [ {
+ "name" : "",
+ "parameterTypes" : [ ]
+ } ]
+}, {
+ "condition" : {
+ "typeReachable" : "freemarker.template.Configuration"
+ },
+ "name" : "freemarker.log._AvalonLoggerFactory.java",
+ "methods" : [ {
+ "name" : "",
+ "parameterTypes" : [ ]
+ } ]
+}, {
+ "condition" : {
+ "typeReachable" : "freemarker.template.Configuration"
+ },
+ "name" : "freemarker.log._CommonsLoggingLoggerFactory.java",
+ "methods" : [ {
+ "name" : "",
+ "parameterTypes" : [ ]
+ } ]
+}, {
+ "condition" : {
+ "typeReachable" : "freemarker.template.Configuration"
+ },
+ "name" : "freemarker.log._JULLoggerFactory.java",
+ "methods" : [ {
+ "name" : "",
+ "parameterTypes" : [ ]
+ } ]
+}, {
+ "condition" : {
+ "typeReachable" : "freemarker.template.Configuration"
+ },
+ "name" : "freemarker.log._Log4jLoggerFactory.java",
+ "methods" : [ {
+ "name" : "",
+ "parameterTypes" : [ ]
+ } ]
+}, {
+ "condition" : {
+ "typeReachable" : "freemarker.template.Configuration"
+ },
+ "name" : "freemarker.log._Log4jOverSLF4JTester.java",
+ "methods" : [ {
+ "name" : "",
+ "parameterTypes" : [ ]
+ } ]
+}, {
+ "condition" : {
+ "typeReachable" : "freemarker.template.Configuration"
+ },
+ "name" : "freemarker.log._NullLoggerFactory.java",
+ "methods" : [ {
+ "name" : "",
+ "parameterTypes" : [ ]
+ } ]
+}, {
+ "condition" : {
+ "typeReachable" : "freemarker.template.Configuration"
+ },
+ "name" : "freemarker.log.CommonsLoggingLoggerFactory.java",
+ "methods" : [ {
+ "name" : "",
+ "parameterTypes" : [ ]
+ } ]
+} ]
\ No newline at end of file
diff --git a/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/resource-config.json b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/resource-config.json
new file mode 100644
index 000000000..02908d662
--- /dev/null
+++ b/freemarker-core/src/main/resources/META-INF/native-image/org.freemarker/freemarker/resource-config.json
@@ -0,0 +1,25 @@
+{
+ "bundles": [],
+ "resources": {
+ "includes": [
+ {
+ "pattern": "\\Qfreemarker/ext/beans/DefaultMemberAccessPolicy-rules\\E",
+ "condition": {
+ "typeReachable": "freemarker.ext.beans.DefaultMemberAccessPolicy"
+ }
+ },
+ {
+ "pattern": "\\Qfreemarker/ext/beans/unsafeMethods.properties\\E",
+ "condition": {
+ "typeReachable": "freemarker.ext.beans.LegacyDefaultMemberAccessPolicy"
+ }
+ },
+ {
+ "pattern": "\\Qfreemarker/version.properties\\E",
+ "condition": {
+ "typeReachable": "freemarker.template.utility.ClassUtil"
+ }
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/freemarker-test-graalvm-native/README.md b/freemarker-test-graalvm-native/README.md
new file mode 100644
index 000000000..269045cc1
--- /dev/null
+++ b/freemarker-test-graalvm-native/README.md
@@ -0,0 +1,48 @@
+# freemarker-test-graalvm-natice
+
+Test project for GraalVM support
+
+Requirements :
+
+- GraalVM 21+
+
+## Quickstart
+
+1. Build the main Apache FreeMarker proejct :
+
+```shell
+./gradlew "-Pfreemarker.signMethod=none" "-Pfreemarker.allowUnsignedReleaseBuild=true" clean build
+```
+
+2. Build the test module native image with GraalVM :
+
+```shell
+./gradlew :freemarker-test-graalvm-native:nativeCompile
+```
+
+3. Run the project :
+
+```shell
+./freemarker-test-graalvm-native/build/native/nativeCompile/freemarker-test-graalvm-native
+```
+
+Output should be similar to :
+
+```txt
+INFO: name : FreeMarker Native Demo, version : 2.3.35-nightly
+Jan 15, 2025 4:28:19 PM freemarker.log._JULLoggerFactory$JULLogger info
+INFO: result :
+
+
+ Hello : FreeMarker GraalVM Native Demo
+
+
+ Hello : FreeMarker GraalVM Native Demo
+ Test template for Apache FreeMarker GraalVM native support (2.3.35-nightly)
+
+
+```
+
+## CI (GitHub workflow)
+
+GraalVM native test for this module is included in the GitHub [CI](../.github/workflows/ci.yml) worflow.
\ No newline at end of file
diff --git a/freemarker-test-graalvm-native/build.gradle.kts b/freemarker-test-graalvm-native/build.gradle.kts
new file mode 100644
index 000000000..ab3624ed8
--- /dev/null
+++ b/freemarker-test-graalvm-native/build.gradle.kts
@@ -0,0 +1,71 @@
+import org.graalvm.buildtools.gradle.tasks.BuildNativeImageTask
+import java.util.Arrays
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ java
+ application
+ id("org.graalvm.buildtools.native") version "0.10.3"
+}
+
+group = "org.freemarker"
+version = rootProject.version
+
+val graalSdkVersion = "24.1.1"
+val junitJupiterVersion = "5.11.4"
+
+val profile = findProperty("profile") as String? ?: "default"
+
+dependencies {
+ implementation(project(":"))
+ implementation("org.python:jython:2.5.0")
+ compileOnly("org.graalvm.sdk:graal-sdk:$graalSdkVersion")
+ testImplementation("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion")
+ testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion")
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ withSourcesJar()
+ withJavadocJar()
+}
+
+application {
+ mainClass.set("org.freemarker.core.graal.HelloFreeMarker")
+}
+
+tasks.test {
+ useJUnitPlatform()
+ testLogging {
+ events("PASSED", "FAILED", "SKIPPED")
+ }
+}
+
+graalvmNative {
+ binaries.all {
+ fallback.set(false)
+ verbose.set(true)
+ resources.autodetect()
+ buildArgs.add( "-H:ReflectionConfigurationFiles=$projectDir/src/main/config/reflect-config.json" )
+ jvmArgs()
+ }
+}
diff --git a/freemarker-test-graalvm-native/src/main/config/reflect-config.json b/freemarker-test-graalvm-native/src/main/config/reflect-config.json
new file mode 100644
index 000000000..f3f506643
--- /dev/null
+++ b/freemarker-test-graalvm-native/src/main/config/reflect-config.json
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "org.freemarker.core.graal.HelloDataModel",
+ "methods": [
+ { "name": "getName", "parameterTypes": [] },
+ { "name": "getVersion", "parameterTypes": [] }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloDataModel.java b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloDataModel.java
new file mode 100644
index 000000000..3052f0e04
--- /dev/null
+++ b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloDataModel.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.freemarker.core.graal;
+
+public class HelloDataModel {
+
+ private String name;
+
+ private String version;
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+}
diff --git a/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloFreeMarker.java b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloFreeMarker.java
new file mode 100644
index 000000000..f35dc1939
--- /dev/null
+++ b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloFreeMarker.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.freemarker.core.graal;
+
+import freemarker.log.Logger;
+
+public class HelloFreeMarker {
+
+ private final static Logger log = Logger.getLogger(HelloFreeMarker.class.getName());
+
+ public static void main( String[] args ) throws Exception {
+ HelloHandler helloHandler = new HelloHandler();
+ helloHandler.sayHello();
+ }
+
+}
diff --git a/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloHandler.java b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloHandler.java
new file mode 100644
index 000000000..2cd7271c3
--- /dev/null
+++ b/freemarker-test-graalvm-native/src/main/java/org/freemarker/core/graal/HelloHandler.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.freemarker.core.graal;
+
+import freemarker.log.Logger;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.Version;
+
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Simple test class, able to say hello.
+ */
+public class HelloHandler {
+
+ private final static Logger log = Logger.getLogger(HelloHandler.class.getName());
+
+ public HelloHandler() throws ClassNotFoundException {
+ // test native configuration
+ Class.forName("freemarker.ext.jython.JythonModel");
+ }
+
+ /**
+ * This method will say hello by printing an Apache FreeMarker template
+ *
+ * @throws Exception if any unexpected situation occurs
+ */
+ public void sayHello() throws Exception {
+ try (StringWriter buffer = new StringWriter()) {
+ Version version = new Version(Configuration.getVersion().toString()); // using latest version
+ // creates FreeMarker configuration
+ Configuration cfg = new Configuration( version );
+ cfg.setClassForTemplateLoading(HelloDataModel.class, "/freemarker-templates/");
+ // creates data model
+ HelloDataModel data = new HelloDataModel();
+ data.setName( "FreeMarker GraalVM Native Demo" );
+ data.setVersion( version.toString() );
+ log.info( String.format( "name : %s, version : %s", data.getName(), data.getVersion() ) );
+ Map input = new HashMap<>();
+ input.put( "data", data );
+ // process template
+ Template template = cfg.getTemplate( "hello-world.ftl" );
+ template.process( input, buffer );
+ log.info( String.format( "result :\n%s", buffer ) );
+ }
+ }
+
+}
diff --git a/freemarker-test-graalvm-native/src/main/resources/freemarker-templates/hello-world.ftl b/freemarker-test-graalvm-native/src/main/resources/freemarker-templates/hello-world.ftl
new file mode 100644
index 000000000..31cd70d15
--- /dev/null
+++ b/freemarker-test-graalvm-native/src/main/resources/freemarker-templates/hello-world.ftl
@@ -0,0 +1,27 @@
+<#--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+
+ Hello : ${data.name}
+
+
+ Hello : ${data.name}
+ Test template for Apache FreeMarker GraalVM native support (${data.version})
+
+
\ No newline at end of file
diff --git a/freemarker-test-graalvm-native/src/test/java/org/freemarker/core/graal/HelloFreeMarkerTest.java b/freemarker-test-graalvm-native/src/test/java/org/freemarker/core/graal/HelloFreeMarkerTest.java
new file mode 100644
index 000000000..e20256006
--- /dev/null
+++ b/freemarker-test-graalvm-native/src/test/java/org/freemarker/core/graal/HelloFreeMarkerTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.freemarker.core.graal;
+
+import freemarker.log.Logger;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class HelloFreeMarkerTest {
+
+ private final static Logger log = Logger.getLogger(HelloFreeMarker.class.getName());
+
+ @Test
+ public void testMain() {
+ try {
+ HelloFreeMarker.main(new String[0]);
+ Assertions.assertTrue( Boolean.TRUE );
+ } catch (Exception e) {
+ String message = String.format( "Error : %s", e );
+ log.error( message , e );
+ Assertions.fail( message );
+ }
+ }
+
+}
diff --git a/rat-excludes b/rat-excludes
index 77fd6e6f1..ba7269790 100644
--- a/rat-excludes
+++ b/rat-excludes
@@ -68,6 +68,10 @@ gradle/**
.directory
.Trash*
+# 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
+
# Well known files that need no license note:
# -------------------------------------------
diff --git a/settings.gradle.kts b/settings.gradle.kts
index e74adfb97..92bfdb546 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -34,3 +34,5 @@ dependencyResolutionManagement {
}
}
}
+
+include("freemarker-test-graalvm-native")
\ No newline at end of file