ultraq / thymeleaf-layout-dialect

A dialect for Thymeleaf that lets you build layouts and reusable templates in order to improve code reuse

Home Page:https://ultraq.github.io/thymeleaf-layout-dialect/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

GraalVM native-image fails with minimum configuration

shainegordon opened this issue · comments

I'm very new to GraalVM, so I am hoping this is a simple fix, with some better understanding.

I was having issues with GraalVM and Groovy dependencies while using version nz.net.ultraq.thymeleaf.thymeleaf-layout-dialect@3.2.0, on step 7 - compiling

I then found this issue - #232 - which alerted me to the existence of 3.2.1

However, this doesnt actually allow this lib to be used in a project that wants to use GraalVM.

Quite easy to reproduce, and I have pushed to a demo repository - https://github.com/shainegordon/thymleaf_layout_graalvm
This can be reproduced by running either
docker build -t thymleaf_layout_graalvm .
OR
./mvnw clean native:compile -Pnative

This is a minimal project, created at https://start.spring.io/, with only "Web" and "Native" added. Kotlin, Maven, Java 17, Spring boot 3.0.5.

DemoApplication.kt

package com.example.demo

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class DemoApplication

fun main(args: Array<String>) {
	runApplication<DemoApplication>(*args)
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
        <kotlin.version>1.8.10</kotlin.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-reflect</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib-jdk8</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.thymeleaf</groupId>
            <artifactId>thymeleaf-spring6</artifactId>
            <version>3.1.1.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>nz.net.ultraq.thymeleaf</groupId>
            <artifactId>thymeleaf-layout-dialect</artifactId>
            <version>3.2.1</version>
        </dependency>

    </dependencies>

    <build>
        <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
        <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.hibernate.orm.tooling</groupId>
                <artifactId>hibernate-enhance-maven-plugin</artifactId>
                <version>${hibernate.version}</version>
                <executions>
                    <execution>
                        <id>enhance</id>
                        <goals>
                            <goal>enhance</goal>
                        </goals>
                        <configuration>
                            <enableLazyInitialization>true</enableLazyInitialization>
                            <enableDirtyTracking>true</enableDirtyTracking>
                            <enableAssociationManagement>true</enableAssociationManagement>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <configuration>
                    <args>
                        <arg>-Xjsr305=strict</arg>
                    </args>
                    <compilerPlugins>
                        <plugin>spring</plugin>
                        <plugin>jpa</plugin>
                    </compilerPlugins>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-allopen</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-noarg</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

</project>

This will basically produce the following output/error

GraalVM Native Image: Generating 'demo' (executable)...
========================================================================================================================
[1/7] Initializing...                                                                                    (5,6s @ 0,32GB)
 Version info: 'GraalVM 22.3.1 Java 17 CE'
 Java version info: '17.0.6+10-jvmci-22.3-b13'
 C compiler: gcc (linux, x86_64, 11.3.0)
 Garbage collector: Serial GC
 2 user-specific feature(s)
 - com.oracle.svm.polyglot.groovy.GroovyIndyInterfaceFeature
 - org.springframework.aot.nativex.feature.PreComputeFieldFeature
Field org.apache.commons.logging.LogAdapter#log4jSpiPresent set to true at build time
Field org.apache.commons.logging.LogAdapter#log4jSlf4jProviderPresent set to true at build time
Field org.apache.commons.logging.LogAdapter#slf4jSpiPresent set to true at build time
Field org.apache.commons.logging.LogAdapter#slf4jApiPresent set to true at build time
Field org.springframework.core.KotlinDetector#kotlinPresent set to true at build time
Field org.springframework.core.KotlinDetector#kotlinReflectPresent set to true at build time
Field org.springframework.core.NativeDetector#imageCode set to true at build time
Field org.springframework.format.support.DefaultFormattingConversionService#jsr354Present set to false at build time
Field org.springframework.cglib.core.AbstractClassGenerator#imageCode set to true at build time
[2/7] Performing analysis...  []                                                                         (8,5s @ 3,78GB)
   9 047 (82,33%) of 10 989 classes reachable
  12 287 (66,39%) of 18 507 fields reachable
  37 945 (49,30%) of 76 964 methods reachable
     612 classes,   363 fields, and 3 625 methods registered for reflection

Error: Classes that should be initialized at run time got initialized during image building:
 java.beans.Introspector was unintentionally initialized at build time. To see why java.beans.Introspector got initialized use --trace-class-initialization=java.beans.Introspector
To see how the classes got initialized, use --trace-class-initialization=java.beans.Introspector
Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception

If we include --trace-class-initialization=java.beans.Introspector (by manually running the native-image command that maven will echo), then we get the following:

GraalVM Native Image: Generating 'demo' (executable)...
========================================================================================================================
[1/7] Initializing...                                                                                    (6,0s @ 0,32GB)
 Version info: 'GraalVM 22.3.1 Java 17 CE'
 Java version info: '17.0.6+10-jvmci-22.3-b13'
 C compiler: gcc (linux, x86_64, 11.3.0)
 Garbage collector: Serial GC
 2 user-specific feature(s)
 - com.oracle.svm.polyglot.groovy.GroovyIndyInterfaceFeature
 - org.springframework.aot.nativex.feature.PreComputeFieldFeature
Field org.springframework.core.NativeDetector#imageCode set to true at build time
Field org.apache.commons.logging.LogAdapter#log4jSpiPresent set to true at build time
Field org.apache.commons.logging.LogAdapter#log4jSlf4jProviderPresent set to true at build time
Field org.apache.commons.logging.LogAdapter#slf4jSpiPresent set to true at build time
Field org.apache.commons.logging.LogAdapter#slf4jApiPresent set to true at build time
Field org.springframework.core.KotlinDetector#kotlinPresent set to true at build time
Field org.springframework.core.KotlinDetector#kotlinReflectPresent set to true at build time
Field org.springframework.format.support.DefaultFormattingConversionService#jsr354Present set to false at build time
Field org.springframework.cglib.core.AbstractClassGenerator#imageCode set to true at build time
[2/7] Performing analysis...  []                                                                         (8,8s @ 4,07GB)
   8 959 (82,17%) of 10 903 classes reachable
  12 221 (66,47%) of 18 385 fields reachable
  37 732 (49,30%) of 76 543 methods reachable
     612 classes,   363 fields, and 3 624 methods registered for reflection

Error: Classes that should be initialized at run time got initialized during image building:
 java.beans.Introspector was unintentionally initialized at build time. groovy.lang.Closure caused initialization of this class with the following trace: 
        at java.beans.Introspector.<clinit>(Introspector.java:136)
        at groovy.lang.MetaClassImpl.lambda$addProperties$25(MetaClassImpl.java:3464)
        at groovy.lang.MetaClassImpl$$Lambda$1474/0x00000007c1ccc6b0.run(Unknown Source)
        at java.security.AccessController.executePrivileged(AccessController.java:807)
        at java.security.AccessController.doPrivileged(AccessController.java:569)
        at groovy.lang.MetaClassImpl.doPrivileged(MetaClassImpl.java:3496)
        at groovy.lang.MetaClassImpl.addProperties(MetaClassImpl.java:3464)
        at groovy.lang.MetaClassImpl.reinitialize(MetaClassImpl.java:3446)
        at groovy.lang.MetaClassImpl.initialize(MetaClassImpl.java:3439)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.<init>(MetaClassRegistryImpl.java:142)
        at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.<init>(MetaClassRegistryImpl.java:94)
        at groovy.lang.GroovySystem.<clinit>(GroovySystem.java:37)
        at org.codehaus.groovy.runtime.InvokerHelper.<clinit>(InvokerHelper.java:71)
        at groovy.lang.GroovyObjectSupport.getDefaultMetaClass(GroovyObjectSupport.java:46)
        at groovy.lang.GroovyObjectSupport.<init>(GroovyObjectSupport.java:32)
        at groovy.lang.Closure.<init>(Closure.java:215)
        at groovy.lang.Closure.<init>(Closure.java:232)
        at groovy.lang.Closure$1.<init>(Closure.java:197)
        at groovy.lang.Closure.<clinit>(Closure.java:197)

At this point I am stuck, as I dont know enough about GraalVM to debug this.

It is also possible that this library cannot actually work with Spring Boot & GraalVM, as Groovy does not seem to be supported as per https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-with-GraalVM#general

+1 I'm facing the exact same issue

Thanks for raising this and for putting together a demo project for me to test with - I'm also new to GraalVM and mostly learned what I know while debugging that last issue that you linked to! 😅 (I was surprised that I could even get something to go given that Groovy is very much in the 'not supported' list of GraalVM.)

I'll give this a look when I get some time and hopefully your demo project reveals something new that I've missed 🤞

Had a look at this over the last few weekends and managed to get something going, and once I pared it all back to see what actually made a difference the solution came back to that java.beans.Introspector was unintentionally initialized at build time. error that was in the initial post.

I had this error while working on the linked issue as well, and was able to get around it by telling GraalVM to initialize those classes at build time anyway: https://github.com/ultraq/thymeleaf-layout-dialect/blob/1af5824e33f61923a7f5700f2c41ede885f35b62/thymeleaf-layout-dialect-benchmark/source/META-INF/native-image/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect-benchmark/native-image.properties

So translating those to your setup, I made the following modifications to your pom.xml file:

Configure the native-maven-plugin:

<plugin>
  <groupId>org.graalvm.buildtools</groupId>
  <artifactId>native-maven-plugin</artifactId>
  <configuration>
    <buildArgs>
      <arg>--initialize-at-build-time=com.sun.beans</arg>
      <arg>--initialize-at-build-time=java.beans.Introspector</arg>
    </buildArgs>
  </configuration>
</plugin>

And I also had to add <arg>-jvm-target=17</arg> to the <configuration>/<args> section of the kotlin-maven-plugin to get it to compile on my machine, but it looks like your setup didn't need it? 🤷‍♂️

I'm now wondering if I should include those args in the layout dialect bundle that gets uploaded (I do include a bunch of other stuff which I'm still on the fence about: https://github.com/ultraq/thymeleaf-layout-dialect/blob/1af5824e33f61923a7f5700f2c41ede885f35b62/thymeleaf-layout-dialect/source/META-INF/native-image/nz.net.ultraq.thymeleaf/thymeleaf-layout-dialect/native-image.properties The line with where the responsibility for all this lies is getting very fuzzy and feels like something that should be fixed once GraalVM provides proper Groovy support 🤔

Somehow I missed your update @ultraq

I’ll definitely revisit this on my side too