quarkiverse / quarkus-jooq

Quarkus Jooq Extension
Apache License 2.0
68 stars 17 forks source link

Native image compilation failure on jooq.runtime.graal.DefaultRecordMapperSubstitutions.proxy #177

Open laurentperez opened 11 months ago

laurentperez commented 11 months ago

Hello

Using latest tag 2.0.0 and quarkusPlatformVersion=3.4.3

Using container build (with Mandrel) :

./gradlew build -Dquarkus.package.type=native -Dquarkus.native.container-build=true -Dquarkus.container-image.build=true

Native compilation fails on graalvm side :


Status: Downloaded newer image for quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-17

<==========---> 80% EXECUTING [3m 4s]
========================================================================================================================
GraalVM Native Image: Generating 'xxxxxxxxxxxx' (executable)...
========================================================================================================================
For detailed information and explanations on the build output, visit:
https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/BuildOutput.md
------------------------------------------------------------------------------------------------------------------------

[1/8] Initializing...                                                                                    (0,0s @ 0,29GB)
Error: Could not find target method: private java.lang.Object io.quarkiverse.jooq.runtime.graal.DefaultRecordMapperSubstitutions.proxy()
com.oracle.svm.core.util.UserError$UserException: Could not find target method: private java.lang.Object io.quarkiverse.jooq.runtime.graal.DefaultRecordMapperSubstitutions.proxy()
        at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.UserError.abort(UserError.java:73)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.findOriginalMethod(AnnotationSubstitutionProcessor.java:872)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.handleMethodInAliasClass(AnnotationSubstitutionProcessor.java:463)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.handleAliasClass(AnnotationSubstitutionProcessor.java:427)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.substitute.AnnotationSubstitutionProcessor.handleClass(AnnotationSubstitutionProcessor.java:400)

Stacktrace side :

> There was a failure while executing work items
   > A failure occurred while executing io.quarkus.gradle.tasks.worker.BuildWorker
      > io.quarkus.builder.BuildException: Build failure: Build failed due to errors
                [error]: Build step io.quarkus.deployment.pkg.steps.NativeImageBuildStep#build threw an exception: io.quarkus.deployment.pkg.steps.NativeImageBuildStep$ImageGenerationFailureException: Image generation failed. Exit code: 1
                at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.imageGenerationFailed(NativeImageBuildStep.java:457)
                at io.quarkus.deployment.pkg.steps.NativeImageBuildStep.build(NativeImageBuildStep.java:263)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
                at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
                at java.base/java.lang.reflect.Method.invoke(Method.java:568)
                at io.quarkus.deployment.ExtensionLoader$3.execute(ExtensionLoader.java:858)
                at io.quarkus.builder.BuildContext.run(BuildContext.java:282)
laurentperez commented 11 months ago

Digging, this is related to unmatchable subsitution of

image

in https://github.com/jOOQ/jOOQ/issues/15482

laurentperez commented 11 months ago

Digging further, since proxy() is now Reflect.on I simply did a crude

rm runtime/src/main/java/io/quarkiverse/jooq/runtime/graal/DefaultRecordMapperSubstitutions.java

and rebuilt a 999-SNAPSHOT. It solves the compilation error for @Substitute however it fails later deep in JOOQ itself.

"This error is reported at image build time because class org.jooq.impl.Tools is registered for linking at image build time by command line"

It seems related to jakarta.persistence. classes graph. See Column, Entity classes :

[2/8] Performing analysis... [] (33,5s @ 0,78GB) 8 087 (70,52%) of 11 468 types reachable 9 198 (49,59%) of 18 548 fields reachable 28 869 (28,25%) of 102 195 methods reachable 3 002 types, 1 679 fields, and 7 555 methods registered for reflection 2 fatal errors detected:

Fatal error: com.oracle.graal.pointsto.util.AnalysisError$ParsingError: Error encountered while parsing org.jooq.impl.Tools$$Lambda$1568/0x00000007c262fba8.get(Unknown Source) 
Parsing context:
   at org.jooq.impl.Cache.run(Cache.java:103)
   at org.jooq.impl.Tools.getMatchingGetter(Tools.java:4458)
   at org.jooq.impl.DefaultRecordMapper$ImmutablePOJOMapper.<init>(DefaultRecordMapper.java:1057)
   at org.jooq.impl.DefaultRecordMapper.init(DefaultRecordMapper.java:568)
   at org.jooq.impl.DefaultRecordMapper.<init>(DefaultRecordMapper.java:361)
