paulcwarren / spring-content

Cloud-Native Storage and Enterprise Content Services (ECMS) for Spring
https://paulcwarren.github.io/spring-content/
Apache License 2.0
275 stars 67 forks source link

Maven build error with Graal VM on Spring Content #1657

Open AkshathSai opened 1 year ago

AkshathSai commented 1 year ago

Hi There,

When attempting to build my project using Graal VM, I encountered an error. I'm not sure if Spring Content supports Graal VM, but I'm raising this issue in case it's a bug or if support could be added.

Expected Result: Successful build using either of the following commands:

mvn -Pnative native:compile

or

 mvn clean package -Pnative

Actual Result: Maven build fails with the following error (stack trace below).

2023-10-25T21:17:58.174+05:30  INFO 2035 --- [           main] rocessor$ProxyRegisteringAotContribution : Created proxy type class org.springdoc.webmvc.ui.SwaggerWelcomeWebMvc$$SpringCGLIB$$0 for class org.springdoc.webmvc.ui.SwaggerWelcomeWebMvc
2023-10-25T21:17:58.181+05:30  INFO 2035 --- [           main] rocessor$ProxyRegisteringAotContribution : Created proxy type class org.springdoc.webmvc.ui.SwaggerConfigResource$$SpringCGLIB$$0 for class org.springdoc.webmvc.ui.SwaggerConfigResource
2023-10-25T21:17:58.283+05:30  INFO 2035 --- [           main] o.springframework.hateoas.aot.AotUtils   : Registering Spring HATEOAS types in org.springframework.hateoas for reflection.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  10.308 s
[INFO] Finished at: 2023-10-25T21:17:59+05:30
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:3.1.5:process-aot (process-aot) on project CinemaPass: Unable to compile generated source
[ERROR] incompatible types: bad return type in lambda expression
[ERROR]     internal.org.springframework.content.commons.config.StoreFragmentsFactoryBean cannot be converted to internal.org.springframework.content.commons.config.StoreFragments /Users/akshathsaipittala/Git/CinemaPass/target/spring-aot/main/sources/internal/org/springframework/content/commons/config/StoreFragments__BeanDefinitions.java 19:54
[ERROR] incompatible types: bad return type in lambda expression
[ERROR]     internal.org.springframework.content.commons.config.StoreFragmentsFactoryBean cannot be converted to internal.org.springframework.content.commons.config.StoreFragments /Users/akshathsaipittala/Git/CinemaPass/target/spring-aot/main/sources/internal/org/springframework/content/commons/config/StoreFragments__BeanDefinitions.java 58:54
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException
dev@Devs-MacBook-Pro CinemaPass % 

Steps to Reproduce:

  1. Use the following tech stack versions:
    • Spring Boot: 3.1.5
    • Spring Content Version: 3.0.6 (Using both spring-content-fs-boot-starter & spring-content-rest-boot-starter)
    • Maven: 3.9.2
    • Graal VM Community Edition: 21
  2. Run the following command: mvn -Pnative native:compile or mvn clean package -Pnative
paulcwarren commented 1 year ago

Hi @AkshathSai, I tried to repro this using one of the getting started guides and I am unable to. That seems to produce a native image - although I am not sure it works.

Any chance you have a project that shows the problem?

AkshathSai commented 1 year ago

Hi Paul, thx for considering this issue. You may use this attached stripped down sample replica project, the command used for native image compilation "mvn -Pnative native:compile"

tunes.zip

paulcwarren commented 1 year ago

Thanks. That's helpful. I took a look at this and the compilation error is spring-aot generated code which is obviously to do with producing the native image. Probably not adding much value by stating that alone.

As I understand it native images operate with a "closed world" principle where, in Spring terms, all beans need to be known ahead of time during compilation.

Spring in general and by implication Spring Data and Spring Content rely heavily on runtime reflection to generate proxy classes that act as the implementation for your musicRepo and musicStore (amoungst other things).

Digging into Spring Data I can see that they have added a bunch AotProcessor classes like this one for example. I am 99% sure are there to help the Spring Aot Processor generate the right BeanDefinition classes for an application.

Spring Content does not have any of these yet and therefore we can probably be reasonably confident in sayng that Spring Content does not yet support Spring Native.

At this point I don't have the first clue what those Spring Data Aot Processor classes are doing. I can guess but that about it. So I'll need to really dig into those and try and understand them as best I can. Then see if I can replicate that behavior in Spring Content. But that might take a while I am afraid.

AkshathSai commented 1 year ago

Thank you for the detailed explanation. It's clear that the issue I'm facing is due to the absence of AotProcessor classes in Spring Content, which are essential for Spring AOT to generate the right BeanDefinition classes for an application. Your insight that Spring Content doesn't support Spring Native yet is enlightening.

I understand the "closed world" principle of native images. Indeed, Spring, and by extension, Spring Data and Spring Content, heavily rely on runtime reflection to generate proxy classes that act as the implementation for repositories and stores. The lack of support for this in Spring Content when using Spring Native is the root of the problem.

I appreciate your willingness to dive deeper into the Spring Data AotProcessor classes despite your unfamiliarity with them. Your commitment to understand these classes and try to replicate their behavior in Spring Content is commendable.

I understand that this process might take some time. I thank you in advance for your efforts and look forward to updates on this issue. In the meantime, I will continue to play around in my project and keep track of updates on Spring Native's support for Spring Content.

Once again, thank you for your assistance and the time you've dedicated for considering this issue.

AkshathSai commented 2 months ago

Hey Paul, I just saw this medium article: https://medium.com/graalvm/enhancing-3rd-party-library-support-in-graalvm-native-image-with-shared-metadata-9eeae1651da4

Apparently, we can mostly get away without coding anything specific for native image support by using this fantastic Tracing Agent: https://www.graalvm.org/latest/reference-manual/native-image/metadata/AutomaticMetadataCollection/

In order to build native images we just need to supply the reachability metadata to the Graal VM and if we start any sample app that's using Spring Content using this sample reference command

Java -agentlib:native-image-agent=config-merge-dir=/Users/xyz/Downloads/graal -jar /Users/xyz/Code/StreamSpace/target/stream-space-0.0.1-SNAPSHOT.jar

and invoke the library code by watching videos (In my app case) it'll gather all the metadata in background & save it to the config-merge-dir specified here. We can filter our application specific code by applying java package names filters to collect only library specific metadata (like below in case of grade need to search for maven...)

{
  ”rules”: [
    {”excludeClasses”: ”**”},
    {”includeClasses”: ”com.github.paulcwarren.**”}
  ]
}

and finally create PR in this repo https://github.com/oracle/graalvm-reachability-metadata/tree/master for automatically building native images using the build native image command leveraging our reachability-metadata.

Here's the link for a sample app jar you may use for doing this: https://github.com/AkshathSai/StreamSpace/releases/tag/test

Download the jar and paste some sample video clip in /Users//Movies or /Users//Downloads folder if you use MacOS or //Videos folder if you use Windows machine After placing the video in the respective folder, start the server JAR file using a cmd like below & go to localhost:8080 on your browser (Pls note you have to use a GraalVM OpenJDK/OracleJDK for the agent)

Java -agentlib:native-image-agent=config-merge-dir=/Users/xyz/Downloads/graal -jar /Users/xyz/Code/StreamSpace/target/stream-space-0.0.1-SNAPSHOT.jar

Click on the personal media tab in the UI and you'll see the name of the video clip you've kept in your folder Click the clip name, a video player will start and click play button to start playing the video, now do this for a different types of files like .mp3 for audio & .mp4 .mkv video formats etc to invoke as much library code as possible

Finally, Gracefully shut down the JAR by using control + c on your keyboard and go to the 'config-merge-dir' you've specified in the cmd for the readability metadata JSON files.

Hope this helps! Thanks for this fantastic library 😊