Caused by: org.graalvm.compiler.java.BytecodeParser$BytecodeParserError: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved type during parsing: jakarta.persistence.Column. This error is reported at image build time because class org.jooq.impl.Tools is registered for linking at image build time by command line
        at parsing org.jooq.impl.Tools.lambda$getAnnotatedGetter$100(Tools.java:4371)
        at jdk.internal.vm.compiler/org.graalvm.compiler.java.BytecodeParser.throwParserError(BytecodeParser.java:2536)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.phases.SharedGraphBuilderPhase$SharedBytecodeParser.throwParserError(SharedGraphBuilderPhase.java:169)
Caused by: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved type during parsing: jakarta.persistence.Entity. This error is reported at image build time because class org.jooq.impl.Tools is registered for linking at image build time by command line
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.phases.SharedGraphBuilderPhase$SharedBytecodeParser.reportUnresolvedElement(SharedGraphBuilderPhase.java:521)
        at org.graalvm.nativeimage.builder/com.oracle.svm.hosted.phases.SharedGraphBuilderPhase$SharedBytecodeParser.reportUnresolvedElement(SharedGraphBuilderPhase.java:515)
laurentperez commented 11 months ago

Latest comments after https://github.com/jOOQ/jOOQ/issues/8779#issuecomment-616177481 suggest JOOQ GraalVM support is planned for JOOQ 3.19.x and above

I'm not sure if the root UnresolvedElementException cause of the error is related to JOOQ referencing JPA itself (Entity, Column, etc.) .

Should the quarkus-jooq plugin import these JPA classes to let resolution happen at graph construction ?

laurentperez commented 11 months ago

I included jakarta.persistence as a dependency by adding a crude import of

implementation("io.quarkus:quarkus-hibernate-orm")

Then graph analysis phase [2/8] Performing analysis works :partying_face:

I'll make a proper PR without depending on H8 for JPA

laurentperez commented 11 months ago

please note that analysis and compilation work, but runtime will fail because Records are not declared for reflection

micronaut solved this thru https://github.com/micronaut-projects/micronaut-sql/blob/master/src/main/docs/guide/jooq/jooq-graalvm.adoc and https://github.com/micronaut-projects/micronaut-sql/tree/master/jooq ( https://github.com/micronaut-projects/micronaut-sql/pull/851 )

laurentperez commented 11 months ago

I can't pinpoint how to tell Quarkus how to generate reflection configuration for JOOQ Records

The JOOQ gradle plugin successfully generates the Jakarta Persistence API annotations but when the native binary is run, these annotated Records don't exist in the closed world "classpath", I get linkage errors (at runtime, not compile/build time)

I understand Micronaut-sql looks for annotations and generates an @Introspected annotation for Records, which itself is scanned at compile time and generates ReflectionConfiguration for graalvm

AFAIK Quarkus has a slightly different approach where it scans classes and generates a bytecode metamodel, and this metamodel generates the reflection configuration

I was expecting QK to work "out of the box" and generate the metamodel for JOOQ Records annotated with JPA. It does not so I'm attempting to do a PR for quarkus-jooq, but I don't see where my "missing link" between a Record and its reflection configuration is. I'm no QK expert ;)

Basically I want to augment the QK scanning process and tell it to add reflection for all public fields + constructor on JOOQ Records annotated with JPA

I'm not sure if this should be a build time thing as in https://github.com/quarkiverse/quarkus-jooq/blob/main/deployment/src/main/java/io/quarkiverse/jooq/deployment/JooqProcessor.java or a runtime one as in https://github.com/quarkiverse/quarkus-jooq/tree/main/runtime/src/main/java/io/quarkiverse/jooq/runtime

I believe the answer might be in https://github.com/quarkusio/quarkus/tree/main/extensions/hibernate-orm code because JPA-annotated H8 entities work, but I'm not sure.

Or it could be simpler and simply adding a @RegisterForReflection to the generated Records, avoiding generation of a per-record reflect-config.json. Is it the proper way to do it ?

@ranjanashish or @angrymango would you have tips on how to do it ?

Duvel commented 8 months ago

I can confirm that removing or overruling DefaultRecordMapperSubstitutions.java does help, so I guess it can be removed from the source.

@laurentperez the issues with jakarta and the reflection didn't occur for my project, so maybe this is more due to the use of micronaut?

My project does use the records in the code and that way they get registered for reflection.

Duvel commented 8 months ago

I created a pull request for removing DefaultRecordMapperSubstitutions.java #188.